feat(universal-router-sdk): support Universal Router v2.3.0 3-parameter UNWRAP_WETH - #639
Draft
claude[bot] wants to merge 2 commits into
Draft
feat(universal-router-sdk): support Universal Router v2.3.0 3-parameter UNWRAP_WETH#639claude[bot] wants to merge 2 commits into
claude[bot] wants to merge 2 commits into
Conversation
…er UNWRAP_WETH From Universal Router v2.3.0 (Uniswap/universal-router#489) UNWRAP_WETH encodes (recipient, amount, minAmount) instead of (recipient, amountMinimum): amount is the exact amount of WETH to unwrap and CONTRACT_BALANCE (2^255) is the only unwrap-the-full-balance sentinel (0 now unwraps nothing). - Add UniversalRouterVersion.V2_3_0 and isAtLeastV2_3_0 (no CHAIN_CONFIGS entries yet; addresses pending deployment) - Add PAYMENTS_COMMANDS_V2_3_0 ABI override table and a layered getCommandDefinition used by both createCommand and CommandParser - Migrate internal UNWRAP_WETH call sites (UniswapTrade, addMixedSwap, encodeSwapStep, UnwrapWETH entity) to the 3-param encoding when urVersion >= 2.3.0, preserving legacy full-balance semantics via CONTRACT_BALANCE (UnwrapWETH entity uses its known exact pulled amount) - Add optional exact `amount` to the UNWRAP_WETH swap step, gated in validateEncodeSwaps to urVersion >= 2.3.0 - v4-sdk: add URVersion.V2_3_0 so toV4URVersion resolves the new version - Tests for both version tiers plus changesets (minor) Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014JpEsbYe1BuXBVri6anYBi
Resolves conflict in universal-router-sdk validateEncodeSwaps.ts between main's direct-transfers recipient validation (#638) and this branch's v2.3.0 UNWRAP_WETH amount support: UNWRAP_WETH now uses the direct-transfers-aware checkRecipient from main while keeping the UNWRAP_WETH_AMOUNT_UNSUPPORTED_BEFORE_V2_3_0 invariant; imports keep both MAX_UINT160 and isAtLeastV2_3_0.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Requested by Alice Henshaw · Slack thread
Description
SDK support for the Universal Router
UNWRAP_WETH(0x0c) breaking change in Uniswap/universal-router#489, exposed as a new opt-in router version tier so the SDK API stays backward compatible.Before / After (the on-chain behavior change)
UNWRAP_WETHencodes(recipient, amountMinimum)and always unwraps the router's entire WETH balance, reverting if that balance is belowamountMinimum. There is no way to unwrap only part of the balance.UNWRAP_WETHencodes(recipient, amount, minAmount)and unwraps exactlyamount, leaving the rest as WETH in the router.CONTRACT_BALANCE(2**255, already exported by the SDK) is the only unwrap-the-full-balance sentinel;0now means "unwrap nothing", not "unwrap all with no minimum". Legacy-equivalent migration:UNWRAP_WETH(recipient, x)→UNWRAP_WETH(recipient, CONTRACT_BALANCE, x).How
Follows the exact pattern the SDK already uses for the v2.1.1
minHopPriceX36swap-command extension:UniversalRouterVersion.V2_3_0 = '2.3.0'+isAtLeastV2_3_0()inuniversal-router-sdk/src/utils/constants.ts, mirroringisAtLeastV2_1_1.PAYMENTS_COMMANDS_V2_3_0inrouterCommands.tswith the 3-paramUNWRAP_WETHABI['address','uint256','uint256'], plus a new exportedgetCommandDefinition(type, urVersion)that layers overrides newest-tier-first overCOMMAND_DEFINITION. Used bycreateCommandfor encoding;CommandParser.parseCalldataspreads the same table for decoding.defaultAbiCoder's arity check doubles as an encode-time guard against un-migrated call sites.urVersionthrough and encode[recipient, CONTRACT_BALANCE, amountMin]on >= 2.3.0 (preserving legacy full-balance semantics), or the old 2-param shape otherwise:entities/actions/uniswap.ts(4 sites: input-unwrap ingress, native-output settlement, exact-output refund, mixed-route WETH→ETH transition) via a sharedunwrapWethParamshelperutils/encodeSwapStep.ts(theencodeSwapsstep encoder)entities/actions/unwrapWETH.ts—UnwrapWETHgains an optionalurVersionconstructor param; since it knows the exact amount it pulled via Permit2, on >= 2.3.0 it encodes a true exact unwrap[ROUTER_AS_RECIPIENT, amount, amount]rather thanCONTRACT_BALANCEUNWRAP_WETHSwapStepgains an optional exactamount?: BigNumberish(defaults toCONTRACT_BALANCEwhen encoding for >= 2.3.0).validateEncodeSwapsrejectsamounton pre-2.3.0 versions (UNWRAP_WETH_AMOUNT_UNSUPPORTED_BEFORE_V2_3_0) instead of silently dropping it.URVersion.V2_3_0enum addition sotoV4URVersiondoesn't throw when encoding V4_SWAP at the new version (the two enums resolve by shared string values). v4-sdk must release first / in the same batch.@uniswap/universal-router-sdkand@uniswap/v4-sdk(the new ABI is gated behind expliciturVersionopt-in, so the SDK API is backward compatible). Note for integrators: calldata built withurVersion >= 2.3.0must only be sent to v2.3.0+ deployments, and 2-param calldata reverts on v2.3.0 routers.CHAIN_CONFIGSentries for V2_3_0 yet:routerConfigsis an optional-per-version map, soUNIVERSAL_ROUTER_ADDRESS(V2_3_0, chainId)throws "not deployed" (the repo's existing convention for versions absent on a chain, e.g. V1_2 on Linea). Entries slot in with no code change once the routers deploy.How Has This Been Tested?
Unit tests (hardhat/mocha), all passing locally — 383 tests across
test/unit+test/utils:test/utils/routerCommands.test.ts(new):getCommandDefinitiontier layering (base / 2.1.1 / 2.3.0) andcreateCommandarity guards in both directionstest/utils/commandParser.test.ts: 3-param decode at 2.3.0 (exact amount andCONTRACT_BALANCEsentinel), legacy 2-param decode at 2.2.0, and 2.1.1 swap overrides still layering at 2.3.0test/unit/encodeSwaps.test.ts:encodeSwapStep2-param vs 3-param encoding withamount ?? CONTRACT_BALANCEdefaulting and exact-amount passthrough; end-to-endSwapRouter.encodeSwapsat 2.3.0;validateEncodeSwapsversion gatetest/unit/toV4URVersion.test.ts: existing enum-sync contract test now covers V2_3_0 automaticallybun testsuite passing (240 pass / 2 skip)Not run in this environment:
test:forge(noforgebinary) and the fork-basedtest/uniswapTrades.test.ts(requiresFORK_URL). Those forge/interop fixtures pin pre-2.3.0 router deployments and are intentionally untouched; 2.3.0 fixture variants need a deployed/forkable v2.3.0 router.Are there any breaking changes?
No SDK API breaking changes — the 3-param encoding only activates behind an explicit
urVersion >= 2.3.0opt-in; all defaults and existing signatures are unchanged (theUnwrapWETHconstructor gains a trailing optional param). The on-chain command ABI is breaking between router versions, which is exactly what the version gate isolates: do not send >= 2.3.0 calldata to older routers or vice versa.(Optional) Feedback Focus
UnwrapWETHentity intentionally encodes an exact unwrap of its known pulled amount on >= 2.3.0 (safer thanCONTRACT_BALANCEthere); theUniswapTradesites keepCONTRACT_BALANCEfor exact parity with legacy behavior. Sanity-check that split.PAYMENTS_COMMANDS_V2_3_0andgetCommandDefinition.(Optional) Follow Ups
2.3.0) once feat: add UNWRAP_WETH_EXACT command and bound command input decoding universal-router#489 ships, and add per-chainCHAIN_CONFIGSaddresses + creation blocks after deployment.UniswapTradeingress/transition unwraps to exact just-transferred amounts instead ofCONTRACT_BALANCE.@uniswap/universal-routerartifact dependency to the release containing feat(sdk): add workspace:* protocol and @uniswap/sdk umbrella package #489 when published (outerexecute()ABI is unchanged, so not required for correctness).🤖 Generated with Claude Code
https://claude.ai/code/session_014JpEsbYe1BuXBVri6anYBi
Generated by Claude Code