From 1001cf370c41eb86544570b359a4a13881a16c9b Mon Sep 17 00:00:00 2001 From: Martin Grabina Date: Fri, 27 Feb 2026 15:25:38 -0300 Subject: [PATCH] fix: match permit deadline to order validTo for CoW limit orders The permit deadline was hardcoded to 1 hour while CoW order validTo can be up to 1 year. This caused orders with long expiries to become permanently unfillable once the permit expired, since the post-hook's permit() call would revert and solvers would skip the order. Co-Authored-By: Claude Opus 4.6 --- .../CollateralSwapActionsViaCoWAdapters.tsx | 1 + .../Swap/actions/DebtSwap/DebtSwapActionsViaCoW.tsx | 1 + .../RepayWithCollateralActionsViaCoW.tsx | 1 + .../Swap/actions/SwapActions/SwapActionsViaCoW.tsx | 11 ++++++----- .../Swap/actions/approval/useSwapTokenApproval.ts | 6 +++++- 5 files changed, 14 insertions(+), 6 deletions(-) diff --git a/src/components/transactions/Swap/actions/CollateralSwap/CollateralSwapActionsViaCoWAdapters.tsx b/src/components/transactions/Swap/actions/CollateralSwap/CollateralSwapActionsViaCoWAdapters.tsx index ee82e3e28d..9aaa91dbcc 100644 --- a/src/components/transactions/Swap/actions/CollateralSwap/CollateralSwapActionsViaCoWAdapters.tsx +++ b/src/components/transactions/Swap/actions/CollateralSwap/CollateralSwapActionsViaCoWAdapters.tsx @@ -137,6 +137,7 @@ export const CollateralSwapActionsViaCowAdapters = ({ allowPermit: !disablePermitDueToActiveOrder, // CoW Adapters do support permit but avoid nonce reuse trackingHandlers, swapType: state.swapType, + validTo, }); // Use centralized gas estimation diff --git a/src/components/transactions/Swap/actions/DebtSwap/DebtSwapActionsViaCoW.tsx b/src/components/transactions/Swap/actions/DebtSwap/DebtSwapActionsViaCoW.tsx index 60eed532bb..97ce2633d2 100644 --- a/src/components/transactions/Swap/actions/DebtSwap/DebtSwapActionsViaCoW.tsx +++ b/src/components/transactions/Swap/actions/DebtSwap/DebtSwapActionsViaCoW.tsx @@ -152,6 +152,7 @@ export const DebtSwapActionsViaCoW = ({ type: 'delegation', // Debt swap uses delegation trackingHandlers, swapType: state.swapType, + validTo, }); // Use centralized gas estimation diff --git a/src/components/transactions/Swap/actions/RepayWithCollateral/RepayWithCollateralActionsViaCoW.tsx b/src/components/transactions/Swap/actions/RepayWithCollateral/RepayWithCollateralActionsViaCoW.tsx index ab3d0a9e4e..3cc8bc461c 100644 --- a/src/components/transactions/Swap/actions/RepayWithCollateral/RepayWithCollateralActionsViaCoW.tsx +++ b/src/components/transactions/Swap/actions/RepayWithCollateral/RepayWithCollateralActionsViaCoW.tsx @@ -150,6 +150,7 @@ export const RepayWithCollateralActionsViaCoW = ({ allowPermit: !disablePermitDueToActiveOrder, // avoid nonce reuse if active order present trackingHandlers, swapType: state.swapType, + validTo, }); // Use centralized gas estimation diff --git a/src/components/transactions/Swap/actions/SwapActions/SwapActionsViaCoW.tsx b/src/components/transactions/Swap/actions/SwapActions/SwapActionsViaCoW.tsx index 3e2dcca891..959d0f9a74 100644 --- a/src/components/transactions/Swap/actions/SwapActions/SwapActionsViaCoW.tsx +++ b/src/components/transactions/Swap/actions/SwapActions/SwapActionsViaCoW.tsx @@ -81,6 +81,11 @@ export const SwapActionsViaCoW = ({ state.sourceToken.addressToSwap ); + const validTo = useMemo( + () => Math.floor(Date.now() / 1000) + ExpiryToSecondsMap[state.expiry], + [state.expiry] + ); + const { requiresApproval, requiresApprovalReset, @@ -101,6 +106,7 @@ export const SwapActionsViaCoW = ({ allowPermit: !disablePermitDueToActiveOrder, trackingHandlers, swapType: state.swapType, + validTo, }); // Use centralized gas estimation @@ -112,11 +118,6 @@ export const SwapActionsViaCoW = ({ approvalTxState, }); - const validTo = useMemo( - () => Math.floor(Date.now() / 1000) + ExpiryToSecondsMap[state.expiry], - [state.expiry] - ); - const { sendTx } = useWeb3Context(); const slippageInPercent = state.slippage; diff --git a/src/components/transactions/Swap/actions/approval/useSwapTokenApproval.ts b/src/components/transactions/Swap/actions/approval/useSwapTokenApproval.ts index 78ae3346ba..517635cbd2 100644 --- a/src/components/transactions/Swap/actions/approval/useSwapTokenApproval.ts +++ b/src/components/transactions/Swap/actions/approval/useSwapTokenApproval.ts @@ -33,6 +33,9 @@ export type SwapTokenApprovalParams = { type?: 'approval' | 'delegation'; trackingHandlers?: TrackAnalyticsHandlers; swapType: SwapType; + /** Unix timestamp (seconds) for the order's validTo. When provided, the permit deadline + * will match this value so the permit stays valid for the order's entire lifetime. */ + validTo?: number; }; export type SignatureLike = { @@ -93,6 +96,7 @@ export const useSwapTokenApproval = ({ type = 'approval', trackingHandlers, swapType, + validTo, }: SwapTokenApprovalParams) => { const [approvedAmount, setApprovedAmount] = useState(); const [approvedAddress, setApprovedAddress] = useState(); @@ -335,7 +339,7 @@ export const useSwapTokenApproval = ({ if (usePermit) { // Permit approval try { - const deadline = Math.floor(Date.now() / 1000 + 3600).toString(); + const deadline = (validTo ?? Math.floor(Date.now() / 1000 + 3600)).toString(); let signatureRequest: string; if (type === 'delegation') { signatureRequest = await generateCreditDelegationSignatureRequest({