Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

portico: fetch swap event #724

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 12 additions & 4 deletions connect/src/routes/portico/automatic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import type {
SourceInitiatedTransferReceipt,
TokenId,
TransactionId,
TransferWarning,
} from "./../../index.js";
import {
PorticoBridge,
Expand Down Expand Up @@ -328,21 +329,28 @@ export class AutomaticPorticoRoute<N extends Network>
if (isAttested(receipt)) {
const toChain = this.wh.getChain(receipt.to);
const toPorticoBridge = await toChain.getPorticoBridge();
const isCompleted = await toPorticoBridge.isTransferCompleted(

const { swapResult, receivedToken } = await toPorticoBridge.getTransferResult(
receipt.attestation.attestation,
);
if (isCompleted) {

if (swapResult === "success" || swapResult === "failed") {
const warnings =
swapResult === "failed"
? [{ type: "SwapFailedWarning" } satisfies TransferWarning]
: undefined;
const transferResult = receivedToken ? { receivedToken, warnings } : undefined;

receipt = {
...receipt,
state: TransferState.DestinationFinalized,
transferResult,
} satisfies CompletedTransferReceipt<AttestationReceipt<"PorticoBridge">>;

yield receipt;
}
}

// TODO: handle swap failed case (highway token received)

yield receipt;
}

Expand Down
16 changes: 14 additions & 2 deletions connect/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import type {
TokenId,
TransactionId,
} from "@wormhole-foundation/sdk-definitions";
import type { QuoteWarning } from "./warnings.js";
import type { TransferWarning, QuoteWarning } from "./warnings.js";

// Transfer state machine states
export enum TransferState {
Expand Down Expand Up @@ -105,6 +105,7 @@ export interface CompletedTransferReceipt<AT, SC extends Chain = Chain, DC exten
originTxs: TransactionId<SC>[];
attestation: AT;
destinationTxs?: TransactionId<DC>[];
transferResult?: TransferResult;
}

export interface FailedTransferReceipt<AT, SC extends Chain = Chain, DC extends Chain = Chain>
Expand Down Expand Up @@ -202,7 +203,7 @@ export interface TransferQuote {
token: TokenId;
amount: bigint;
};
// If the transfer being quoted asked for native gas dropoff
// If the transfer being quoted asked for native gas drop-off
// this will contain the amount of native gas that is to be minted
// on the destination chain given the current swap rates
destinationNativeGas?: bigint;
Expand All @@ -212,3 +213,14 @@ export interface TransferQuote {
// Estimated time to completion in milliseconds
eta?: number;
}

// Information about the result of a transfer
export interface TransferResult {
// How much of what token was received
receivedToken: {
token: TokenId;
amount: bigint;
}
// Any warnings that occurred (e.g. swap failed)
warnings?: TransferWarning[];
}
6 changes: 6 additions & 0 deletions connect/src/warnings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,9 @@ export type GovernorLimitWarning = {
};

export type QuoteWarning = DestinationCapacityWarning | GovernorLimitWarning;

export type SwapFailedWarning = {
type: "SwapFailedWarning";
};

export type TransferWarning = SwapFailedWarning;
11 changes: 11 additions & 0 deletions core/definitions/src/protocols/portico/portico.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,14 @@ export namespace PorticoBridge {
relayerFee: bigint;
};

export type TransferResult = {
swapResult: "success" | "failed" | "pending";
receivedToken?: {
token: TokenId;
amount: bigint;
};
};

const _transferPayloads = ["Transfer"] as const;
const _payloads = [..._transferPayloads] as const;

Expand Down Expand Up @@ -113,4 +121,7 @@ export interface PorticoBridge<N extends Network = Network, C extends Chain = Ch

/** Get the Portico contract address for the token group */
getPorticoAddress(group: string): string;

/** Get the transfer result on the this chain */
getTransferResult(vaa: PorticoBridge.VAA): Promise<PorticoBridge.TransferResult>;
}
1 change: 1 addition & 0 deletions platforms/evm/protocols/portico/src/abis.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { ethers } from 'ethers';
export const porticoAbi = new ethers.Interface([
'function start((bytes32,address,address,address,address,address,uint256,uint256,uint256,uint256)) returns (address,uint16,uint64)',
'function receiveMessageAndSwap(bytes)',
'event PorticoSwapFinish(bool swapCompleted, uint256 finaluserAmount, uint256 relayerFeeAmount, tuple(bytes32 flags, address finalTokenAddress, address recipientAddress, uint256 canonAssetAmount, uint256 minAmountFinish, uint256 relayerFee) data)',
]);

export const porticoSwapFinishedEvent =
Expand Down
84 changes: 84 additions & 0 deletions platforms/evm/protocols/portico/src/bridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import { EvmWormholeCore } from '@wormhole-foundation/sdk-evm-core';
import { EvmTokenBridge } from '@wormhole-foundation/sdk-evm-tokenbridge';

import '@wormhole-foundation/sdk-evm-tokenbridge';
import { finality } from '@wormhole-foundation/sdk-connect';

export class EvmPorticoBridge<
N extends Network,
Expand Down Expand Up @@ -349,4 +350,87 @@ export class EvmPorticoBridge<
}
return portico.uniswapQuoterV2;
}

