From 602cee85c7d705e94cdce6584229d77e5d2ea05d Mon Sep 17 00:00:00 2001 From: fernandomg Date: Tue, 21 Jan 2025 20:09:34 +0100 Subject: [PATCH 1/8] feat: support intents --- src/consts/warpRoutes.ts | 172 +++++++++++++++++++- src/features/tokens/TokenListModal.tsx | 5 +- src/features/transfer/TransferTokenForm.tsx | 16 +- src/features/warpCore/warpCoreConfig.ts | 2 +- src/utils/intents.ts | 5 + 5 files changed, 192 insertions(+), 8 deletions(-) create mode 100644 src/utils/intents.ts diff --git a/src/consts/warpRoutes.ts b/src/consts/warpRoutes.ts index 8e32c5c9..3af94c1f 100644 --- a/src/consts/warpRoutes.ts +++ b/src/consts/warpRoutes.ts @@ -1,9 +1,177 @@ -import { WarpCoreConfig } from '@hyperlane-xyz/sdk'; +import { TokenStandard, WarpCoreConfig } from '@hyperlane-xyz/sdk'; + +const ROUTER = '0x6d2175B89315A9EB6c7eA71fDE54Ac0f294aDC34'; +const ITT = '0x5f94BC7Fb4A2779fef010F96b496cD36A909E818'; // A list of Warp Route token configs // These configs will be merged with the warp routes in the configured registry // The input here is typically the output of the Hyperlane CLI warp deploy command export const warpRouteConfigs: WarpCoreConfig = { - tokens: [], + tokens: [ + { + addressOrDenom: ROUTER, + chainName: 'optimismsepolia', + collateralAddressOrDenom: ITT, + connections: [ + { + token: 'ethereum|basesepolia|' + ITT, + }, + { + token: 'ethereum|arbitrumsepolia|' + ITT, + }, + { + token: 'ethereum|sepolia|' + ITT, + }, + ], + decimals: 18, + logoURI: '/deployments/warp_routes/ETH/logo.svg', + name: 'ITT', + standard: TokenStandard.EvmIntent, + symbol: 'ITT', + }, + { + addressOrDenom: ROUTER, + chainName: 'basesepolia', + collateralAddressOrDenom: ITT, + connections: [ + { + token: 'ethereum|optimismsepolia|' + ITT, + }, + { + token: 'ethereum|arbitrumsepolia|' + ITT, + }, + { + token: 'ethereum|sepolia|' + ITT, + }, + ], + decimals: 18, + logoURI: '/deployments/warp_routes/ETH/logo.svg', + name: 'ITT', + standard: TokenStandard.EvmIntent, + symbol: 'ITT', + }, + { + addressOrDenom: ROUTER, + chainName: 'arbitrumsepolia', + collateralAddressOrDenom: ITT, + connections: [ + { + token: 'ethereum|optimismsepolia|' + ITT, + }, + { + token: 'ethereum|basesepolia|' + ITT, + }, + { + token: 'ethereum|sepolia|' + ITT, + }, + ], + decimals: 18, + logoURI: '/deployments/warp_routes/ETH/logo.svg', + name: 'ITT', + standard: TokenStandard.EvmIntent, + symbol: 'ITT', + }, + { + addressOrDenom: ROUTER, + chainName: 'sepolia', + collateralAddressOrDenom: ITT, + connections: [ + { + token: 'ethereum|optimismsepolia|' + ITT, + }, + { + token: 'ethereum|arbitrumsepolia|' + ITT, + }, + { + token: 'ethereum|basesepolia|' + ITT, + }, + ], + decimals: 18, + logoURI: '/deployments/warp_routes/ETH/logo.svg', + name: 'ITT', + standard: TokenStandard.EvmIntent, + symbol: 'ITT', + }, + { + addressOrDenom: ROUTER, + chainName: 'optimismsepolia', + connections: [ + { + token: 'ethereum|basesepolia|', + }, + { + token: 'ethereum|arbitrumsepolia|', + }, + { + token: 'ethereum|sepolia|', + }, + ], + decimals: 18, + logoURI: '/deployments/warp_routes/ETH/logo.svg', + name: 'ETH', + standard: TokenStandard.EvmIntentNative, + symbol: 'ETH', + }, + { + addressOrDenom: ROUTER, + chainName: 'basesepolia', + connections: [ + { + token: 'ethereum|optimismsepolia|', + }, + { + token: 'ethereum|arbitrumsepolia|', + }, + { + token: 'ethereum|sepolia|', + }, + ], + decimals: 18, + logoURI: '/deployments/warp_routes/ETH/logo.svg', + name: 'ETH', + standard: TokenStandard.EvmIntentNative, + symbol: 'ETH', + }, + { + addressOrDenom: ROUTER, + chainName: 'arbitrumsepolia', + connections: [ + { + token: 'ethereum|optimismsepolia|', + }, + { + token: 'ethereum|basesepolia|', + }, + { + token: 'ethereum|sepolia|', + }, + ], + decimals: 18, + logoURI: '/deployments/warp_routes/ETH/logo.svg', + name: 'ETH', + standard: TokenStandard.EvmIntentNative, + symbol: 'ETH', + }, + { + addressOrDenom: ROUTER, + chainName: 'sepolia', + connections: [ + { + token: 'ethereum|optimismsepolia|', + }, + { + token: 'ethereum|arbitrumsepolia|', + }, + { + token: 'ethereum|basesepolia|', + }, + ], + decimals: 18, + logoURI: '/deployments/warp_routes/ETH/logo.svg', + name: 'ETH', + standard: TokenStandard.EvmIntentNative, + symbol: 'ETH', + }, + ], options: {}, }; diff --git a/src/features/tokens/TokenListModal.tsx b/src/features/tokens/TokenListModal.tsx index dab6a41f..ee1da2d2 100644 --- a/src/features/tokens/TokenListModal.tsx +++ b/src/features/tokens/TokenListModal.tsx @@ -6,6 +6,7 @@ import { TokenIcon } from '../../components/icons/TokenIcon'; import { TextInput } from '../../components/input/TextField'; import { config } from '../../consts/config'; import InfoIcon from '../../images/icons/info-circle.svg'; +import { isIntentStandard } from '../../utils/intents'; import { useMultiProvider } from '../chains/hooks'; import { getChainDisplayName } from '../chains/utils'; import { useWarpCore } from './hooks'; @@ -143,7 +144,9 @@ export function TokenList({
- {t.token.collateralAddressOrDenom || t.token.addressOrDenom || 'Native chain token'} + {(isIntentStandard(t.token.standard) + ? t.token.collateralAddressOrDenom + : t.token.addressOrDenom) ?? 'Native chain token'}
{`Decimals: ${t.token.decimals}`} diff --git a/src/features/transfer/TransferTokenForm.tsx b/src/features/transfer/TransferTokenForm.tsx index f2c6e708..64a14df6 100644 --- a/src/features/transfer/TransferTokenForm.tsx +++ b/src/features/transfer/TransferTokenForm.tsx @@ -21,6 +21,7 @@ import { TextField } from '../../components/input/TextField'; import { WARP_QUERY_PARAMS } from '../../consts/args'; import { config } from '../../consts/config'; import { Color } from '../../styles/Color'; +import { isIntentStandard } from '../../utils/intents'; import { logger } from '../../utils/logger'; import { getQueryParams, updateQueryParam } from '../../utils/queryParams'; import { ChainConnectionWarning } from '../chains/ChainConnectionWarning'; @@ -469,12 +470,18 @@ function ReviewDetails({ visible }: { visible: boolean }) {
)}
-

