From a0b11b23cbb03d9c2636bfbff3bc23c86961a710 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 4 May 2026 17:34:41 +0000 Subject: [PATCH 1/2] Initial plan From b53e604bb9fa39e61fd208110667583cc3a8dcce Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 4 May 2026 17:40:08 +0000 Subject: [PATCH 2/2] Address PR review: improve nonces() error handling, validate tx sender, fix phase messages --- .../squidrouter-permit-execution-handler.ts | 17 ++++++++++++---- .../offramp/routes/evm-to-alfredpay.ts | 20 ++++++++++++++++--- .../src/pages/progress/phaseMessages.ts | 14 ++++++++++--- apps/frontend/src/translations/en.json | 3 +++ apps/frontend/src/translations/pt.json | 3 +++ bun.lock | 1 + 6 files changed, 48 insertions(+), 10 deletions(-) diff --git a/apps/api/src/api/services/phases/handlers/squidrouter-permit-execution-handler.ts b/apps/api/src/api/services/phases/handlers/squidrouter-permit-execution-handler.ts index a2b38bdfc..19e6dcc16 100644 --- a/apps/api/src/api/services/phases/handlers/squidrouter-permit-execution-handler.ts +++ b/apps/api/src/api/services/phases/handlers/squidrouter-permit-execution-handler.ts @@ -118,7 +118,8 @@ export class SquidrouterPermitExecuteHandler extends BasePhaseHandler { state: RampState, hash: `0x${string}` | undefined, fromNetwork: EvmNetworks, - label: string + label: string, + expectedFrom?: `0x${string}` ): Promise { if (!hash) { throw this.createRecoverableError(`${label} hash not yet reported by frontend`); @@ -128,29 +129,37 @@ export class SquidrouterPermitExecuteHandler extends BasePhaseHandler { if (!receipt || receipt.status !== "success") { throw this.createRecoverableError(`${label} tx failed: ${hash}`); } + if (expectedFrom && receipt.from.toLowerCase() !== expectedFrom.toLowerCase()) { + throw this.createUnrecoverableError(`${label} tx ${hash} was sent by ${receipt.from}, expected ${expectedFrom}`); + } logger.info(`${label} tx confirmed: ${hash}`); } private async executeNoPermitFallback(state: RampState, fromNetwork: EvmNetworks): Promise { + const expectedFrom = state.state.walletAddress as `0x${string}` | undefined; + if (state.state.isDirectTransfer) { await this.waitForUserHash( state, state.state.squidRouterNoPermitTransferHash as `0x${string}` | undefined, fromNetwork, - "No-permit direct transfer" + "No-permit direct transfer", + expectedFrom ); } else { await this.waitForUserHash( state, state.state.squidRouterNoPermitApproveHash as `0x${string}` | undefined, fromNetwork, - "No-permit approve" + "No-permit approve", + expectedFrom ); await this.waitForUserHash( state, state.state.squidRouterNoPermitSwapHash as `0x${string}` | undefined, fromNetwork, - "No-permit swap" + "No-permit swap", + expectedFrom ); } diff --git a/apps/api/src/api/services/transactions/offramp/routes/evm-to-alfredpay.ts b/apps/api/src/api/services/transactions/offramp/routes/evm-to-alfredpay.ts index b70080eef..0947eef17 100644 --- a/apps/api/src/api/services/transactions/offramp/routes/evm-to-alfredpay.ts +++ b/apps/api/src/api/services/transactions/offramp/routes/evm-to-alfredpay.ts @@ -22,7 +22,16 @@ import { UnsignedTx } from "@vortexfi/shared"; import Big from "big.js"; -import { encodeAbiParameters, encodeFunctionData, keccak256, PublicClient, pad, parseAbiParameters, toHex } from "viem"; +import { + ContractFunctionExecutionError, + encodeAbiParameters, + encodeFunctionData, + keccak256, + PublicClient, + pad, + parseAbiParameters, + toHex +} from "viem"; import { privateKeyToAccount } from "viem/accounts"; import { MOONBEAM_EXECUTOR_PRIVATE_KEY } from "../../../../../constants/constants"; import AlfredPayCustomer from "../../../../../models/alfredPayCustomer.model"; @@ -232,6 +241,7 @@ export async function prepareEvmToAlfredpayOfframpTransactions({ const chainId = getNetworkId(fromNetwork)!; // Probe EIP-2612 support: tokens that don't implement nonces() (e.g. USDT on Base) revert here. + // Only treat contract-call failures as "no permit"; rethrow network/transport errors. let userNonce: bigint | null = null; try { userNonce = (await publicClient.readContract({ @@ -240,8 +250,12 @@ export async function prepareEvmToAlfredpayOfframpTransactions({ args: [userAddress], functionName: "nonces" })) as bigint; - } catch { - userNonce = null; + } catch (error) { + if (error instanceof ContractFunctionExecutionError) { + userNonce = null; + } else { + throw error; + } } const supportsPermit = userNonce !== null; diff --git a/apps/frontend/src/pages/progress/phaseMessages.ts b/apps/frontend/src/pages/progress/phaseMessages.ts index 5449426fe..9351652e4 100644 --- a/apps/frontend/src/pages/progress/phaseMessages.ts +++ b/apps/frontend/src/pages/progress/phaseMessages.ts @@ -100,9 +100,17 @@ export function getMessageForPhase(ramp: RampState | undefined, t: TFunction<"tr assetSymbol: outputAssetSymbol }), squidRouterApprove: getSquidRouterSwapMessage(), - squidRouterNoPermitApprove: getSquidRouterPermitMessage(), - squidRouterNoPermitSwap: getSquidRouterPermitMessage(), - squidRouterNoPermitTransfer: getSquidRouterPermitMessage(), + squidRouterNoPermitApprove: t("pages.progress.squidRouterNoPermitApprove", { + assetSymbol: inputAssetSymbol + }), + squidRouterNoPermitSwap: t("pages.progress.squidRouterNoPermitSwap", { + assetSymbol: inputAssetSymbol, + fromNetwork: quote.from, + toNetwork: quote.to + }), + squidRouterNoPermitTransfer: t("pages.progress.squidRouterNoPermitTransfer", { + assetSymbol: inputAssetSymbol + }), squidRouterPay: getSquidRouterSwapMessage(), squidRouterPermitExecute: getSquidRouterPermitMessage(), squidRouterSwap: getSquidRouterSwapMessage(), diff --git a/apps/frontend/src/translations/en.json b/apps/frontend/src/translations/en.json index 4b4117961..809048ca7 100644 --- a/apps/frontend/src/translations/en.json +++ b/apps/frontend/src/translations/en.json @@ -1185,6 +1185,9 @@ "pendulumToAssethubXcm": "Transferring {{assetSymbol}} from Pendulum --> AssetHub", "pendulumToHydrationXcm": "Transferring {{assetSymbol}} from Pendulum --> Hydration", "pendulumToMoonbeamXcm": "Transferring {{assetSymbol}} from Pendulum --> Moonbeam", + "squidRouterNoPermitApprove": "Approving {{assetSymbol}} for cross-chain transfer", + "squidRouterNoPermitSwap": "Transferring {{assetSymbol}} from {{fromNetwork}} to {{toNetwork}}", + "squidRouterNoPermitTransfer": "Transferring {{assetSymbol}} to Vortex", "squidRouterPermitExecute": "Initializing the transfer of {{assetSymbol}} from {{fromNetwork}}", "squidRouterSwap": "Transferring {{assetSymbol}} from {{fromNetwork}} to {{toNetwork}}", "stellarPayment": "Transferring {{assetSymbol}} from Stellar --> local partner", diff --git a/apps/frontend/src/translations/pt.json b/apps/frontend/src/translations/pt.json index 19ed043d5..f1f8f10a7 100644 --- a/apps/frontend/src/translations/pt.json +++ b/apps/frontend/src/translations/pt.json @@ -1189,6 +1189,9 @@ "pendulumToAssethubXcm": "Transferindo {{assetSymbol}} de Pendulum --> AssetHub", "pendulumToHydrationXcm": "Transferindo {{assetSymbol}} de Pendulum --> Hydration", "pendulumToMoonbeamXcm": "Transferindo {{assetSymbol}} de Pendulum --> Moonbeam", + "squidRouterNoPermitApprove": "Aprovando {{assetSymbol}} para transferĂȘncia cross-chain", + "squidRouterNoPermitSwap": "Transferindo {{assetSymbol}} de {{fromNetwork}} para {{toNetwork}}", + "squidRouterNoPermitTransfer": "Transferindo {{assetSymbol}} para Vortex", "squidRouterPermitExecute": "Autorizando transferĂȘncia de {{assetSymbol}} de {{fromNetwork}}", "squidRouterSwap": "Transferindo {{assetSymbol}} de {{fromNetwork}} para {{toNetwork}}", "stellarPayment": "Transferindo {{assetSymbol}} de Stellar --> parceiro local", diff --git a/bun.lock b/bun.lock index 2e2f56008..48638ddf9 100644 --- a/bun.lock +++ b/bun.lock @@ -1,5 +1,6 @@ { "lockfileVersion": 1, + "configVersion": 0, "workspaces": { "": { "name": "vortex-monorepo",