Conversation
Register the new workspace package for the Uniswap v4 margin trading periphery, mirroring the liquidity-launcher-sdk toolchain: viem-only runtime dependency, three-target tsc build (cjs/esm/types), bun test, and the shared eslint/prettier configuration. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… ABIs Minimal exact ABIs for the MarginRouter, the venue-agnostic ILendingAdapter surface (plus per-venue setMarket/MarketSet variants for Morpho Blue, Aave v3, and Aave v4), the MarginAccount, and Permit2. Every entry-point selector was verified against the deployed mainnet router via expired-deadline DeadlinePassed reverts, and every read was called live against the deployed Morpho adapter. Includes the mainnet deployment registry (router, account implementation, three lending adapters), WAD/sentinel constants (OPEN_DELTA, CONTRACT_BALANCE, FULL_CLOSE), typed onchain struct mirrors, and the stable-coded MarginSdkError. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…math predictMarginAccountAddress mirrors MarginRouter.accountOf offchain: the Solady clone-with-immutable-args CREATE2 derivation with (owner, manager) baked into the initcode and an (owner, manager, subId) salt. Verified against nine accountOf vectors read from the live mainnet router. Market helpers mirror the onchain Market type (pool/market reconciliation and zeroForOne derivation); the math module provides decimal-aware position sizing (sizeIncrease/sizeDecrease from a quote plus slippage), leverage/LTV conversions, and health factors matching describePosition semantics. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Calldata encoders and viem write descriptors for increasePosition, decreasePosition (plus closePosition FULL_CLOSE sugar), addCollateral, execute, multicall, and the forwarded Permit2 permit — with SDK-side mirrors of the contract guards (slippage bounds, uint128 ranges, pool/market reconciliation, native-equity handling). Encodings are test-anchored byte-for-byte to cast-generated calldata. MarginPlanner composes execute plans from the v4 routing actions and the 0x30-range margin actions, enforcing offchain the structural rules the router reverts on (SET_ACCOUNT before account-scoped opcodes, PULL_TO_ACCOUNT zero-amount and CONTRACT_BALANCE-from-user footguns), and finalizes to abi.encode(bytes actions, bytes[] params) unlockData. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Venue-agnostic read descriptors (accountOf, describePosition, positionOf, LTV/allowlist/market-support checks, account views) that drop into wagmi useReadContract(s) or viem, each paired with a PublicClient helper. README covers the position model, the quickstart long flow, native-ETH equity, shorts and per-subId venue isolation, execute-plan composition rules, and the mainnet deployment table. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…deployment Runnable anvil-fork demos (bun run demo) proving the SDK drives every flow the v4-periphery margin contract tests exercise, with the margin deployer impersonated as sender and all sizing derived from real v4 Quoter quotes against the live USDC/WETH 0.05% pool: - 01 long lifecycle (MarginRouterIntegration + E2E fork): predicted accountOf, Permit2 setup, 2x open with event decoding and SDK-vs- onchain health math, top-up, leverage-only increase, partial delever, full close with residual returned - 02 native-ETH equity (MarginRouterNative): open and top up with raw msg.value, no approvals - 03 shorts on Aave v3 + Aave v4 (Aave adapter fork tests): reversed (collateral, debt) pairing and decimals, identical code per venue, 78% liquidation threshold read back on both - 04 cross-venue hedge (MarginRouterCrossVenueHedge.fork): isolated sub-accounts, delta-neutral within live-pool impact, independent unwind - 05 execute plans (MarginRouterExecute): a MarginPlanner plan reproducing the curated open action-for-action, repay-from-wallet, and the owner-only MarginAccount.execute escape hatch The harness pins the fork below head (load-balanced RPCs serve inconsistent tip state), pads gas (anvil fork-mode estimates run low on cold slots), checks every receipt status, and deals tokens by probing balance-mapping slots. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
● Reviewed · 2026-08-27 14:22 UTC · 2 reviews · view run ↗
Adds AssessmentThe package is additive and follows the established |
There was a problem hiding this comment.
Note
✅ Approved — see full review in the sticky comment ↑
Graphite Automations"Request reviewers once CI passes on sdks monorepo" took an action on this PR • (07/24/26)3 reviewers were added and 1 assignee was added to this PR based on Siyu Jiang (See-You John)'s automation. |
From the production-readiness review: mirror more contract constraints offchain so misuse fails with a typed MarginSdkError instead of a confusing revert or a silently-wrong transaction. - deadlines are validated as plausible Unix seconds: zero/negative rejected, and Date.now()-scale millisecond values rejected loudly (a ms deadline would silently disable the timer for ~3,000 years) - adapter, market, pool, and hook addresses are validated before encoding, replacing raw viem errors with typed ones - toPoolKey enforces the v4 bounds: static LP fee up to MAX_LP_FEE or the DYNAMIC_FEE_FLAG, and tick spacing in [1, 32767] - planner fund-out actions (withdraw, borrow, account sweep, take, take-portion, sweep) reject the zero address as recipient; the MSG_SENDER/ADDRESS_THIS sentinels remain valid Relative imports in the touched files gain explicit .js extensions as part of the native-ESM packaging fix landing in this series. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
From the production-readiness review: the read descriptors are the backend's entire position/health-monitoring surface and had no tests, and the Permit2 forward only asserted its selector — a swapped uint48 expiration/nonce pair would have been selector-identical while authorizing the wrong permit. - every read descriptor's (address, abi, functionName, args) wiring is asserted and ABI-encoded against the SDK ABI - encodeRouterPermit and permit2ApproveCall are pinned byte-for-byte to cast-generated calldata vectors Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…JS + ESM)
Fixes the two ship blockers from the production-readiness review — the
publish artifact could not be loaded by the Trading API backend target
in either module system, while CI stayed green because nothing ever
imported dist/ or ran under real Node:
- tslib moves from devDependencies to dependencies: importHelpers emits
'require("tslib")' at the top of the CJS entry, so a clean consumer
install crashed MODULE_NOT_FOUND on require('@uniswap/margin-sdk')
- every relative import gains an explicit .js extension and the build
writes per-directory module-type markers (dist/esm {type:module},
dist/cjs {type:commonjs}); native Node ESM previously failed
ERR_MODULE_NOT_FOUND on the extensionless specifiers
- viem becomes a peerDependency (its types cross the public API) and
stays in devDependencies for the test suite
Adds a built-artifact smoke gate (bun run check:package, wired into
test): packs the real publish artifact, installs it into an isolated
consumer with ONLY declared dependencies resolvable, and loads it under
native Node via both require() and import, running a live account-
derivation vector — so this failure class cannot regress silently.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
From the production-readiness review: the offline tests validate the SDK against frozen vectors, so nothing automated proved the SDK produces calldata the live contracts accept. The end-to-end demo suite is that proof — wire it into the test run as a gated fork stage. bun run test:fork executes demo/run-all.ts (the full lifecycle, native ETH, Aave v3/v4 shorts, hedge, and execute-plan flows against the live mainnet deployment on an anvil fork) when FORK_URL or MARGIN_DEMO_RPC is set — CI provides FORK_URL alongside its Foundry install — and skips cleanly otherwise so local runs stay offline by default. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
From the production-readiness review: the SDK carries ~800 lines of hand-written ABI and viem encodes tuples positionally, so a field reorder in the still-draft contracts would produce silently-wrong calldata with the offline suite green. bun run check:abi-drift compiles the contracts from a local v4-periphery checkout (V4_PERIPHERY_PATH) via forge inspect and asserts every SDK ABI entry — router, account, and all three adapters, plus the venue-agnostic surface against each venue — exists in the Solidity with an identical canonical signature, outputs, state mutability, and event index layout. Verified green against the margin-trading branch. README documents the three validation gates (package smoke, ABI drift, gated fork suite) and the 0.0.x pre-release posture while v4-periphery#563 is in review and governance remains the deployer EOA. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
From the production-readiness review: the hardcoded mainnet addresses point at contracts still in review (v4-periphery#563) with governance on the deployer EOA pending the timelock/multisig handoff. Downgrade the initial changeset from minor to patch so the package publishes as 0.0.1 and graduates to 0.1.0 only once the deployment is final. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The CollateralAdded debt-unchanged check compared the event's interest- accrued debtTotal exactly against a position read a few blocks earlier, so a few wei of Morpho accrual could flake the fork suite depending on live rates. Compare within 1 bps like the other cross-block assertions. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ings pinned to v4-periphery From the re-review: the ~800 hand-written ABI lines were the weakest link — verified today, but with viem's positional tuple encoding a future contract field reorder would produce silently-wrong calldata. src/generated/abis.ts is now produced by scripts/generate-abis.ts from a v4-periphery checkout PINNED to a specific commit (recorded in the file header, currently fe8105a9 — the mainnet deployment build): forge-compiled full ABIs for the router, account, and all three adapters, normalized (stable key order, solc internalType stripped) and prettier-formatted deterministically. The venue-agnostic LENDING_ADAPTER_ABI is assembled from the compiled ILendingAdapter interface plus the ownership/error items of the compiled Morpho adapter — nothing hand-written remains for the margin contracts. PERMIT2_ABI stays local deliberately: canonical Permit2 is immutable. regenerate:abis rebuilds from the pin (refusing dirty or off-pin checkouts; --update-pin re-pins); check:abis regenerates to memory and diffs against the committed file, replacing the weaker signature-level check-abi-drift script. Full ABIs also mean simulate/decode now sees every inherited error and event, not just the curated subset. Equivalence is proven, not assumed: the byte-for-byte cast calldata vectors and live-mainnet selector anchors all pass unchanged against the generated bindings (105/105). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
From the re-review: the Node CJS+ESM smoke covered the backend target but the browser target was never actually loaded. The package check gains two browser stages over the same packed artifact: - a static scan of the shipped ESM module graph that fails on any Node-builtin specifier (node:*, fs, crypto, ...) — the real browser failure mode for an isomorphic library, invisible to Node smoke tests - a jsdom load: the installed ESM entry is imported with jsdom's window/document as globals and runs the account-derivation vector jsdom is a devDependency only; the SDK under test still resolves nothing beyond its declared dependencies. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ry build on every PR From the re-review: the drift gate existed but was manual and unpinned. The margin-sdk-abi-check workflow (mirroring the liquidity-launcher lock-bytecode gate) now runs on every PR touching the package: it reads the pinned commit from the generated bindings header, clones v4-periphery at exactly that commit, compiles it with forge, and fails if the committed bindings differ from a fresh regeneration — closing the silent-wrong-calldata gap in CI rather than by convention. README documents the regenerate/re-pin workflow and the browser-load stage. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
|
Warning Review the following alerts detected in dependencies. According to your organization's Security Policy, it is recommended to resolve "Warn" alerts. Learn more about Socket for GitHub.
|
…cape-hatch encoders The router has no curated withdraw entry point — its only write entry points are increasePosition, decreasePosition, addCollateral, and execute — so withdrawing collateral without touching debt had to be hand-rolled as an execute plan, where every failure mode is a silently unsafe position rather than a revert. - `withdrawCollateralPlan(...)` builds the curated plan (SET_ACCOUNT → ACCOUNT_WITHDRAW_COLLATERAL → ASSERT_HEALTH) and closes the three footguns: a literal (non-sentinel) recipient, an explicit amount, and a mandatory non-zero maxLtvAfter. - Account-direct encoders for the IMarginAccount owner escape hatch — withdrawCollateral, supplyCollateral, borrow, repay, sweep — for recovering a position if the router is ever deprecated, paused, or compromised. New AccountContractWrite descriptor type, since these target the account, not the router. No ABI regeneration needed: the generated MARGIN_ACCOUNT_ABI already carries all five plus ReceiverNotAllowed. Also fixes the recipient guard on the account-scoped fund-out actions. The router forwards ACCOUNT_WITHDRAW_COLLATERAL / ACCOUNT_BORROW / ACCOUNT_SWEEP recipients straight to the account without _mapRecipient (unlike the router-level TAKE/SWEEP opcodes), so the MSG_SENDER/ADDRESS_THIS sentinels arrive as the literal 0x…01 / 0x…02, match neither the account's owner nor its manager, and revert ReceiverNotAllowed. `validateAccountRecipient` now rejects them at build time. The planner's borrow test asserted the opposite and passed ADDRESS_THIS as a recipient; it now uses the literal router address, matching what the curated increase encodes (`address(this)`). Calldata and plan params are anchored to cast-generated ground truth. Contract behaviour verified against v4-periphery at the pinned commit fe8105a9e31ac6e30c9b18bd1078047cab3e1cea (Market.toSwapParams, MarginAccount._requireReceiver, MarginRouter._handleAction). Adds demo/06-withdraw-collateral.ts (16 assertions, green against the live mainnet deployment on an anvil fork): the curated plan, a native exit via withdraw → unwrap → sweep with the router netted to zero, and the account-direct escape hatch. It also pins that maxLtvAfter really binds onchain and that the unsafe variants (zero bound, MSG_SENDER recipient) never build. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Pushed Withdrawing collateral without touching debt
|
…rading Regenerate the forge-generated bindings from upstream e4a5062 (was fe8105a), picking up every ABI-level change since the last pin: - MarginRouter: new ZeroAmount() error — zero-amount inputs (collateralToBuy, addCollateral amount, PULL_TO_ACCOUNT amount, partial debtToRepay) now revert with it instead of SlippageBoundRequired - MarginAccount: `function receive()` became a true receive() fallback - MorphoLendingAdapter: ZeroAddress() constructor guard - CompoundV3LendingAdapter: new fourth-venue adapter, now compiled into the bindings as COMPOUND_V3_LENDING_ADAPTER_ABI (setMarket(collateral,debt,bool), MarketSet event, ZeroAddress/DebtNotBaseToken/AccountMismatch errors) The venue-agnostic LENDING_ADAPTER_ABI surface is unchanged (ILendingAdapter and the MarketAllowlist refactor were doc/storage-only; MarketNotSupported kept its signature when it moved to file scope). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…vert split Track the upstream margin-periphery additions in the SDK surface: - add 'compoundV3' to LendingVenue and re-export COMPOUND_V3_LENDING_ADAPTER_ABI; no mainnet address entry — the adapter is not in the live deployment yet, and the addresses test pins that absence as a tripwire to flip on deploy - document that Compound reads are account-level like Aave (one position per (owner, subId)) and that the adapter binds a single Comet whose base token is the only borrowable debt - document the SlippageBoundRequired → ZeroAmount split for zero-amount inputs, noting the live mainnet router predates the new error and still reverts those paths with SlippageBoundRequired; SDK-side validation already distinguished the two (INVALID_AMOUNT vs SLIPPAGE_BOUND_REQUIRED), so behavior is unchanged Verified: bun test (105 pass), check:abis against the new pin, check:package (CJS/ESM/browser), and the full anvil-fork demo suite against the live mainnet deployment. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Re-pin the forge-generated bindings to v4-periphery margin-trading 4202ba3, where position swaps route through a caller-supplied Universal Router instead of a single v4 pool: - IncreaseParams/DecreaseParams drop poolKey/minHopPriceX36 for universalRouter/routeCommands/routeInputs; encoders mirror the new onchain validation (UniversalRouterNotSet, IneffectiveLtvBound) with UNIVERSAL_ROUTER_REQUIRED / INEFFECTIVE_LTV_BOUND error codes. - New buildV4ExactOutRoute builds the canonical single-pool v4 route (a byte-for-byte mirror of the periphery's MarginRouteHelpers), the ergonomic replacement for the old poolKey field. - MarginPlanner gains routeSwap (ROUTE_SWAP 0x39) and assertAccountBalance (ASSERT_ACCOUNT_BALANCE 0x3a, account-scoped). - The PositionUpdated snapshot event ships in the regenerated ABI. - Mainnet addresses retarget to the redeployed suite (router 0x00000000000Dc78b00e36d3a7997Bd9c4cd9F1f0) and add the Compound v3 adapter; every address verified onchain (code, governance, allowlist). Cast-generated calldata vectors and accountOf vectors regenerated against the live router (new selectors 0x084a1ed3/0x9b304505 confirmed dispatching via expired-deadline probes).
Every demo flow now builds its swap as a Universal Router route delivered to the derived MarginAccount (demoRoute over buildV4ExactOutRoute), matching the redeployed router's API. Demo 05 reconstructs the curated open with ROUTE_SWAP + ASSERT_ACCOUNT_BALANCE and asserts the per-mutation PositionUpdated snapshots. The fork boots with --disable-code-size-limit and deploys a post-#491 Universal Router from the pinned v4-periphery artifact (override with MARGIN_DEMO_UNIVERSAL_ROUTER): the periphery's default-profile UR build exceeds EIP-170, which forge's test EVM relaxes but anvil enforces. All five flows pass end-to-end against the live mainnet deployment on an anvil fork.
…suite Quickstart, close/delever, and short examples now build Universal Router routes; the deployment table points at the live redeployed suite (with Compound v3) and explains why the Universal Router is a per-call parameter rather than a table entry. Error docs add IneffectiveLtvBound/UniversalRouterNotSet and drop the stale ZeroAmount pre-release caveat.
The withdraw-collateral demo landed upstream against the pool-key increasePosition signature; its open2xLong helper now derives the account first and supplies the Universal Router route via demoRoute, like the other demo flows. The withdrawal paths themselves are swap-free and unchanged. All six fork demos pass against the live mainnet deployment.
This comment has been minimized.
This comment has been minimized.
…ccrual A close route must buy at least the live debt, and the exact-output is static calldata, so integrators buffered the quote with a flat 10 bps haircut that conflates swap slippage with interest accrual and over-buys by orders of magnitude on quiet markets. New accrual module: - measureBorrowRatePerSecond samples the interest-accrued debt (adapter.positionOf) at two blocks and derives the position's realized per-second growth. Venue-agnostic across all four adapters and prices in venue quirks a nominal APR read would miss; refuses windows that contain a position mutation. - estimateInterestAccrual / projectDebt compound the rate over a blocks-denominated inclusion horizon (3-term Taylor of e^x, the series Morpho accrues with; upper-bounds Compound's linear accrual). - sizeFullClose composes the accrual-buffered debtToBuy with the separately-priced swap-slippage cap, mirroring sizeDecrease. Demo 01's full close now measures the rate over a clean 2-block window and asserts the resulting buffer undercuts the old flat haircut; validated against live Morpho on the fork suite (all six flows green).
The pin advances past the PositionUpdated unlock-free-path emissions (addCollateral and the zero-debt swap-free close). The ABI itself is unchanged: the events and signatures already existed, they now fire on more paths. Note the live mainnet router predates this behavior; the new emissions apply from the next router deployment.
… ABI Re-pins bindings to v4-periphery 7fc38d2, where every lending adapter implements IAmountResolver (the resolver side of the proposed Universal Router RESOLVE instruction). resolveAmount joins the shared adapter surface assembled into LENDING_ADAPTER_ABI, since it is identical across venues. Context ABI: abi.encode(kind, account, market) with kind 0 = DEBT, 1 = COLLATERAL. Route-builder support lands when a RESOLVE-capable UR deployment exists; until then the accrual estimator sizes the static buffer.
Router 0x000000000075e82F7B7DdC5DD1B4984b560eF5D4 plus all four adapters at their new CREATE2 addresses (the account implementation is unchanged). The suite carries the unlock-free-path PositionUpdated emissions and the IAmountResolver adapter surface. Every address verified onchain (code, governance, allowlist, markets, a resolveAmount probe); accountOf test vectors regenerated from the live router and the selector dispatch re-confirmed via expired-deadline probes. Full fork demo suite green against the new deployment.
…ediations) Captures the M-01/M-02/M-03 and L-05/L-08/L-14 audit fixes. The ABI-affecting change is M-02's new ILendingAdapter.encodeEnableCollateral (now 11 items); the other fixes are behavior/internal only. check:abis and the unit suite pass against the new pin.
…indings The 2026-08-26 DeployMargin broadcast (blocks 25842465-25842483) redeployed the full margin suite with the OpenZeppelin audit fix set. Addresses verified onchain (code, governance, adapter allowlist, canonical markets) before pinning: - MarginRouter 0x0000000000F57fCd0d5a78a19907240F1169EDEC - MarginAccount impl 0xdDD0967e90bCBc2D1F026b3977bb4dE39133b109 - Morpho 0x766C34DcFBA565a1b72ce83ECD96712376Ca1f3D, Aave v3 0x7E1A543Bd8ed2F16D61DA4b6bC2eC5d240D098aC, Aave v4 0xAb3C2661c810295Db32125942f04b92c61fAE2Eb, Compound v3 0x77598B845d0200fc707bD32A8Ad6DCF85C995e0d ABI bindings regenerated pinned to v4-periphery 0aa9cc3 (picks up the audit wave: NoDebtToRepay, encodeEnableCollateral, and the owner-lifecycle and governance-construction events). Account-address vectors re-read from the new router onchain, both curated selectors re-confirmed dispatching against it (DeadlinePassed probe), and the full fork demo suite passed against the live deployment.
The demo suite routes swaps through a Universal Router with unlocked V4_SWAP support (v4-periphery #491), which the historical mainnet deployments predate, so it deploys one onto the fork from a v4-periphery forge build. The shared monorepo test job has no such checkout, so `test:fork` — wired into the package's `test` script — failed there on every run. Move it to a dedicated workflow, gated on `sdks/margin-sdk/**`, that clones v4-periphery at the same commit the committed ABI bindings are pinned to, builds it, and points the suite at it via V4_PERIPHERY_PATH. The package's `test` stays unit tests + the package check: fast, no foundry, no periphery checkout, and unrelated PRs pay nothing for the Solidity build.
@uniswap/margin-sdk
A new workspace package for the margin trading periphery (Uniswap/v4-periphery#563): leveraged spot positions built from a v4 swap composed with a borrow/supply against an external lending venue — Morpho Blue, Aave v3, or Aave v4 — all behind one
MarginRouter. Follows theliquidity-launcher-sdkconventions: viem-only runtime dependency, minimalas const satisfies AbiABIs, descriptor +PublicClientread pattern, bun test, three-target tsc build.What's included
encode.tsincreasePosition,decreasePosition(+closePositionsugar over theFULL_CLOSEsentinel),addCollateral,execute,multicall, forwarded Permit2permit— with SDK-side mirrors of the contract guards (mandatory slippage caps, uint128 bounds, pool↔market reconciliation, native-ETH equity handling)account.tsaccountOf: Solady clone-with-immutable-args CREATE2 derivation, so frontends resolve account addresses with zero RPC callsmath.tssizeIncrease/sizeDecreasefrom a quote + slippage, works for both the 18d/6d long and reversed-decimal short), leverage↔LTV conversions, health factors matchingdescribePositionsemanticsplanner.ts/actions.tsMarginPlannerforexecuteplans: v4 routing actions + the0x30-range margin opcodes, enforcing offchain the structural rules the router reverts on (SET_ACCOUNTordering,PULL_TO_ACCOUNTzero-amount /CONTRACT_BALANCEfootguns)reads.tsuseReadContract(s)/ viem multicall) +PublicClienthelpersabis.ts/addresses.ts/types.tsValidation
The SDK is anchored to the deployed mainnet contracts, not just the source:
DeadlinePassedreverts (a wrong selector would revert empty).accountOftest vectors were read from the live router; the CWIA derivation reproduces them byte-for-byte.cast-generated ground truth.0.86e18read back exactly as documented).80 unit tests, lint, typecheck, build, and dep-consistency all green.
End-to-end demos (
demo/,bun run demo)Runnable anvil-fork flows proving the SDK drives everything the v4-periphery contract tests exercise, against the live deployment and the real USDC/WETH 0.05% pool, with sizing from real v4 Quoter quotes — 60 assertions, all passing:
MarginRouterIntegration+E2E.fork): predicted account, Permit2 setup, 2x open with event decoding and SDK-vs-onchain health math (matches to 1 bps), top-up, leverage-only increase, partial delever, full close with residual returned.MarginRouterNative): open/top-up with rawmsg.value, no approvals.CrossVenueHedge.fork): isolated sub-accounts, delta-neutral within live-pool impact, independent unwind.executeplans (MarginRouterExecute): aMarginPlannerplan reproducing the curated open action-for-action, repay-from-wallet, and the owner escape hatch.Notes for reviewers
0.0.0with aminorchangeset — the standard release flow mints0.1.0.0x0000000004BBC92D0657580CAe35aEBF054E5CDC); a redeploy requires regenerating them (the test files document how).