Skip to content

Commit

Permalink
Use union types for TransferReceipt (#197)
Browse files Browse the repository at this point in the history
  • Loading branch information
artursapek authored Jan 4, 2024
1 parent ed182b5 commit 8849160
Show file tree
Hide file tree
Showing 3 changed files with 173 additions and 89 deletions.
89 changes: 61 additions & 28 deletions connect/src/protocols/cctpTransfer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,17 @@ import { signSendWait } from "../common";
import { DEFAULT_TASK_TIMEOUT } from "../config";
import { Wormhole } from "../wormhole";
import {
AttestedTransferReceipt,
CompletedTransferReceipt,
SourceFinalizedTransferReceipt,
SourceInitiatedTransferReceipt,
TransferQuote,
TransferReceipt,
TransferState,
WormholeTransfer,
hasReachedState,
isAttested,
isSourceFinalized,
isSourceInitiated,
} from "../wormholeTransfer";

type CircleTransferProtocol = "CircleBridge" | "AutomaticCircleBridge";
Expand Down Expand Up @@ -473,7 +479,7 @@ export class CircleTransfer<N extends Network = Network>
// This attestation may be either the auto relay vaa or the circle attestation
// depending on the request

let receipt: Partial<TransferReceipt<CircleTransferProtocol>> = {
let receipt: TransferReceipt<CircleTransferProtocol> = {
protocol: xfer.transfer.automatic ? "AutomaticCircleBridge" : "CircleBridge",
request: xfer.transfer,
from: from.chain,
Expand All @@ -483,22 +489,44 @@ export class CircleTransfer<N extends Network = Network>

const originTxs = xfer.txids.filter((txid) => txid.chain === xfer.transfer.from.chain);
if (originTxs.length > 0) {
receipt = { ...receipt, state: TransferState.SourceInitiated, originTxs };
receipt = {
...receipt,
state: TransferState.SourceInitiated,
originTxs,
} satisfies SourceInitiatedTransferReceipt<CircleTransferProtocol>;
}

const att = xfer.attestations?.filter((a) => isWormholeMessageId(a.id)) ?? [];
const ctt = xfer.attestations?.filter((a) => isCircleMessageId(a.id)) ?? [];
const attestation = att.length > 0 ? att[0]! : ctt.length > 0 ? ctt[0]! : undefined;
if (attestation && attestation.attestation) {
receipt = { ...receipt, state: TransferState.Attested, attestation: attestation };
if (attestation) {
if (attestation.id) {
receipt = {
...(receipt as SourceInitiatedTransferReceipt<CircleTransferProtocol>),
state: TransferState.SourceFinalized,
attestation: attestation,
} satisfies SourceFinalizedTransferReceipt<CircleTransferProtocol>;

if (attestation.attestation) {
receipt = {
...receipt,
state: TransferState.Attested,
attestation: { id: attestation.id, attestation: attestation.attestation },
} satisfies AttestedTransferReceipt<CircleTransferProtocol>;
}
}
}

const destinationTxs = xfer.txids.filter((txid) => txid.chain === xfer.transfer.to.chain);
if (destinationTxs.length > 0) {
receipt = { ...receipt, state: TransferState.DestinationInitiated, destinationTxs };
receipt = {
...(receipt as AttestedTransferReceipt<CircleTransferProtocol>),
state: TransferState.DestinationInitiated,
destinationTxs,
} satisfies CompletedTransferReceipt<CircleTransferProtocol>;
}

return receipt as TransferReceipt<CircleTransferProtocol>;
return receipt;
}

// AsyncGenerator fn that produces status updates through an async generator
Expand All @@ -521,17 +549,21 @@ export class CircleTransfer<N extends Network = Network>

// Check the source chain for initiation transaction
// and capture the message id
if (hasReachedState(receipt, TransferState.SourceInitiated)) {
if (isSourceInitiated(receipt)) {
if (receipt.originTxs.length === 0)
throw "Invalid state transition: no originating transactions";

const initTx = receipt.originTxs[receipt.originTxs.length - 1]!;
const xfermsg = await CircleTransfer.getTransferMessage(_fromChain, initTx.txid);
receipt = { ...receipt, attestation: { id: xfermsg }, state: TransferState.SourceFinalized };
receipt = {
...receipt,
attestation: { id: xfermsg },
state: TransferState.SourceFinalized,
} satisfies SourceFinalizedTransferReceipt<CircleTransferProtocol, SC, DC>;
yield receipt;
}

if (hasReachedState(receipt, TransferState.SourceFinalized)) {
if (isSourceFinalized(receipt)) {
if (!receipt.attestation) throw "Invalid state transition: no attestation id";

if (receipt.protocol === "AutomaticCircleBridge") {
Expand All @@ -549,18 +581,17 @@ export class CircleTransfer<N extends Network = Network>
...receipt,
attestation: { id: receipt.attestation.id, attestation: vaa },
state: TransferState.Attested,
};
} satisfies AttestedTransferReceipt<CircleTransferProtocol, SC, DC>;
yield receipt;
}
}
}

if (hasReachedState(receipt, TransferState.Attested)) {
// First try to grab the tx status from the API
// Note: this requires a subsequent async step on the backend
// to have the dest txid populated, so it may be delayed by some time
if (isAttested(receipt) || isSourceFinalized(receipt)) {
if (!receipt.attestation) throw "Invalid state transition";

// First try to grab the tx status from the API
// Note: this requires a subsequent async step on the backend
// to have the dest txid populated, so it may be delayed by some time
const txStatus = await wh.getTransactionStatus(
receipt.attestation.id as WormholeMessageId,
leftover(start, timeout),
Expand All @@ -572,24 +603,26 @@ export class CircleTransfer<N extends Network = Network>
...receipt,
destinationTxs: [{ chain: toChain(chainId) as DC, txid: txHash }],
state: TransferState.DestinationFinalized,
};
} satisfies CompletedTransferReceipt<CircleTransferProtocol, SC, DC>;
yield receipt;
}
}

// Fall back to asking the destination chain if this VAA has been redeemed
// assuming we have the full attestation
if (hasReachedState(receipt, TransferState.Attested)) {
// Fall back to asking the destination chain if this VAA has been redeemed
// assuming we have the full attestation
if (isAttested(receipt)) {
const isComplete = await CircleTransfer.isTransferComplete(
_toChain,
receipt.attestation.attestation,
);
if (isComplete) {
receipt = {
...receipt,
state: (await CircleTransfer.isTransferComplete(
_toChain,
receipt.attestation.attestation,
))
? TransferState.DestinationFinalized
: TransferState.Attested,
};
yield receipt;
state: TransferState.DestinationFinalized,
destinationTxs: [],
} as CompletedTransferReceipt<CircleTransferProtocol, SC, DC>;
}
yield receipt;
}
}
}
94 changes: 61 additions & 33 deletions connect/src/protocols/tokenTransfer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,17 @@ import { signSendWait } from "../common";
import { DEFAULT_TASK_TIMEOUT } from "../config";
import { Wormhole } from "../wormhole";
import {
AttestedTransferReceipt,
CompletedTransferReceipt,
SourceFinalizedTransferReceipt,
SourceInitiatedTransferReceipt,
TransferQuote,
TransferReceipt,
TransferState,
WormholeTransfer,
hasReachedState,
isAttested,
isSourceFinalized,
isSourceInitiated,
} from "../wormholeTransfer";

