From c9520edb6c04c2933136d57a2fb2f1ebd4ba7628 Mon Sep 17 00:00:00 2001 From: gomes-bot Date: Mon, 16 Mar 2026 18:08:45 +0100 Subject: [PATCH 1/5] fix: use real chainflip swap id for explorer links The status-by-id endpoint returns the real Chainflip network swap ID in status.swapId, but we were using the broker deposit channel ID from swap creation for the scan.chainflip.io link. The channel ID points to a completely different (or nonexistent) swap. Now we extract the real swapId from the status response and only use it for the explorer link once available. Before deposit detection (when swapId isn't in the response yet), we fall back to the regular chain explorer link instead of a wrong Chainflip scan link. closes #11899 Co-Authored-By: Claude Opus 4.6 (1M context) --- .../src/swappers/ChainflipSwapper/endpoints.ts | 4 +++- .../swapper/src/swappers/ChainflipSwapper/types.ts | 1 + packages/swapper/src/types.ts | 1 + .../TradeConfirm/hooks/useTradeExecution.tsx | 11 +++++++---- .../useSwapActionSubscriber.tsx | 7 +++++-- src/lib/tradeExecution.ts | 2 ++ 6 files changed, 19 insertions(+), 7 deletions(-) diff --git a/packages/swapper/src/swappers/ChainflipSwapper/endpoints.ts b/packages/swapper/src/swappers/ChainflipSwapper/endpoints.ts index 406015fcb09..c59a2b0601a 100644 --- a/packages/swapper/src/swappers/ChainflipSwapper/endpoints.ts +++ b/packages/swapper/src/swappers/ChainflipSwapper/endpoints.ts @@ -231,7 +231,7 @@ export const chainflipApi: SwapperApi = { const { data: statusResponse } = maybeStatusResponse.unwrap() const { - status: { swapEgress }, + status: { swapEgress, swapId }, } = statusResponse // Assume no outbound Tx is a pending Tx @@ -240,6 +240,7 @@ export const chainflipApi: SwapperApi = { buyTxHash: undefined, status: TxStatus.Pending, message: getLatestChainflipStatusMessage(statusResponse), + chainflipSwapId: swapId ? Number(swapId) : undefined, } } @@ -249,6 +250,7 @@ export const chainflipApi: SwapperApi = { buyTxHash: swapEgress.transactionReference, status: TxStatus.Confirmed, message: undefined, + chainflipSwapId: swapId ? Number(swapId) : undefined, } }, } diff --git a/packages/swapper/src/swappers/ChainflipSwapper/types.ts b/packages/swapper/src/swappers/ChainflipSwapper/types.ts index 89cc43d67ae..4f40b298b78 100644 --- a/packages/swapper/src/swappers/ChainflipSwapper/types.ts +++ b/packages/swapper/src/swappers/ChainflipSwapper/types.ts @@ -5,6 +5,7 @@ import type { ChainflipBaasStatusSwap } from './models/ChainflipBaasStatusSwap' export type ChainFlipStatus = { status: { state: 'waiting' | 'receiving' | 'swapping' | 'sending' | 'sent' | 'completed' | 'failed' + swapId?: string swap?: ChainflipBaasStatusSwap swapEgress?: ChainflipBaasStatusEgress } diff --git a/packages/swapper/src/types.ts b/packages/swapper/src/types.ts index e5c1f785c7a..6d1109cc02b 100644 --- a/packages/swapper/src/types.ts +++ b/packages/swapper/src/types.ts @@ -821,6 +821,7 @@ export type TradeStatus = { relayerExplorerTxLink?: string | undefined message: string | [string, InterpolationOptions] | undefined actualBuyAmountCryptoBaseUnit?: string + chainflipSwapId?: number } // a result containing all routes that were successfully generated, or an error in the case where diff --git a/src/components/MultiHopTrade/components/TradeConfirm/hooks/useTradeExecution.tsx b/src/components/MultiHopTrade/components/TradeConfirm/hooks/useTradeExecution.tsx index 3160cfc2d69..2cc5e8d96dd 100644 --- a/src/components/MultiHopTrade/components/TradeConfirm/hooks/useTradeExecution.tsx +++ b/src/components/MultiHopTrade/components/TradeConfirm/hooks/useTradeExecution.tsx @@ -261,7 +261,7 @@ export const useTradeExecution = ( ) execution.on( TradeExecutionEvent.Status, - ({ buyTxHash, message, actualBuyAmountCryptoBaseUnit }) => { + ({ buyTxHash, message, actualBuyAmountCryptoBaseUnit, chainflipSwapId }) => { dispatch( tradeQuoteSlice.actions.setSwapTxMessage({ hopIndex, @@ -280,9 +280,9 @@ export const useTradeExecution = ( ) } - // Update the swap with the actual buy amount if available + // Update the swap with the actual buy amount and/or real Chainflip swap ID if available // Read fresh state to avoid stale closure - swapsById captured at render time may have outdated status - if (actualBuyAmountCryptoBaseUnit) { + if (actualBuyAmountCryptoBaseUnit || chainflipSwapId) { const freshActiveSwapId = swapSlice.selectors.selectActiveSwapId(store.getState()) if (freshActiveSwapId) { const currentSwap = swapSlice.selectors.selectSwapsById(store.getState())[ @@ -292,7 +292,10 @@ export const useTradeExecution = ( dispatch( swapSlice.actions.upsertSwap({ ...currentSwap, - actualBuyAmountCryptoBaseUnit, + ...(actualBuyAmountCryptoBaseUnit && { actualBuyAmountCryptoBaseUnit }), + ...(chainflipSwapId && { + metadata: { ...currentSwap.metadata, chainflipSwapId }, + }), }), ) } diff --git a/src/hooks/useActionCenterSubscribers/useSwapActionSubscriber.tsx b/src/hooks/useActionCenterSubscribers/useSwapActionSubscriber.tsx index 369283f643b..1f16c35251a 100644 --- a/src/hooks/useActionCenterSubscribers/useSwapActionSubscriber.tsx +++ b/src/hooks/useActionCenterSubscribers/useSwapActionSubscriber.tsx @@ -213,7 +213,7 @@ export const useSwapActionSubscriber = () => { if (!swap.sellTxHash) return if (!swap.receiveAddress) return - const { status, message, buyTxHash, actualBuyAmountCryptoBaseUnit } = + const { status, message, buyTxHash, actualBuyAmountCryptoBaseUnit, chainflipSwapId } = await queryClient.fetchQuery({ queryKey: tradeStatusQueryKey(swap.id, swap.sellTxHash), queryFn: () => @@ -252,7 +252,7 @@ export const useSwapActionSubscriber = () => { defaultExplorerBaseUrl, maybeSafeTx, stepSource: status && status !== TxStatus.Unknown ? swap.source : undefined, - maybeChainflipSwapId: `${swap.metadata.chainflipSwapId}`, + maybeChainflipSwapId: chainflipSwapId?.toString(), maybeNearIntentsDepositAddress: swap.metadata.nearIntentsSpecific?.depositAddress, ...(swap.swapperName === SwapperName.CowSwap ? { tradeId: txHash } : { txId: txHash }), ...(swap.metadata.relayerTxHash && { @@ -272,6 +272,9 @@ export const useSwapActionSubscriber = () => { buyTxHash, txLink, actualBuyAmountCryptoBaseUnit, + ...(chainflipSwapId && { + metadata: { ...swap.metadata, chainflipSwapId }, + }), }), ) diff --git a/src/lib/tradeExecution.ts b/src/lib/tradeExecution.ts index 77a9b064d73..fc4ebda605e 100644 --- a/src/lib/tradeExecution.ts +++ b/src/lib/tradeExecution.ts @@ -88,6 +88,7 @@ export const fetchTradeStatus = async ({ relayerTxHash, relayerExplorerTxLink, actualBuyAmountCryptoBaseUnit, + chainflipSwapId, } = await swapper.checkTradeStatus({ txHash: sellTxHash, chainId: sellAssetChainId, @@ -114,6 +115,7 @@ export const fetchTradeStatus = async ({ relayerTxHash, relayerExplorerTxLink, actualBuyAmountCryptoBaseUnit, + chainflipSwapId, } } From aac28975db0171c907395bb5014358e087bdc79d Mon Sep 17 00:00:00 2001 From: gomes-bot Date: Mon, 16 Mar 2026 19:11:51 +0100 Subject: [PATCH 2/5] fix: increase solana compute budget for chainflip deposit addresses Chainflip deposit addresses are program-owned accounts that require more compute than a regular SOL transfer. The simulated compute units were too low, causing "Computational budget exceeded" on broadcast. Apply a 50k floor with 1.6x margin to match Jupiter's recommendation. Co-Authored-By: Claude Opus 4.6 (1M context) --- packages/swapper/src/swappers/ChainflipSwapper/endpoints.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/swapper/src/swappers/ChainflipSwapper/endpoints.ts b/packages/swapper/src/swappers/ChainflipSwapper/endpoints.ts index c59a2b0601a..53a6ec975c0 100644 --- a/packages/swapper/src/swappers/ChainflipSwapper/endpoints.ts +++ b/packages/swapper/src/swappers/ChainflipSwapper/endpoints.ts @@ -169,6 +169,10 @@ export const chainflipApi: SwapperApi = { chainSpecific: { from, tokenId }, }) + // Chainflip deposit addresses are program-owned accounts that require more compute + // than a regular SOL transfer. Apply a safety margin to avoid "Computational budget exceeded". + const computeUnits = Math.ceil(Math.max(Number(fast.chainSpecific.computeUnits), 50_000) * 1.6) + return adapter.buildSendApiTransaction({ to, from, @@ -176,7 +180,7 @@ export const chainflipApi: SwapperApi = { accountNumber, chainSpecific: { tokenId, - computeUnitLimit: fast.chainSpecific.computeUnits, + computeUnitLimit: String(computeUnits), computeUnitPrice: fast.chainSpecific.priorityFee, }, }) From a9dbf0a452b7739687be097fde7a95285940477d Mon Sep 17 00:00:00 2001 From: gomes-bot Date: Mon, 16 Mar 2026 19:33:11 +0100 Subject: [PATCH 3/5] fix: keep chainflipSwapId as string to preserve u64 precision swapId is a u64 on-chain, can exceed JS Number.MAX_SAFE_INTEGER. Remove Number() coercion from status response, widen type to number | string to accept both SDK (number) and status API (string). Co-Authored-By: Claude Opus 4.6 (1M context) --- packages/public-api/src/lib/quoteStore.ts | 2 +- packages/swapper/src/swappers/ChainflipSwapper/endpoints.ts | 4 ++-- packages/swapper/src/types.ts | 6 +++--- .../TradeConfirm/hooks/useChainflipStreamingProgress.tsx | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/public-api/src/lib/quoteStore.ts b/packages/public-api/src/lib/quoteStore.ts index caae711f67f..60a3e8ee798 100644 --- a/packages/public-api/src/lib/quoteStore.ts +++ b/packages/public-api/src/lib/quoteStore.ts @@ -16,7 +16,7 @@ export type StoredQuote = { createdAt: number expiresAt: number metadata: { - chainflipSwapId?: number + chainflipSwapId?: number | string nearIntentsDepositAddress?: string nearIntentsDepositMemo?: string relayId?: string diff --git a/packages/swapper/src/swappers/ChainflipSwapper/endpoints.ts b/packages/swapper/src/swappers/ChainflipSwapper/endpoints.ts index 53a6ec975c0..5d2873f4dcd 100644 --- a/packages/swapper/src/swappers/ChainflipSwapper/endpoints.ts +++ b/packages/swapper/src/swappers/ChainflipSwapper/endpoints.ts @@ -244,7 +244,7 @@ export const chainflipApi: SwapperApi = { buyTxHash: undefined, status: TxStatus.Pending, message: getLatestChainflipStatusMessage(statusResponse), - chainflipSwapId: swapId ? Number(swapId) : undefined, + chainflipSwapId: swapId ?? undefined, } } @@ -254,7 +254,7 @@ export const chainflipApi: SwapperApi = { buyTxHash: swapEgress.transactionReference, status: TxStatus.Confirmed, message: undefined, - chainflipSwapId: swapId ? Number(swapId) : undefined, + chainflipSwapId: swapId ?? undefined, } }, } diff --git a/packages/swapper/src/types.ts b/packages/swapper/src/types.ts index 6d1109cc02b..f8bb33adc81 100644 --- a/packages/swapper/src/types.ts +++ b/packages/swapper/src/types.ts @@ -448,7 +448,7 @@ export type TradeQuoteStep = { } cowswapQuoteResponse?: OrderQuoteResponse chainflipSpecific?: { - chainflipSwapId?: number + chainflipSwapId?: number | string chainflipDepositAddress?: string chainflipNumberOfChunks?: number chainflipChunkIntervalBlocks?: number @@ -567,7 +567,7 @@ export type SwapExecutionMetadata = { } export type SwapperSpecificMetadata = { - chainflipSwapId: number | undefined + chainflipSwapId: number | string | undefined nearIntentsSpecific?: { depositAddress: string depositMemo?: string @@ -821,7 +821,7 @@ export type TradeStatus = { relayerExplorerTxLink?: string | undefined message: string | [string, InterpolationOptions] | undefined actualBuyAmountCryptoBaseUnit?: string - chainflipSwapId?: number + chainflipSwapId?: number | string } // a result containing all routes that were successfully generated, or an error in the case where diff --git a/src/components/MultiHopTrade/components/TradeConfirm/hooks/useChainflipStreamingProgress.tsx b/src/components/MultiHopTrade/components/TradeConfirm/hooks/useChainflipStreamingProgress.tsx index b1a8ad05a9e..523ba56bdad 100644 --- a/src/components/MultiHopTrade/components/TradeConfirm/hooks/useChainflipStreamingProgress.tsx +++ b/src/components/MultiHopTrade/components/TradeConfirm/hooks/useChainflipStreamingProgress.tsx @@ -24,7 +24,7 @@ const DEFAULT_STREAMING_SWAP_METADATA: StreamingSwapMetadata = { } const getChainflipStreamingSwap = async ( - swapId: number | undefined, + swapId: number | string | undefined, ): Promise => { if (!swapId) return From 14915feac448b2ae7f62434c8397313ab66b9ed3 Mon Sep 17 00:00:00 2001 From: gomes-bot Date: Mon, 16 Mar 2026 19:50:30 +0100 Subject: [PATCH 4/5] fix: sync solana fee quote with boosted compute budget + nullish swapId checks - getSolanaTransactionFees now mirrors the same 1.6x compute unit boost as getUnsignedSolanaTransaction to avoid underquoting fees - Use nullish checks (== null / != null) for chainflipSwapId since 0 is a valid u64 swap ID but falsy in JS Co-Authored-By: Claude Opus 4.6 (1M context) --- .../swapper/src/swappers/ChainflipSwapper/endpoints.ts | 8 +++++++- .../TradeConfirm/hooks/useChainflipStreamingProgress.tsx | 4 ++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/packages/swapper/src/swappers/ChainflipSwapper/endpoints.ts b/packages/swapper/src/swappers/ChainflipSwapper/endpoints.ts index 5d2873f4dcd..0a0c2cda6d5 100644 --- a/packages/swapper/src/swappers/ChainflipSwapper/endpoints.ts +++ b/packages/swapper/src/swappers/ChainflipSwapper/endpoints.ts @@ -211,7 +211,13 @@ export const chainflipApi: SwapperApi = { }, }) - return fast.txFee + // Mirror the same compute budget boost as getUnsignedSolanaTransaction + // to avoid underquoting fees for Chainflip deposit addresses + const simulatedUnits = Number(fast.chainSpecific.computeUnits) + const boostedUnits = Math.ceil(Math.max(simulatedUnits, 50_000) * 1.6) + const ratio = simulatedUnits > 0 ? boostedUnits / simulatedUnits : 1 + + return String(Math.ceil(Number(fast.txFee) * ratio)) }, checkTradeStatus: async ({ config, swap }) => { const chainflipSwapId = swap?.metadata.chainflipSwapId diff --git a/src/components/MultiHopTrade/components/TradeConfirm/hooks/useChainflipStreamingProgress.tsx b/src/components/MultiHopTrade/components/TradeConfirm/hooks/useChainflipStreamingProgress.tsx index 523ba56bdad..5c1fba25e1a 100644 --- a/src/components/MultiHopTrade/components/TradeConfirm/hooks/useChainflipStreamingProgress.tsx +++ b/src/components/MultiHopTrade/components/TradeConfirm/hooks/useChainflipStreamingProgress.tsx @@ -26,7 +26,7 @@ const DEFAULT_STREAMING_SWAP_METADATA: StreamingSwapMetadata = { const getChainflipStreamingSwap = async ( swapId: number | string | undefined, ): Promise => { - if (!swapId) return + if (swapId == null) return const config = getConfig() const brokerUrl = config.VITE_CHAINFLIP_API_URL @@ -105,7 +105,7 @@ export const useChainflipStreamingProgress = ({ useQuery({ queryKey: ['streamingSwapData', chainflipSwapId, SwapperName.Chainflip], queryFn: - chainflipSwapId && + chainflipSwapId != null && swap && swap.swapperName === SwapperName.Chainflip && sellTxHash && From 3e28e7aa119234004d32c498ae57252097eb8401 Mon Sep 17 00:00:00 2001 From: gomes-bot Date: Mon, 16 Mar 2026 22:34:21 +0100 Subject: [PATCH 5/5] fix: nullish guard for chainflipSwapId in checkTradeStatus 0 is a valid u64 swap ID but falsy in JS. Co-Authored-By: Claude Opus 4.6 (1M context) --- packages/swapper/src/swappers/ChainflipSwapper/endpoints.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/swapper/src/swappers/ChainflipSwapper/endpoints.ts b/packages/swapper/src/swappers/ChainflipSwapper/endpoints.ts index 0a0c2cda6d5..17858beb28b 100644 --- a/packages/swapper/src/swappers/ChainflipSwapper/endpoints.ts +++ b/packages/swapper/src/swappers/ChainflipSwapper/endpoints.ts @@ -221,7 +221,7 @@ export const chainflipApi: SwapperApi = { }, checkTradeStatus: async ({ config, swap }) => { const chainflipSwapId = swap?.metadata.chainflipSwapId - if (!chainflipSwapId) throw Error(`chainflipSwapId is required`) + if (chainflipSwapId == null) throw Error(`chainflipSwapId is required`) const brokerUrl = config.VITE_CHAINFLIP_API_URL const apiKey = config.VITE_CHAINFLIP_API_KEY