async getTransferResult(
vaa: PorticoBridge.VAA,
): Promise<PorticoBridge.TransferResult> {
// First check if the transfer is completed
const isCompleted = await this.isTransferCompleted(vaa);
if (!isCompleted) return { swapResult: 'pending' };

const finalToken = Wormhole.tokenId(
this.chain,
vaa.payload.payload.flagSet.flags.shouldUnwrapNative
? 'native'
: vaa.payload.payload.finalTokenAddress.toNative(this.chain).toString(),
);

const finalAmount = (() => {
const amountLessFee =
vaa.payload.payload.minAmountFinish - vaa.payload.payload.relayerFee;
return amountLessFee < 0n ? 0n : amountLessFee;
})();

const defaultResult: PorticoBridge.TransferResult = {
swapResult: 'success',
receivedToken: {
token: finalToken,
amount: finalAmount,
},
};

// This is a simplification since there is no swap on Ethereum
// since the highway token originates there
if (this.chain === 'Ethereum') return defaultResult;

// Check if the swap succeeded or failed
// NOTE: If we can't find the event, assume the swap succeeded
const tokenBridge = this.tokenBridge.tokenBridge;
const filter = tokenBridge.filters.TransferRedeemed(
toChainId(vaa.emitterChain),
vaa.emitterAddress.toString(),
vaa.sequence,
);

// Search for the event in the last 15 minutes
const latestBlock = await EvmPlatform.getLatestBlock(this.provider);
Copy link
Contributor Author

@kev1n-peters kev1n-peters Oct 24, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have to constrain the block range we're searching in otherwise the RPC may return an error

const blockTime = finality.blockTime.get(this.chain)!;
const fromBlock = latestBlock - Math.floor((15 * 60 * 1000) / blockTime);

const logs = await tokenBridge
.queryFilter(filter, fromBlock, latestBlock)
.catch(() => null);
if (!logs || logs.length === 0) return defaultResult;

const txhash = logs[0]!.transactionHash;
const receipt = await this.provider.getTransactionReceipt(txhash);
if (!receipt) return defaultResult;

const [event] = receipt.logs
.map((log) => porticoAbi.parseLog(log))
.filter((log) => log);
if (!event) return defaultResult;

const swapCompleted = event.args.swapCompleted;
const finaluserAmount = event.args.finaluserAmount;

// If the swap failed, the highway / Wormhole-wrapped token is received instead
// of the finalToken
const token = swapCompleted
? finalToken
: Wormhole.tokenId(
this.chain,
(
await this.tokenBridge.getWrappedAsset(vaa.payload.token)
).toString(),
);

return {
swapResult: swapCompleted ? 'success' : 'failed',
receivedToken: {
token,
amount: finaluserAmount,
},
};
}
}
Loading