{`Transaction${isApproveRequired ? ' 2' : ''}: Transfer Remote`}

+

{`Transaction${isApproveRequired ? ' 2' : ''}: + ${destinationToken && isIntentStandard(destinationToken.standard) ? 'Open Order' : 'Transfer Remote'} + `}

{destinationToken?.addressOrDenom && (

Remote Token - {destinationToken.addressOrDenom} + + {isIntentStandard(destinationToken.standard) + ? (destinationToken.collateralAddressOrDenom ?? 'Native chain token') + : destinationToken.addressOrDenom} +

)}

@@ -484,7 +491,7 @@ function ReviewDetails({ visible }: { visible: boolean }) { {fees?.localQuote && fees.localQuote.amount > 0n && (

Local Gas (est.) - {`${fees.localQuote.getDecimalFormattedAmount().toFixed(4) || '0'} ${ + {`${fees.localQuote.getDecimalFormattedAmount().toFixed(18) || '0'} ${ fees.localQuote.token.symbol || '' }`}

@@ -492,7 +499,7 @@ function ReviewDetails({ visible }: { visible: boolean }) { {fees?.interchainQuote && fees.interchainQuote.amount > 0n && (

Interchain Gas - {`${fees.interchainQuote.getDecimalFormattedAmount().toFixed(4) || '0'} ${ + {`${fees.interchainQuote.getDecimalFormattedAmount().toFixed(18) || '0'} ${ fees.interchainQuote.token.symbol || '' }`}

@@ -577,6 +584,7 @@ async function validateForm( origin, accounts, ); + const result = await warpCore.validateTransfer({ originTokenAmount: token.amount(amountWei), destination, diff --git a/src/features/warpCore/warpCoreConfig.ts b/src/features/warpCore/warpCoreConfig.ts index d77f6b8c..3985a2a7 100644 --- a/src/features/warpCore/warpCoreConfig.ts +++ b/src/features/warpCore/warpCoreConfig.ts @@ -57,7 +57,7 @@ function filterToIds( function dedupeTokens(tokens: WarpCoreConfig['tokens']): WarpCoreConfig['tokens'] { const idToToken: Record = {}; for (const token of tokens) { - const id = `${token.chainName}|${token.addressOrDenom?.toLowerCase()}`; + const id = `${token.chainName}|${token.addressOrDenom?.toLowerCase()}|${token.collateralAddressOrDenom?.toLocaleLowerCase()}`; idToToken[id] = objMerge(idToToken[id] || {}, token); } return Object.values(idToToken); diff --git a/src/utils/intents.ts b/src/utils/intents.ts new file mode 100644 index 00000000..9b4bafbe --- /dev/null +++ b/src/utils/intents.ts @@ -0,0 +1,5 @@ +import { TokenStandard } from '@hyperlane-xyz/sdk'; + +export function isIntentStandard(standard: TokenStandard): boolean { + return standard === TokenStandard.EvmIntent || standard === TokenStandard.EvmIntentNative; +} From 7decb6be79761208230552c904221dcc974d4258 Mon Sep 17 00:00:00 2001 From: fernandomg Date: Fri, 28 Feb 2025 12:53:12 +0100 Subject: [PATCH 2/8] feat: identify tokens also by standard --- src/features/warpCore/warpCoreConfig.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/features/warpCore/warpCoreConfig.ts b/src/features/warpCore/warpCoreConfig.ts index 3985a2a7..c08e836d 100644 --- a/src/features/warpCore/warpCoreConfig.ts +++ b/src/features/warpCore/warpCoreConfig.ts @@ -57,7 +57,7 @@ function filterToIds( function dedupeTokens(tokens: WarpCoreConfig['tokens']): WarpCoreConfig['tokens'] { const idToToken: Record = {}; for (const token of tokens) { - const id = `${token.chainName}|${token.addressOrDenom?.toLowerCase()}|${token.collateralAddressOrDenom?.toLocaleLowerCase()}`; + const id = `${token.chainName}|${token.addressOrDenom?.toLowerCase()}|${token.standard.toLowerCase()}`; idToToken[id] = objMerge(idToToken[id] || {}, token); } return Object.values(idToToken); From f0c6a18eeda6057dd341c0ec30c1e4b40e9bd245 Mon Sep 17 00:00:00 2001 From: fernandomg Date: Fri, 28 Feb 2025 12:53:48 +0100 Subject: [PATCH 3/8] feat: display if a token will be bridged via intents --- src/features/tokens/TokenListModal.tsx | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/features/tokens/TokenListModal.tsx b/src/features/tokens/TokenListModal.tsx index ee1da2d2..a7938932 100644 --- a/src/features/tokens/TokenListModal.tsx +++ b/src/features/tokens/TokenListModal.tsx @@ -152,6 +152,14 @@ export function TokenList({ {`Decimals: ${t.token.decimals}`} - {`Chain: ${getChainDisplayName(multiProvider, t.token.chainName)}`} + {isIntentStandard(t.token.standard) && ( + <> + - + + via intents + + + )}
{t.disabled && ( From bb270a23d42fc2c72d9f3da98c33a9063dfceced Mon Sep 17 00:00:00 2001 From: fernandomg Date: Fri, 28 Feb 2025 20:56:17 +0100 Subject: [PATCH 4/8] feat: recognize intents in the UI --- package.json | 1 + src/features/store.ts | 4 +- src/features/tokens/balances.ts | 45 ++++++++++- .../transfer/TransfersDetailsModal.tsx | 28 +++++++ src/features/transfer/types.ts | 3 + src/features/transfer/useTokenTransfer.ts | 27 ++++++- src/features/transfer/utils.ts | 2 + yarn.lock | 76 ++++++++++++++++++- 8 files changed, 182 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 93169885..da00c328 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,7 @@ "version": "8.5.0", "author": "J M Rossy", "dependencies": { + "@bootnodedev/intents-framework-core": "^0.1.0", "@chakra-ui/next-js": "^2.4.2", "@chakra-ui/react": "^2.8.2", "@cosmjs/cosmwasm-stargate": "^0.32.4", diff --git a/src/features/store.ts b/src/features/store.ts index 5173c7e8..9a8a0282 100644 --- a/src/features/store.ts +++ b/src/features/store.ts @@ -47,7 +47,7 @@ export interface AppState { updateTransferStatus: ( i: number, s: TransferStatus, - options?: { msgId?: string; originTxHash?: string }, + options?: { msgId?: string; originTxHash?: string; orderId?: string; remoteTxHash?: string }, ) => void; failUnconfirmedTransfers: () => void; @@ -114,6 +114,8 @@ export const useStore = create()( txs[i].status = s; txs[i].msgId ||= options?.msgId; txs[i].originTxHash ||= options?.originTxHash; + txs[i].orderId ||= options?.orderId; + txs[i].remoteTxHash ||= options?.remoteTxHash; return { transfers: txs, }; diff --git a/src/features/tokens/balances.ts b/src/features/tokens/balances.ts index 99a3ecd1..f4533f08 100644 --- a/src/features/tokens/balances.ts +++ b/src/features/tokens/balances.ts @@ -1,5 +1,7 @@ +import { Hyperlane7683__factory as Hyperlane7683Factory } from '@bootnodedev/intents-framework-core'; +import { FilledEvent } from '@bootnodedev/intents-framework-core/dist/src/Base7683'; import { IToken, MultiProtocolProvider, Token } from '@hyperlane-xyz/sdk'; -import { isValidAddress } from '@hyperlane-xyz/utils'; +import { assert, isValidAddress } from '@hyperlane-xyz/utils'; import { useAccountAddressForChain } from '@hyperlane-xyz/widgets'; import { useQuery } from '@tanstack/react-query'; import { toast } from 'react-toastify'; @@ -86,3 +88,44 @@ export function useEvmWalletBalance( return { balance: data, isError, isLoading }; } +export async function checkOrderFilled({ + destination, + orderId, + originToken, + multiProvider, +}: { + destination: ChainName; + orderId: string; + originToken: Token; + multiProvider: MultiProtocolProvider; +}): Promise { + const provider = multiProvider.toMultiProvider().getProvider(destination); + const connection = originToken.getConnectionForChain(destination); + + assert(connection?.token.addressOrDenom, 'No connection found for destination chain'); + + const contract = Hyperlane7683Factory.connect(connection.token.addressOrDenom, provider); + const filter = contract.filters.Filled(); + + return new Promise((resolve, reject) => { + const intervalId = setInterval(async () => { + try { + const to = await provider.getBlockNumber(); + const from = to - 10; + const events = await contract.queryFilter(filter, from, to); + + for (const event of events as Array) { + const resolvedOrder = event.args.orderId; + + if (resolvedOrder === orderId) { + clearInterval(intervalId); + resolve(event.transactionHash); + } + } + } catch (error) { + clearInterval(intervalId); + reject(error); + } + }, 4_000); + }); +} diff --git a/src/features/transfer/TransfersDetailsModal.tsx b/src/features/transfer/TransfersDetailsModal.tsx index aa859430..4685773c 100644 --- a/src/features/transfer/TransfersDetailsModal.tsx +++ b/src/features/transfer/TransfersDetailsModal.tsx @@ -22,6 +22,7 @@ import { getHypExplorerLink } from '../../utils/links'; import { logger } from '../../utils/logger'; import { useMultiProvider } from '../chains/hooks'; import { getChainDisplayName, hasPermissionlessChain } from '../chains/utils'; +import { useStore } from '../store'; import { tryFindToken, useWarpCore } from '../tokens/hooks'; import { TransferContext, TransferStatus } from './types'; import { @@ -55,6 +56,8 @@ export function TransfersDetailsModal({ originTxHash, msgId, timestamp, + orderId, + remoteTxHash, } = transfer || {}; const multiProvider = useMultiProvider(); @@ -173,6 +176,9 @@ export function TransfersDetailsModal({ url={originTxUrl} /> )} + {orderId && remoteTxHash && ( + + )} {msgId && } {explorerLink && (
@@ -257,6 +263,28 @@ function TransferProperty({ name, value, url }: { name: string; value: string; u ); } +function RemoteTransferProperty({ name, orderId }: { name: string; orderId: string }) { + const multiProvider = useMultiProvider(); + const { transfers } = useStore((s) => ({ + transfers: s.transfers, + })); + const transfer = transfers.find((t) => t.orderId === orderId)!; + + return ( + transfer.remoteTxHash && ( + + ) + ); +} + function WideChevron() { return ( + log.topics[0].toLowerCase() === + '0x3448bbc2203c608599ad448eeb1007cea04b788ac631f9f558e8dd01a3c27b3d', // `Open` event + )?.topics[1]; + + updateTransferStatus(transferIndex, (transferStatus = TransferStatus.WaitingForFulfillment), { originTxHash: hashes.at(-1), msgId, + orderId, + }); + + const remoteTxHash = + orderId && + (await checkOrderFilled({ + destination, + orderId, + originToken, + multiProvider, + }).catch((error) => { + logger.error('Error checking order filled', error); + return undefined; + })); + + updateTransferStatus(transferIndex, (transferStatus = TransferStatus.ConfirmedTransfer), { + remoteTxHash, }); } catch (error: any) { logger.error(`Error at stage ${transferStatus}`, error); diff --git a/src/features/transfer/utils.ts b/src/features/transfer/utils.ts index 8480da10..2034f00b 100644 --- a/src/features/transfer/utils.ts +++ b/src/features/transfer/utils.ts @@ -29,6 +29,8 @@ export function getTransferStatusLabel( else statusDescription = 'Transfer confirmed, the funds will arrive when the message is delivered.'; + else if (status === TransferStatus.WaitingForFulfillment) + statusDescription = 'Transfer confirmed, waiting for solver to deliver...'; else if (status === TransferStatus.Delivered) statusDescription = 'Delivery complete, transfer successful!'; else if (status === TransferStatus.Failed) diff --git a/yarn.lock b/yarn.lock index d1d8d858..d5517b1b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1016,6 +1016,18 @@ __metadata: languageName: node linkType: hard +"@bootnodedev/intents-framework-core@npm:^0.1.0": + version: 0.1.0 + resolution: "@bootnodedev/intents-framework-core@npm:0.1.0" + dependencies: + "@hyperlane-xyz/core": "npm:^5.4.1" + "@openzeppelin/contracts": "npm:^4.9.6" + "@uniswap/permit2": "github:Uniswap/permit2" + dotenv-run-script: "npm:^0.4.1" + checksum: 10/f8df5a44e2ba7008e42d5e878ab6b2c57ebad0aaed1e8b134bc4a84d5539d5c62b04f299011435e9e4b2383c218e0d9897aef955c0b03b257ddbf7288242eb46 + languageName: node + linkType: hard + "@bytecodealliance/preview2-shim@npm:0.17.0": version: 0.17.0 resolution: "@bytecodealliance/preview2-shim@npm:0.17.0" @@ -4568,6 +4580,26 @@ __metadata: languageName: node linkType: hard +"@hyperlane-xyz/core@npm:^5.4.1": + version: 5.12.0 + resolution: "@hyperlane-xyz/core@npm:5.12.0" + dependencies: + "@arbitrum/nitro-contracts": "npm:^1.2.1" + "@chainlink/contracts-ccip": "npm:^1.5.0" + "@eth-optimism/contracts": "npm:^0.6.0" + "@hyperlane-xyz/utils": "npm:8.9.0" + "@layerzerolabs/lz-evm-oapp-v2": "npm:2.0.2" + "@openzeppelin/contracts": "npm:^4.9.3" + "@openzeppelin/contracts-upgradeable": "npm:^4.9.3" + fx-portal: "npm:^1.0.3" + peerDependencies: + "@ethersproject/abi": "*" + "@ethersproject/providers": "*" + "@types/sinon-chai": "*" + checksum: 10/9f28ca2d4f3f4baf1d3daed961b8a07d22a3c2c6ab3377620312c5fa3a3084cb17fb4c95e7e3fcf9f273f22d47e361deba3c1f3ea00d46142485d6f432be5211 + languageName: node + linkType: hard + "@hyperlane-xyz/registry@npm:11.3.0": version: 11.3.0 resolution: "@hyperlane-xyz/registry@npm:11.3.0" @@ -4609,6 +4641,21 @@ __metadata: languageName: node linkType: hard +"@hyperlane-xyz/utils@npm:8.9.0": + version: 8.9.0 + resolution: "@hyperlane-xyz/utils@npm:8.9.0" + dependencies: + "@cosmjs/encoding": "npm:^0.32.4" + "@solana/web3.js": "npm:^1.95.4" + bignumber.js: "npm:^9.1.1" + ethers: "npm:^5.7.2" + lodash-es: "npm:^4.17.21" + pino: "npm:^8.19.0" + yaml: "npm:2.4.5" + checksum: 10/49b06a286b1ed9be1198c05c5de45591b55f69f41864397e961686393a428d31a701208b20c62c5897e39dd667cd1bec8b927982ec371d830160522ff96a2892 + languageName: node + linkType: hard + "@hyperlane-xyz/utils@npm:9.2.1": version: 9.2.1 resolution: "@hyperlane-xyz/utils@npm:9.2.1" @@ -4628,6 +4675,7 @@ __metadata: version: 0.0.0-use.local resolution: "@hyperlane-xyz/warp-ui-template@workspace:." dependencies: + "@bootnodedev/intents-framework-core": "npm:^0.1.0" "@chakra-ui/next-js": "npm:^2.4.2" "@chakra-ui/react": "npm:^2.8.2" "@cosmjs/cosmwasm-stargate": "npm:^0.32.4" @@ -7248,7 +7296,7 @@ __metadata: languageName: node linkType: hard -"@openzeppelin/contracts@npm:^4.2.0": +"@openzeppelin/contracts@npm:^4.2.0, @openzeppelin/contracts@npm:^4.9.6": version: 4.9.6 resolution: "@openzeppelin/contracts@npm:4.9.6" checksum: 10/71f45ad42e68c0559be4ba502115462a01c76fc805c08d3005c10b5550a093f1a2b00b2d7e9d6d1f331e147c50fd4ad832f71c4470ec5b34f5a2d0751cd19a47 @@ -12532,6 +12580,13 @@ __metadata: languageName: node linkType: hard +"@uniswap/permit2@github:Uniswap/permit2": + version: 1.0.0 + resolution: "@uniswap/permit2@https://github.com/Uniswap/permit2.git#commit=cc56ad0f3439c502c246fc5cfcc3db92bb8b7219" + checksum: 10/746891ef1b7e494eada03caf4818223d21c15558c1932111dbdf6584bc632188e9e333685b09ec3f29e9238c11e4246adabefa222a0ae8cc4e11e9a1fc093c38 + languageName: node + linkType: hard + "@vanilla-extract/css@npm:1.15.5": version: 1.15.5 resolution: "@vanilla-extract/css@npm:1.15.5" @@ -16172,6 +16227,25 @@ __metadata: languageName: node linkType: hard +"dotenv-expand@npm:^10.0.0": + version: 10.0.0 + resolution: "dotenv-expand@npm:10.0.0" + checksum: 10/b41eb278bc96b92cbf3037ca5f3d21e8845bf165dc06b6f9a0a03d278c2bd5a01c0cfbb3528ae3a60301ba1a8a9cace30e748c54b460753bc00d4c014b675597 + languageName: node + linkType: hard + +"dotenv-run-script@npm:^0.4.1": + version: 0.4.1 + resolution: "dotenv-run-script@npm:0.4.1" + dependencies: + dotenv: "npm:^16.3.1" + dotenv-expand: "npm:^10.0.0" + bin: + dotenv-run-script: bin/dotenv-run-script + checksum: 10/dc0ec2b3bfbc5d01c32c937c24e6a6e4e910b6c95119dfd57d206a8c77f389a5f0512d68d4ec6e716e5afd3f1f799cc7d2e4a944b0334df781510f8aed0e5bd0 + languageName: node + linkType: hard + "dotenv@npm:^16.3.1": version: 16.4.5 resolution: "dotenv@npm:16.4.5" From 5dd88561701ea7c00ff59ffebb552abc7ee929e2 Mon Sep 17 00:00:00 2001 From: fernandomg Date: Mon, 31 Mar 2025 14:59:26 +0200 Subject: [PATCH 5/8] fix: remove unneeded type assertion --- src/features/tokens/balances.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/features/tokens/balances.ts b/src/features/tokens/balances.ts index f4533f08..831325b5 100644 --- a/src/features/tokens/balances.ts +++ b/src/features/tokens/balances.ts @@ -1,5 +1,4 @@ import { Hyperlane7683__factory as Hyperlane7683Factory } from '@bootnodedev/intents-framework-core'; -import { FilledEvent } from '@bootnodedev/intents-framework-core/dist/src/Base7683'; import { IToken, MultiProtocolProvider, Token } from '@hyperlane-xyz/sdk'; import { assert, isValidAddress } from '@hyperlane-xyz/utils'; import { useAccountAddressForChain } from '@hyperlane-xyz/widgets'; @@ -114,7 +113,7 @@ export async function checkOrderFilled({ const from = to - 10; const events = await contract.queryFilter(filter, from, to); - for (const event of events as Array) { + for (const event of events) { const resolvedOrder = event.args.orderId; if (resolvedOrder === orderId) { From fc7a3a09cb8a427212458af81a31eb3595a43834 Mon Sep 17 00:00:00 2001 From: fernandomg Date: Mon, 31 Mar 2025 15:05:37 +0200 Subject: [PATCH 6/8] fix: extract values to const variables --- src/features/tokens/balances.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/features/tokens/balances.ts b/src/features/tokens/balances.ts index 831325b5..c42db3fd 100644 --- a/src/features/tokens/balances.ts +++ b/src/features/tokens/balances.ts @@ -106,11 +106,14 @@ export async function checkOrderFilled({ const contract = Hyperlane7683Factory.connect(connection.token.addressOrDenom, provider); const filter = contract.filters.Filled(); + const BLOCKS_TO_CHECK = 10; + const BLOCK_CHECK_INTERVAL = 4_000; + return new Promise((resolve, reject) => { const intervalId = setInterval(async () => { try { const to = await provider.getBlockNumber(); - const from = to - 10; + const from = to - BLOCKS_TO_CHECK; const events = await contract.queryFilter(filter, from, to); for (const event of events) { @@ -125,6 +128,6 @@ export async function checkOrderFilled({ clearInterval(intervalId); reject(error); } - }, 4_000); + }, BLOCK_CHECK_INTERVAL); }); } From 52b787480b7106f8fd32289f368d6c1199f09a48 Mon Sep 17 00:00:00 2001 From: fernandomg Date: Mon, 31 Mar 2025 15:18:03 +0200 Subject: [PATCH 7/8] refactor: guard clause for conditional rendering --- .../transfer/TransfersDetailsModal.tsx | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/features/transfer/TransfersDetailsModal.tsx b/src/features/transfer/TransfersDetailsModal.tsx index 4685773c..9a7d4de8 100644 --- a/src/features/transfer/TransfersDetailsModal.tsx +++ b/src/features/transfer/TransfersDetailsModal.tsx @@ -270,18 +270,18 @@ function RemoteTransferProperty({ name, orderId }: { name: string; orderId: stri })); const transfer = transfers.find((t) => t.orderId === orderId)!; + if (!transfer.remoteTxHash) return null; + return ( - transfer.remoteTxHash && ( - - ) + ); } From 052c6ef8ec92b314bb64ab232d13d5b37729b62a Mon Sep 17 00:00:00 2001 From: fernandomg Date: Mon, 31 Mar 2025 15:21:24 +0200 Subject: [PATCH 8/8] refactor: extract to constant Open intent topic hash --- src/features/transfer/useTokenTransfer.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/features/transfer/useTokenTransfer.ts b/src/features/transfer/useTokenTransfer.ts index b09a175f..77add928 100644 --- a/src/features/transfer/useTokenTransfer.ts +++ b/src/features/transfer/useTokenTransfer.ts @@ -171,10 +171,11 @@ async function executeTransfer({ ? tryGetMsgIdFromTransferReceipt(multiProvider, origin, txReceipt) : undefined; + const INTENT_OPEN_EVENT_TOPIC_ID = + '0x3448bbc2203c608599ad448eeb1007cea04b788ac631f9f558e8dd01a3c27b3d'; + const orderId = (txReceipt?.receipt as TransactionReceipt)?.logs?.find( - (log) => - log.topics[0].toLowerCase() === - '0x3448bbc2203c608599ad448eeb1007cea04b788ac631f9f558e8dd01a3c27b3d', // `Open` event + (log) => log.topics[0].toLowerCase() === INTENT_OPEN_EVENT_TOPIC_ID, )?.topics[1]; updateTransferStatus(transferIndex, (transferStatus = TransferStatus.WaitingForFulfillment), {