From 40e74a16592cb79fb4e11989a06a46d0266896e2 Mon Sep 17 00:00:00 2001 From: Ben Guidarelli Date: Thu, 28 Dec 2023 11:25:38 -0500 Subject: [PATCH] add circle transfer receipt and tracker --- connect/src/protocols/cctpTransfer.ts | 253 ++++++++++++++--- connect/src/protocols/tokenTransfer.ts | 267 +++++++++--------- connect/src/wormholeTransfer.ts | 13 +- core/definitions/src/attestation.ts | 11 +- .../definitions/src/protocols/circleBridge.ts | 5 + 5 files changed, 367 insertions(+), 182 deletions(-) diff --git a/connect/src/protocols/cctpTransfer.ts b/connect/src/protocols/cctpTransfer.ts index 7ad7cb104..708447073 100644 --- a/connect/src/protocols/cctpTransfer.ts +++ b/connect/src/protocols/cctpTransfer.ts @@ -1,9 +1,18 @@ -import { Chain, Network, Platform, circle, encoding } from "@wormhole-foundation/sdk-base"; import { + Chain, + ChainToPlatform, + Network, + Platform, + circle, + encoding, + toChain, +} from "@wormhole-foundation/sdk-base"; +import { + Attestation, AttestationId, + AttestationReceipt, AutomaticCircleBridge, ChainContext, - CircleAttestation, CircleBridge, CircleMessageId, CircleTransferDetails, @@ -21,10 +30,17 @@ import { import { signSendWait } from "../common"; import { DEFAULT_TASK_TIMEOUT } from "../config"; import { Wormhole } from "../wormhole"; -import { TransferQuote, TransferState, WormholeTransfer } from "../wormholeTransfer"; +import { + TransferQuote, + TransferReceipt, + TransferState, + WormholeTransfer, +} from "../wormholeTransfer"; + +type CircleTransferProtocol = "CircleBridge" | "AutomaticCircleBridge"; export class CircleTransfer - implements WormholeTransfer<"CircleBridge" | "AutomaticCircleBridge"> + implements WormholeTransfer { private readonly wh: Wormhole; @@ -37,18 +53,7 @@ export class CircleTransfer // Populated after Initialized txids: TransactionId[] = []; - // Populated if !automatic and after initialized - circleAttestations?: { - id: CircleMessageId; - message: CircleBridge.Message; - attestation?: CircleAttestation; - }[]; - - // Populated if automatic and after initialized - vaas?: { - id: WormholeMessageId; - vaa?: AutomaticCircleBridge.VAA; - }[]; + attestations?: AttestationReceipt[]; private constructor(wh: Wormhole, transfer: CircleTransferDetails) { this._state = TransferState.Created; @@ -137,7 +142,7 @@ export class CircleTransfer }; const tt = new CircleTransfer(wh, details); - tt.vaas = [{ id: { emitter, sequence: vaa.sequence, chain: chain }, vaa }]; + tt.attestations = [{ id: { emitter, sequence: vaa.sequence, chain: chain }, attestation: vaa }]; tt._state = TransferState.Attested; return tt; @@ -165,7 +170,7 @@ export class CircleTransfer }; const xfer = new CircleTransfer(wh, details); - xfer.circleAttestations = [{ id: { hash }, message: msg }]; + xfer.attestations = [{ id: { hash }, attestation: { message: msg } }]; xfer._state = TransferState.SourceInitiated; return xfer; @@ -200,7 +205,7 @@ export class CircleTransfer }; ct = new CircleTransfer(wh, details); - ct.circleAttestations = [{ id: circleMessage.id, message: circleMessage.message }]; + ct.attestations = [{ id: circleMessage.id, attestation: { message: circleMessage.message } }]; } ct._state = TransferState.SourceInitiated; @@ -249,20 +254,27 @@ export class CircleTransfer } private async _fetchWormholeAttestation(timeout?: number): Promise { - if (!this.vaas || this.vaas.length == 0) throw new Error("No VAA details available"); + let attestations = (this.attestations ?? []) as AttestationReceipt<"AutomaticCircleBridge">[]; + if (!attestations || attestations.length == 0) throw new Error("No VAA details available"); // Check if we already have the VAA - for (const idx in this.vaas) { - // already got it - if (this.vaas[idx]!.vaa) continue; - this.vaas[idx]!.vaa = await CircleTransfer.getTransferVaa(this.wh, this.vaas[idx]!.id); + for (const idx in attestations) { + if (attestations[idx]!.attestation) continue; + + attestations[idx]!.attestation = await CircleTransfer.getTransferVaa( + this.wh, + attestations[idx]!.id, + timeout, + ); } + this.attestations = attestations; - return this.vaas.map((v) => v.id); + return attestations.map((v) => v.id); } private async _fetchCircleAttestation(timeout?: number): Promise { - if (!this.circleAttestations || this.circleAttestations.length == 0) { + let attestations = (this.attestations ?? []) as AttestationReceipt<"CircleBridge">[]; + if (!attestations || attestations.length == 0) { // If we dont have any circle attestations yet, we need to start by // fetching the transaction details from the source chain if (this.txids.length === 0) @@ -275,20 +287,22 @@ export class CircleTransfer const cb = await fromChain.getCircleBridge(); const circleMessage = await cb.parseTransactionDetails(txid!.txid); - this.circleAttestations = [{ id: circleMessage.id, message: circleMessage.message }]; + attestations = [{ id: circleMessage.id, attestation: { message: circleMessage.message } }]; } - for (const idx in this.circleAttestations) { - const ca = this.circleAttestations[idx]!; - if (ca.attestation) continue; // already got it + for (const idx in attestations) { + const ca = attestations[idx]!; + if (ca.attestation.attestation) continue; // already got it const attestation = await this.wh.getCircleAttestation(ca.id.hash, timeout); if (attestation === null) throw new Error("No attestation available after timeout exhausted"); - this.circleAttestations[idx]!.attestation = attestation; + attestations[idx].attestation.attestation = attestation; } - return this.circleAttestations.map((v) => v.id); + this.attestations = attestations; + + return attestations.map((v) => v.id); } // wait for the VAA to be ready @@ -326,10 +340,10 @@ export class CircleTransfer // If its automatic, this does not need to be called if (this.transfer.automatic) { - if (!this.vaas) throw new Error("No VAA details available"); - if (this.vaas.length > 1) throw new Error(`Expected a VAA, found ${this.vaas.length}`); - - const { vaa } = this.vaas[0]!; + if (!this.attestations) throw new Error("No VAA details available"); + const vaa = this.attestations.find((a) => + isWormholeMessageId(a.id), + ) as AttestationReceipt<"AutomaticCircleBridge">; if (!vaa) throw new Error("No VAA found"); //const tb = await toChain.getAutomaticCircleBridge(); @@ -338,16 +352,21 @@ export class CircleTransfer throw new Error("No method to redeem auto circle bridge tx (yet)"); } - if (!this.circleAttestations) throw new Error("No Circle Attestations found"); + if (!this.attestations) throw new Error("No Circle Attestations found"); - if (this.circleAttestations.length > 1) - throw new Error( - `Expected a single circle attestation, found ${this.circleAttestations.length}`, - ); + const circleAttestations = this.attestations.filter((a) => + isCircleMessageId(a.id), + ) as AttestationReceipt<"CircleBridge">[]; + + if (circleAttestations.length > 1) + throw new Error(`Expected a single circle attestation, found ${circleAttestations.length}`); const toChain = this.wh.getChain(this.transfer.to.chain); - const { id, message, attestation } = this.circleAttestations[0]!; + const { + id, + attestation: { message, attestation }, + } = circleAttestations[0]!; if (!attestation) throw new Error(`No Circle Attestation for ${id.hash}`); @@ -411,6 +430,13 @@ export class CircleTransfer }; } + static async isTransferComplete( + toChain: ChainContext, + attestation: Attestation, + ) { + throw new Error("Not implemented"); + } + static async getTransferVaa( wh: Wormhole, wormholeMessageId: WormholeMessageId, @@ -424,4 +450,147 @@ export class CircleTransfer if (!vaa) throw new Error(`No VAA available after timeout exhausted`); return vaa; } + + static async getTransferMessage( + fromChain: ChainContext, + txid: TxHash, + ) { + const cb = await fromChain.getCircleBridge(); + const circleMessage = await cb.parseTransactionDetails(txid); + return circleMessage.id; + } + + static getReceipt( + xfer: CircleTransfer, + ): TransferReceipt { + const { from, to } = xfer.transfer; + + const att = xfer.attestations.filter((a) => + isWormholeMessageId(a.id), + ) as AttestationReceipt<"AutomaticCircleBridge">[]; + + const ctt = xfer.attestations.filter((a) => + isCircleMessageId(a.id), + ) as AttestationReceipt<"CircleBridge">[]; + + // This attestation may be either the auto relay vaa or the circle attestation + // depending on the request + const attestation = att.length > 0 ? att[0]! : ctt.length > 0 ? ctt[0]! : undefined; + + const receipt: TransferReceipt = { + protocol: xfer.transfer.automatic ? "AutomaticCircleBridge" : "CircleBridge", + from: from.chain, + to: to.chain, + state: TransferState.Created, + originTxs: xfer.txids.filter((txid) => txid.chain === xfer.transfer.from.chain), + destinationTxs: xfer.txids.filter((txid) => txid.chain === xfer.transfer.to.chain), + request: xfer.transfer, + attestation, + }; + + if (receipt.originTxs.length > 0) receipt.state = TransferState.SourceInitiated; + if (receipt.attestation && receipt.attestation.attestation) + receipt.state = TransferState.Attested; + if (receipt.destinationTxs.length > 0) receipt.state = TransferState.DestinationInitiated; + + return receipt; + } + + // AsyncGenerator fn that produces status updates through an async generator + // eventually producing a receipt + // can be called repeatedly so the receipt is updated as it moves through the + // steps of the transfer + static async *track( + wh: Wormhole, + receipt: TransferReceipt, + timeout: number = DEFAULT_TASK_TIMEOUT, + // Optional parameters to override chain context (typically for custom rpc) + _fromChain?: ChainContext, typeof receipt.from>, + _toChain?: ChainContext, typeof receipt.to>, + ) { + const start = Date.now(); + const leftover = (start: number, max: number) => Math.max(max - (Date.now() - start), 0); + + _fromChain = _fromChain ?? wh.getChain(receipt.from); + _toChain = _toChain ?? wh.getChain(receipt.to); + + // Check the source chain for initiation transaction + // and capture the message id + if (receipt.state === TransferState.SourceInitiated) { + if (receipt.originTxs.length === 0) + throw "Invalid state transition: no originating transactions"; + + if (!receipt.attestation || !receipt.attestation.id) { + const initTx = receipt.originTxs[receipt.originTxs.length - 1]!; + const xfermsg = await CircleTransfer.getTransferMessage(_fromChain, initTx.txid); + receipt.attestation = { id: xfermsg }; + receipt.state = TransferState.SourceFinalized; + yield receipt; + } + } + + if (receipt.state == TransferState.SourceFinalized) { + if (!receipt.attestation) throw "Invalid state transition: no attestation id"; + + if (receipt.protocol === "AutomaticCircleBridge") { + // we need to get the attestation so we can deliver it + // we can use the message id we parsed out of the logs, if we have them + // or try to fetch it from the last origin transaction + let vaa = receipt.attestation.attestation ? receipt.attestation.attestation : undefined; + if (!vaa) { + vaa = await CircleTransfer.getTransferVaa( + wh, + receipt.attestation.id as WormholeMessageId, + leftover(start, timeout), + ); + receipt.attestation.attestation = vaa; + receipt.state = TransferState.Attested; + yield receipt; + } + } + } + + if (receipt.state == TransferState.Attested) { + 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), + ); + if (!txStatus) { + yield receipt; + return; + } + + if (txStatus.globalTx?.destinationTx?.txHash) { + const { chainId, txHash } = txStatus.globalTx.destinationTx; + + receipt.destinationTxs = [ + { + chain: toChain(chainId), + txid: txHash, + }, + ]; + + receipt.state = TransferState.DestinationFinalized; + yield receipt; + } + + // Fall back to asking the destination chain if this VAA has been redeemed + // assuming we have the full attestation + if ( + receipt.attestation.attestation && + (await CircleTransfer.isTransferComplete(_toChain, receipt.attestation.attestation), + leftover(start, timeout)) + ) { + receipt.state = TransferState.DestinationFinalized; + yield receipt; + } + } + yield receipt; + return; + } } diff --git a/connect/src/protocols/tokenTransfer.ts b/connect/src/protocols/tokenTransfer.ts index 3c212b284..97fcf030f 100644 --- a/connect/src/protocols/tokenTransfer.ts +++ b/connect/src/protocols/tokenTransfer.ts @@ -9,6 +9,7 @@ import { } from "@wormhole-foundation/sdk-base"; import { AttestationId, + AttestationReceipt, AutomaticTokenBridge, ChainContext, Signer, @@ -57,10 +58,7 @@ export class TokenTransfer // The corresponding vaa representing the TokenTransfer // on the source chain (if its been completed and finalized) - vaas?: { - id: WormholeMessageId; - vaa?: TokenTransferVAA; - }[]; + attestations?: AttestationReceipt[]; private constructor(wh: Wormhole, transfer: TokenTransferDetails) { this._state = TransferState.Created; @@ -154,7 +152,7 @@ export class TokenTransfer // TODO: grab at least the init tx from the api const tt = new TokenTransfer(wh, details); - tt.vaas = [{ id: id, vaa }]; + tt.attestations = [{ id: id, attestation: vaa }]; tt._state = TransferState.Attested; return tt; } @@ -203,7 +201,7 @@ export class TokenTransfer if (this._state < TransferState.SourceInitiated || this._state > TransferState.Attested) throw new Error("Invalid state transition in `ready`"); - if (!this.vaas || this.vaas.length === 0) { + if (!this.attestations || this.attestations.length === 0) { if (this.txids.length === 0) throw new Error("No VAAs set and txids available to look them up"); @@ -214,22 +212,22 @@ export class TokenTransfer txid.txid, timeout, ); - this.vaas = [{ id: msgId }]; + this.attestations = [{ id: msgId }]; } - for (const idx in this.vaas) { + for (const idx in this.attestations) { // Check if we already have the VAA - if (this.vaas[idx]!.vaa) continue; + if (this.attestations[idx]!.attestation) continue; - this.vaas[idx]!.vaa = await TokenTransfer.getTransferVaa( + this.attestations[idx]!.attestation = await TokenTransfer.getTransferVaa( this.wh, - this.vaas[idx]!.id, + this.attestations[idx]!.id, timeout, ); } this._state = TransferState.Attested; - return this.vaas.map((vaa) => vaa.id); + return this.attestations.map((vaa) => vaa.id); } // finish the WormholeTransfer by submitting transactions to the destination chain @@ -244,122 +242,23 @@ export class TokenTransfer if (this._state < TransferState.Attested) throw new Error("Invalid state transition in `finish`. Be sure to call `fetchAttestation`."); - if (!this.vaas) throw new Error("No VAA details available"); + if (!this.attestations) throw new Error("No VAA details available"); // TODO: when do we get >1? - const { vaa } = this.vaas[0]!; - if (!vaa) throw new Error(`No VAA found for ${this.vaas[0]!.id.sequence}`); + const { attestation } = this.attestations[0]!; + if (!attestation) throw new Error(`No VAA found for ${this.attestations[0]!.id.sequence}`); const toChain = this.wh.getChain(this.transfer.to.chain); - const redeemTxids = await TokenTransfer.redeem(toChain, vaa, signer); + const redeemTxids = await TokenTransfer.redeem( + toChain, + attestation as TokenTransferVAA, + signer, + ); this.txids.push(...redeemTxids); return redeemTxids.map(({ txid }) => txid); } - // AsyncGenerator fn that produces status updates through an async generator - // eventually producing a receipt - // can be called repeatedly so the receipt is updated as it moves through the - // steps of the transfer - static async *track( - wh: Wormhole, - receipt: TransferReceipt, - timeout: number = DEFAULT_TASK_TIMEOUT, - // Optional parameters to override chain context (typically for custom rpc) - _fromChain?: ChainContext, typeof receipt.from>, - _toChain?: ChainContext, typeof receipt.to>, - ) { - const start = Date.now(); - const leftover = (start: number, max: number) => Math.max(max - (Date.now() - start), 0); - - _fromChain = _fromChain ?? wh.getChain(receipt.from); - _toChain = _toChain ?? wh.getChain(receipt.to); - - // Check the source chain for initiation transaction - // and capture the message id - if (receipt.state === TransferState.SourceInitiated) { - if (receipt.originTxs.length === 0) - throw "Invalid state transition: no originating transactions"; - - if (!receipt.attestation || !receipt.attestation.id.emitter) { - const initTx = receipt.originTxs[receipt.originTxs.length - 1]!; - const xfermsg = await TokenTransfer.getTransferMessage( - _fromChain, - initTx.txid, - leftover(start, timeout), - ); - receipt.attestation = { id: xfermsg }; - receipt.state = TransferState.SourceFinalized; - yield receipt; - } - } - - if (receipt.state == TransferState.SourceFinalized) { - if (!receipt.attestation) throw "Invalid state transition: no attestation id"; - - // we need to get the attestation so we can deliver it - // we can use the message id we parsed out of the logs, if we have them - // or try to fetch it from the last origin transaction - let vaa = receipt.attestation.attestation ? receipt.attestation.attestation : undefined; - if (!vaa) { - vaa = await TokenTransfer.getTransferVaa( - wh, - { ...receipt.attestation.id }, - leftover(start, timeout), - ); - receipt.attestation.attestation = vaa; - receipt.state = TransferState.Attested; - yield receipt; - } - } - - if (receipt.state == TransferState.Attested) { - 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!, - leftover(start, timeout), - ); - if (!txStatus) { - yield receipt; - return; - } - - if (txStatus.globalTx?.destinationTx?.txHash) { - const { chainId, txHash } = txStatus.globalTx.destinationTx; - - receipt.destinationTxs = [ - { - chain: toChain(chainId), - txid: txHash, - }, - ]; - - receipt.state = TransferState.DestinationFinalized; - yield receipt; - } - - // Fall back to asking the destination chain if this VAA has been redeemed - // assuming we have the full attestation - if ( - receipt.attestation.attestation && - (await TokenTransfer.isTransferComplete( - _toChain, - receipt.attestation.attestation as TokenTransferVAA, - ), - leftover(start, timeout)) - ) { - receipt.state = TransferState.DestinationFinalized; - yield receipt; - } - } - yield receipt; - return; - } - // Static method to perform the transfer so a custom RPC may be used // Note: this assumes the transfer has already been validated with `validateTransfer` static async transfer( @@ -622,21 +521,24 @@ export class TokenTransfer static getReceipt( xfer: TokenTransfer, - ): TransferReceipt< - "TokenBridge" | "AutomaticTokenBridge", - typeof xfer.transfer.from.chain, - typeof xfer.transfer.to.chain - > { - const att = xfer.vaas && xfer.vaas.length > 0 ? xfer.vaas![0]! : undefined; + ): TransferReceipt { + const { transfer } = xfer; - const attestation = att && att.id.emitter ? { id: att.id, attestation: att.vaa } : undefined; + 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; const receipt = { - from: xfer.transfer.from.chain, - to: xfer.transfer.to.chain, - state: TransferState.SourceInitiated, - originTxs: xfer.txids.filter((txid) => txid.chain === xfer.transfer.from.chain), - destinationTxs: xfer.txids.filter((txid) => txid.chain === xfer.transfer.to.chain), + protocol: (transfer.automatic + ? "AutomaticTokenBridge" + : "TokenBridge") as TokenTransferProtocol, + request: transfer, + from: transfer.from.chain, + to: transfer.to.chain, + state: TransferState.Created, + originTxs: xfer.txids.filter((txid) => txid.chain === transfer.from.chain), + destinationTxs: xfer.txids.filter((txid) => txid.chain === transfer.to.chain), attestation, }; @@ -647,4 +549,107 @@ export class TokenTransfer return receipt; } + + // AsyncGenerator fn that produces status updates through an async generator + // eventually producing a receipt + // can be called repeatedly so the receipt is updated as it moves through the + // steps of the transfer + static async *track( + wh: Wormhole, + receipt: TransferReceipt, + timeout: number = DEFAULT_TASK_TIMEOUT, + // Optional parameters to override chain context (typically for custom rpc) + _fromChain?: ChainContext, typeof receipt.from>, + _toChain?: ChainContext, typeof receipt.to>, + ) { + const start = Date.now(); + const leftover = (start: number, max: number) => Math.max(max - (Date.now() - start), 0); + + _fromChain = _fromChain ?? wh.getChain(receipt.from); + _toChain = _toChain ?? wh.getChain(receipt.to); + + // Check the source chain for initiation transaction + // and capture the message id + if (receipt.state === TransferState.SourceInitiated) { + if (receipt.originTxs.length === 0) + throw "Invalid state transition: no originating transactions"; + + if (!receipt.attestation || !receipt.attestation.id) { + const initTx = receipt.originTxs[receipt.originTxs.length - 1]!; + const xfermsg = await TokenTransfer.getTransferMessage( + _fromChain, + initTx.txid, + leftover(start, timeout), + ); + receipt.attestation = { id: xfermsg }; + receipt.state = TransferState.SourceFinalized; + yield receipt; + } + } + + if (receipt.state == TransferState.SourceFinalized) { + if (!receipt.attestation) throw "Invalid state transition: no attestation id"; + + // we need to get the attestation so we can deliver it + // we can use the message id we parsed out of the logs, if we have them + // or try to fetch it from the last origin transaction + let vaa = receipt.attestation.attestation ? receipt.attestation.attestation : undefined; + if (!vaa) { + vaa = await TokenTransfer.getTransferVaa( + wh, + { ...receipt.attestation.id }, + leftover(start, timeout), + ); + receipt.attestation.attestation = vaa; + receipt.state = TransferState.Attested; + yield receipt; + } + } + + if (receipt.state == TransferState.Attested) { + 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!, + leftover(start, timeout), + ); + if (!txStatus) { + yield receipt; + return; + } + + if (txStatus.globalTx?.destinationTx?.txHash) { + const { chainId, txHash } = txStatus.globalTx.destinationTx; + + receipt.destinationTxs = [ + { + chain: toChain(chainId), + txid: txHash, + }, + ]; + + receipt.state = TransferState.DestinationFinalized; + yield receipt; + } + + // Fall back to asking the destination chain if this VAA has been redeemed + // assuming we have the full attestation + if ( + receipt.attestation.attestation && + (await TokenTransfer.isTransferComplete( + _toChain, + receipt.attestation.attestation as TokenTransferVAA, + ), + leftover(start, timeout)) + ) { + receipt.state = TransferState.DestinationFinalized; + yield receipt; + } + } + yield receipt; + return; + } } diff --git a/connect/src/wormholeTransfer.ts b/connect/src/wormholeTransfer.ts index 06ad3b1d9..42048e8fc 100644 --- a/connect/src/wormholeTransfer.ts +++ b/connect/src/wormholeTransfer.ts @@ -6,8 +6,8 @@ import { ProtocolName, } from "@wormhole-foundation/sdk-base"; import { - Attestation, AttestationId, + AttestationReceipt, ChainContext, CircleTransferDetails, GatewayTransferDetails, @@ -46,15 +46,14 @@ export type TransferReceipt< SC extends Chain = Chain, DC extends Chain = Chain, > = { + readonly protocol: PN; + readonly request: TransferRequest; + readonly from: SC; + readonly to: DC; state: TransferState; - from: SC; - to: DC; originTxs: TransactionId[]; destinationTxs: TransactionId[]; - attestation?: { - id: AttestationId; - attestation?: Attestation; - }; + attestation?: AttestationReceipt; }; // Quote with optional relayer fees if the transfer diff --git a/core/definitions/src/attestation.ts b/core/definitions/src/attestation.ts index 604528356..37e704e24 100644 --- a/core/definitions/src/attestation.ts +++ b/core/definitions/src/attestation.ts @@ -3,7 +3,7 @@ import { SequenceId } from "./types"; import { UniversalAddress } from "./universalAddress"; import { VAA } from "./vaa"; import { AutomaticTokenBridge, TokenBridge } from "./protocols/tokenBridge"; -import { AutomaticCircleBridge } from "./protocols/circleBridge"; +import { AutomaticCircleBridge, CircleBridge } from "./protocols/circleBridge"; import { IbcTransferData } from "./protocols/ibc"; // Could be VAA or Circle or ..? @@ -25,11 +25,18 @@ export type Attestation = PN extends : PN extends "AutomaticCircleBridge" ? AutomaticCircleBridge.VAA : PN extends "CircleBridge" - ? CircleAttestation + ? CircleBridge.Attestation : PN extends "IbcBridge" ? IbcTransferData : never; +// Attestation Receipt contains the Id to lookup the attestation +// and possibly a cached/parsed attestation +export type AttestationReceipt = { + id: AttestationId; + attestation?: Attestation; +}; + // Wormhole Message Identifier used to fetch a VAA // Possibly with a VAA already set export type WormholeMessageId = { diff --git a/core/definitions/src/protocols/circleBridge.ts b/core/definitions/src/protocols/circleBridge.ts index 0a7af31a0..3ca99f228 100644 --- a/core/definitions/src/protocols/circleBridge.ts +++ b/core/definitions/src/protocols/circleBridge.ts @@ -28,6 +28,11 @@ export namespace CircleBridge { export type Message = LayoutToType; + export type Attestation = { + message: Message; + attestation?: string; + }; + export const deserialize = (data: Uint8Array): [CircleBridge.Message, string] => { const msg = deserializeLayout(circleMessageLayout, data); const messsageHash = encoding.hex.encode(keccak256(data), true);