export type TokenTransferProtocol = "TokenBridge" | "AutomaticTokenBridge";
Expand Down Expand Up @@ -527,7 +533,7 @@ export class TokenTransfer<N extends Network = Network>
const from = transfer.from.chain;
const to = transfer.to.chain;

let receipt: Partial<TransferReceipt<TokenTransferProtocol>> = {
let receipt: TransferReceipt<TokenTransferProtocol> = {
protocol,
request: transfer,
from: from,
Expand All @@ -537,31 +543,44 @@ export class TokenTransfer<N extends Network = Network>

const originTxs = xfer.txids.filter((txid) => txid.chain === transfer.from.chain);
if (originTxs.length > 0) {
receipt = { ...receipt, state: TransferState.SourceInitiated, originTxs: originTxs };
receipt = {
...receipt,
state: TransferState.SourceInitiated,
originTxs: originTxs,
} satisfies SourceInitiatedTransferReceipt<TokenTransferProtocol>;
}

const att =
xfer.attestations && xfer.attestations.length > 0 ? xfer.attestations![0]! : undefined;
const attestation =
att && att.id.emitter ? { id: att.id, attestation: att.attestation } : undefined;
if (attestation && attestation.attestation) {
receipt = {
...receipt,
state: TransferState.Attested,
attestation: attestation,
};
const attestation = att && att.id ? { id: att.id, attestation: att.attestation } : undefined;
if (attestation) {
if (attestation.id) {
receipt = {
...(receipt as SourceInitiatedTransferReceipt<TokenTransferProtocol>),
state: TransferState.SourceFinalized,
attestation: { id: attestation.id },
} satisfies SourceFinalizedTransferReceipt<TokenTransferProtocol>;

if (attestation.attestation) {
receipt = {
...receipt,
state: TransferState.Attested,
attestation: { id: attestation.id, attestation: attestation.attestation },
} satisfies AttestedTransferReceipt<TokenTransferProtocol>;
}
}
}

const destinationTxs = xfer.txids.filter((txid) => txid.chain === transfer.to.chain);
if (destinationTxs.length > 0) {
receipt = {
...receipt,
...(receipt as AttestedTransferReceipt<TokenTransferProtocol>),
state: TransferState.DestinationInitiated,
destinationTxs: destinationTxs,
};
} satisfies CompletedTransferReceipt<TokenTransferProtocol>;
}

return receipt as TransferReceipt<TokenTransferProtocol>;
return receipt;
}

// AsyncGenerator fn that produces status updates through an async generator
Expand All @@ -584,36 +603,41 @@ export class TokenTransfer<N extends Network = Network>

// Check the source chain for initiation transaction
// and capture the message id
if (hasReachedState(receipt, TransferState.SourceInitiated)) {
if (isSourceInitiated(receipt)) {
if (receipt.originTxs.length === 0) throw "Origin transactions required to fetch message id";
const { txid } = receipt.originTxs[receipt.originTxs.length - 1]!;
const msg = await TokenTransfer.getTransferMessage(
_fromChain,
txid,
leftover(start, timeout),
);
receipt = { ...receipt, state: TransferState.SourceFinalized, attestation: { id: msg } };
receipt = {
...receipt,
state: TransferState.SourceFinalized,
attestation: { id: msg },
} satisfies SourceFinalizedTransferReceipt<TokenTransferProtocol>;
yield receipt;
}

// If the source is finalized, we need to fetch the signed attestation
// so that we may deliver it to the destination chain
// or at least track the transfer through its progress
if (hasReachedState(receipt, TransferState.SourceFinalized)) {
if (isSourceFinalized(receipt)) {
if (!receipt.attestation.id) throw "Attestation id required to fetch attestation";
const { id } = receipt.attestation;
const attestation = await TokenTransfer.getTransferVaa(wh, id, leftover(start, timeout));
receipt = { ...receipt, attestation: { id, attestation }, state: TransferState.Attested };
receipt = {
...receipt,
attestation: { id, attestation },
state: TransferState.Attested,
} satisfies AttestedTransferReceipt<TokenTransferProtocol>;
yield receipt;
}

// First try to grab the tx status from the API
// Note: this requires a subsequent async step on the backend
// to have the dest txid populated, so it may be delayed by some time
if (
hasReachedState(receipt, TransferState.Attested) ||
hasReachedState(receipt, TransferState.SourceFinalized)
) {
if (isAttested(receipt) || isSourceFinalized(receipt)) {
if (!receipt.attestation.id) throw "Attestation id required to fetch redeem tx";
const { id } = receipt.attestation;
const txStatus = await wh.getTransactionStatus(id, leftover(start, timeout));
Expand All @@ -623,24 +647,28 @@ export class TokenTransfer<N extends Network = Network>
...receipt,
destinationTxs: [{ chain: toChain(chainId) as DC, txid: txHash }],
state: TransferState.DestinationFinalized,
};
} satisfies CompletedTransferReceipt<TokenTransferProtocol>;
}
yield receipt;
}

// Fall back to asking the destination chain if this VAA has been redeemed
// Note: We do not get any destinationTxs with this method
if (hasReachedState(receipt, TransferState.Attested)) {
if (isAttested(receipt)) {
if (!receipt.attestation.attestation) throw "Signed Attestation required to check for redeem";
receipt = {
...receipt,
state: (await TokenTransfer.isTransferComplete(
_toChain,
receipt.attestation.attestation as TokenTransferVAA,
))
? TransferState.DestinationFinalized
: TransferState.Attested,
};

let isComplete = await TokenTransfer.isTransferComplete(
_toChain,
receipt.attestation.attestation as TokenTransferVAA,
);

if (isComplete) {
receipt = {
...receipt,
state: TransferState.DestinationFinalized,
} satisfies CompletedTransferReceipt<TokenTransferProtocol>;
}

yield receipt;
}

Expand Down
Loading

0 comments on commit 8849160

Please sign in to comment.