f(x) Protocol private fxUSD minting recipes - #53
Merged
Conversation
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…lose math Also adds viem@2.9.0 as a dependency and upgrades TypeScript to 5.x (needed because viem 2.x type declarations use TS5 syntax incompatible with TS 4.9). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Port FxMintOpenRecipe and FxMintCloseRecipe from PrivateMint monorepo into the cookbook fork; update imports to cookbook-internal relative paths and wire the borrow barrel. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…fxPositionNFT constant The pool registry now carries each pool's native collateral decimals (wstETH=18, WBTC=8) so cookbook amount metadata matches on-chain reality. FxMintPoolRef's custom-pool branch widens to require collateralDecimals. The previous FX_ADDRESSES.fxPositionNFT pinned wstETH-Long's pool address and was wrong for WBTC-Long (different pool, different NFT contract). Removed; recipes already used per-pool addresses for outputNFTs. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
FxMintRepayDebtRecipe needs the same fee accounting that computeFxClose already uses for its repay leg. Extract computeFxRepay; have computeFxClose delegate to it for the repay computation, then add the collateral-side proportional withdraw on top. Behavior of computeFxClose is preserved (golden-value test asserts equality with pre-refactor output).
…lateralDecimals The borrow fee is per-(pool, operator) and governance-upgradable on f(x). Hardcoding 0.5% would silently mis-account if governance moves the rate. Step now takes borrowFeeRatio as a constructor arg; caller fetches via PoolConfiguration.getPoolFeeRatio(pool, operator)[2] at recipe build time. Also plumbs collateralDecimals through so cookbook's amount metadata is correct for non-18-decimal collateral (8 for WBTC). Was hardcoded 18n, which would mis-account by 10^10 for WBTC-Long. Note: borrowFeeRatio and collateralDecimals are temporarily optional on the step's data type (defaulted to current mainnet values: 5_000_000n and 18n respectively) so FxMintOpenRecipe still compiles; Task 7 tightens them to required as part of the recipe-layer update.
Was hardcoded 18n for collateral output/spent lines, which would mis-account by 10^10 for WBTC-Long. Now driven by the pool registry's collateralDecimals (8 for WBTC, 18 for wstETH). Note: collateralDecimals is temporarily optional on the step's data type (default 18n) so FxMintCloseRecipe still compiles; Task 8 tightens to required as part of the recipe-layer update. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
New step that wraps PoolManager.operate() with signed collDelta/debtDelta arguments. Used by the new topup, topup-and-borrow, borrow-more, and repay-debt recipes (in subsequent commits). Position NFT is consumed and re-emitted with the same tokenSubID (adjusts never burn the NFT). Validates at construction that at least one delta is non-zero and that the relevant fee ratio is supplied for the active direction. Matches FxMintClosePositionStep's partial-close branch for NFT handling (only outputNFTs, not spentNFTs — the step-validator combines spent+output into a single map and rejects duplicates). 7/7 new tests passing; full-suite failure count unchanged at 9. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…alidation Recipe now branches its step graph on swapQuote presence: - wstETH-Long: requires swapQuote (WETH → wstETH path; existing behavior). - WBTC-Long: forbids swapQuote (direct WBTC input). - Custom pool ref: trusts caller's choice. Borrow fee ratio is now a required opt (no more hardcoded 0.5%); caller fetches it via PoolConfiguration.getPoolFeeRatio at recipe-build time. Plumbs collateralDecimals through to the step. validatePoolFlow is exported for reuse by close, topup, and topup-and-borrow recipes in subsequent commits. Tightens FxMintOpenPositionStep's borrowFeeRatio + collateralDecimals back to required (Task 4 left them temporarily optional). Cleans up the open-step test's non-null assertions and FEE_DENOM hardcode.
…imals plumbing Recipe now branches on swapQuote presence: - wstETH-Long (swap path): operate(close) → approve(0x) → swap(wstETH → WETH) - WBTC-Long (direct path): operate(close) (collateral shields back as-is) Reuses validatePoolFlow from fx-mint-open-recipe for per-pool flow checks. Tightens FxMintClosePositionStep's collateralDecimals back to required (Task 5 left it temporarily optional). Cleans up the close-step test's NFT assertions and imprecise comment.
Adds collateral to an existing f(x) Long position without changing debt. Two paths via shared validatePoolFlow: - wstETH-Long (swap path): WETH -> wstETH -> operate(positionId, +coll, 0) - WBTC-Long (direct path): WBTC -> operate(positionId, +coll, 0) Position NFT consumed and re-emitted (same id). Builds on the new FxMintAdjustPositionStep with collDelta > 0, debtDelta = 0.
Combines collateral top-up with additional fxUSD mint in a single operate() call. Uses FxMintAdjustPositionStep with both deltas non-zero. Same per-pool flow validation as FxMintTopupRecipe via shared validatePoolFlow. Construction validates additionalDebt > 0 (otherwise caller should use FxMintTopupRecipe).
…tion) Single-step recipe: operate(positionId, 0, +additionalDebt). No swap leg, no collateral side, pool-agnostic. Useful for extracting more fxUSD as position appreciates.
Symmetric counterpart to FxMintTopupRecipe — together they form the position's two risk-management dials. Burns fxUSD via PoolManager.operate to reduce debt on an existing position; collateral unchanged. Two-step recipe: ApproveERC20SpenderStep(PoolMgr, fxUSD, approveAmount) → FxMintAdjustPositionStep(positionId, 0, -repayAmount, repayFeeRatio). Caller pre-computes amounts via computeFxRepay (extracted in Task 3).
Wraps PoolManager.getPoolInfo + Pool.getTotalRawCollaterals + Pool.getLiquidateRatios + PoolConfiguration.getPoolFeeRatio into a single sync function returning the FxPool shape. Default operator is the Railgun relay-adapter; integrators can override. Adds the getLiquidateRatios ABI fragment to FX_POOL_ABI (confirmed via Task 1 discovery: lives on Pool, returns (debtRatio: 1e18, bonusRatio: 1e9); mainnet readings 0.95e18 / 4e7 on both wstETH-Long and WBTC-Long). Wallets call this once and feed borrowFeeRatio/repayFeeRatio into recipe constructors directly — no more hardcoded fees anywhere. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Returns per-position state (rawColls, rawDebts, debtRatio) plus a native-decimals collateralAmount derived via the rawColls × collateralBalance / totalRawColls formula computeFxClose uses internally. Wallet integrators feed collateralAmount into their own price feeds for LTV display; rawColls/rawDebts feed into computeFxClose / computeFxRepay for close/repay flows.
…+ read API + repay math Wires up the four new recipes (Topup / TopupAndBorrow / BorrowMore / RepayDebt), FxMintAdjustPositionStep, the getFxPool / getFxPosition read API, and the computeFxRepay helper through the package barrel chain so consumers can import them all from '@railgun-community/cookbook' root. Also adds the missing api/borrow and api/borrow/fx index barrels (no api/borrow existed before) and threads './borrow' into src/api/index.ts.
- Move DEFAULT_FXMINT_OPERATOR to fx-mint-util.ts (single source of truth).
- Restore slippage-pair test in FxMintTopupAndBorrowRecipe.
- Backfill real positionId in getFxPosition fork test.
- Export FxPoolEntry; spread {...repay} in computeFxClose return.
- Strengthen golden-value test in computeFxClose.
- Add runtime-throw test for FxMintAdjustPositionStep no-input branch.
- Misc comment + cast tightening (no behavioral changes).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Expands the recipe README for v0.1 surface: - Top-up / top-up-and-borrow / borrow-more / repay-debt usage sections - collateralDecimals column in pool support table - Swap-vs-direct path explanation per pool - Read API section (getFxPool + getFxPosition + DEFAULT_FXMINT_OPERATOR) - Dynamic-fees note (no constants hardcoded; caller fetches via getFxPool) - File layout updated for new files Calibration receipts section stubbed for v0.1 — actual tx hashes appended once mainnet calibration broadcasts complete. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replaces the stubbed prose with a 7-row table; each row has a <FILL B.x> placeholder matching the Phase B step IDs in the calibration runbook. After mainnet broadcasts, user sed-replaces each placeholder with the tx hash from the corresponding broadcast.
…y path Mirrors FxMintClosePositionStep's pattern: when debtDelta < 0, the input fxUSD is split by the preceding ApproveERC20SpenderStep into (approveAmount with approvedSpender=PoolManager) + any leftover change (approvedSpender=undefined). Cookbook's step-validator demands per-step input == spent + outputs + fees; the recipe-engine epilogue shield-back happens AFTER all steps, so the leftover must be declared as a step output for the balance to close. Caught by FxMintRepayDebtRecipe mainnet broadcast B.7 (5.94 fxUSD input, 2.004 fxUSD spent → validator rejected). Fix verified by repeat of all 8 FxMintAdjustPositionStep unit tests; test repay-input updated to simulate post-approve state (approvedSpender=PoolManager for the exact spend amount, no orphan in unit test). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Six mainnet calibrations landed (12 May 2026): wstETH-Long open re-cal proving dynamic borrowFeeRatio plumbing, topup (swap), borrow-more, partial repay-debt; WBTC-Long open (first-ever WBTC direct-path fxmint position on mainnet) and partial close (direct, WBTC out). WBTC-Long topup skipped — its step-graph is identical to wstETH-Long topup with the leading approve target swapped, and the WBTC direct input path is exercised by the WBTC-Long open above. Redundant for v0.1 calibration scope.
f(x) Pool exposes a rebalance threshold BELOW the liquidation threshold. When a position's debtRatio crosses the rebalance ratio, the protocol's rebalancer service progressively unwinds collateral to keep the position from ever reaching the liquidation ratio (and getting fully seized). Adds: - getRebalanceRatios() ABI fragment to FX_POOL_ABI - rebalanceDebtRatio + rebalanceBonusRatio fields on FxPool - Read step in getFxPool() Mainnet values (May 2026, wstETH-Long): (0.88e18, 2.5e7) — 88% rebalance threshold, 2.5% rebalancer bonus (vs 95% / 4% for liquidation). Wallets should treat [rebalanceDebtRatio, liquidationDebtRatio) as a yellow risk zone: the position is alive but f(x) is actively unwinding. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…tened guards Pre-upstream-PR cleanup batch addressing f(x) dev's code review: - Drop viem dep entirely; migrate 4 call sites to ethers (Interface for encodeFunctionData; Contract for readContract; JsonRpcProvider in tests). Address type now exported from fx-mint-util. - Replace `as never` casts in three step files with proper ContractTransaction typing — the previous cast silently silenced downstream type checks. - Pin TypeScript back to ^4.9.5 (was ^5.9.3). fxmint only needs 4.9 for `as const satisfies`; the 5.x jump was out of scope for the feature PR. - FxMintRepayDebtRecipe: enforce strict approveAmount equality against the fee-uplifted target (was >= repayAmount). Errors now name the expected value and point at computeFxRepay instead of surfacing as a downstream step-validator failure. - FxMintOpenPositionStep and FxMintAdjustPositionStep: set minBalance = fxUSDNet on the deterministic fxUSD output (was 0n). Unblocks future v0.2 steps that want to consume this fxUSD with a fixed amount. - README: integrator note on DEFAULT_FXMINT_OPERATOR — recommend wallets pass `operator` explicitly rather than relying on the pinned default. - Run prettier over all fxmint files (single quotes / trailing commas / arrow parens to match cookbook house style). v0.2 follow-ups (reviewer agreed defer): rename `positionWstETH` field, multicall batching in getFxPool/getFxPosition. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The previous review-fix commit moved cookbook's Address type from viem's
`\`0x\${string}\`` brand to plain `string`. That broke wallet integrators
(and our CLI) that import `Address` from viem for their own internal use
— passing a cookbook-returned address into viem's strict-typed APIs no
longer typechecks.
Restore the brand natively in fx-mint-util (no viem dep — purely a
type-level convention) plus a small `getAddress` wrapper around ethers'
checksum function that casts the returned plain string back to the brand
so FX_ADDRESSES / KNOWN_POOLS / DEFAULT_FXMINT_OPERATOR stay tidy.
Runtime is unchanged. Type-system change only.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Squabble9
pushed a commit
to Squabble9/cookbook
that referenced
this pull request
May 18, 2026
- Replace placeholder with live upstream PR link (Railgun-Community#53). - Fix the recipe-docs pointer from non-existent docs/fx/ to the actual src/recipes/borrow/fx/ location. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
zy0n
self-requested a review
May 18, 2026 14:03
Collaborator
|
Thank you, will review this soon! |
Replaces three positional `const [, /* label */ value, ...]` destructures in getFxPool and getFxPosition with fully-named slots (`_`-prefixed for unused ones). The old form attached inline-comment labels to the value that followed them, but the labels actually named the slot before — the reverse of how the eye reads top-to-bottom. Pure cosmetic change. No behavior delta; tests unchanged. Addresses items raised in PR Railgun-Community#53 review: - getFxPool poolInfo destructure - getFxPosition poolInfo destructure - fees tuple in getFxPool (same anti-pattern, same file) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The README's getFxPool field-list comment was out of date — commit 00fed54 added `rebalanceDebtRatio` and `rebalanceBonusRatio` to the FxPool return shape but didn't update this block. The FxPool type docstring (fx-position-reader.ts) already covers both fields; this just brings the integrator-facing README in sync. Order matches the actual return-object order in getFxPool (liquidation* → rebalance* → fees). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
No dependency changes detected. Learn more about Socket for GitHub. 👍 No dependency changes detected in pull request |
Revert package.json + yarn.lock to origin/main. Rewrite fx-mint-util's `as const satisfies` to plain `as const` so the type still narrows under typescript@4.8.3.
Collaborator
|
tests confirmed locally. |
Squabble9
pushed a commit
to Squabble9/cookbook
that referenced
this pull request
May 27, 2026
Follow-up to PR Railgun-Community#53 (item Railgun-Community#3 from @zy0n's review). validatePoolFlow now performs a direction-aware shape check on the provided swapQuote, in addition to the existing presence check. The shape check runs on BOTH named-pool and custom-pool refs (zy0n's "both of those logic branches" comment): 'deposit' (open / topup / topup-and-borrow): sellTokenAddress must equal WETH buyERC20Amount.tokenAddress must equal pool.collateralToken 'withdraw' (close): sellTokenAddress must equal pool.collateralToken buyERC20Amount.tokenAddress must equal WETH The direction asymmetry surfaced during implementation: open/topup recipes hardcode `inputToken = FX_ADDRESSES.WETH` and target pool.collateralToken, while close-recipe reverses (withdraws collateral, swaps to WETH). A single direction-agnostic check would reject every valid close-recipe quote, so validatePoolFlow takes a third arg. Compares are case-insensitive. Mismatches raise a clear `fxmint:` error at recipe construction time, replacing what previously surfaced as a confusing on-chain gas-estimate revert. Tests: 9 new cases in the validatePoolFlow describe block, covering both directions (deposit + withdraw), both branches (named + custom), and the checksum-vs-lowercase case-insensitivity path. Existing presence-check tests pass unchanged after adding the direction arg. Fixture sweep across the four recipe test files added the previously- missing sellTokenAddress (the fixtures were cast `as unknown as SwapQuoteData` to bypass the type check; the new shape check would otherwise throw TypeError on undefined). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 tasks
zy0n
pushed a commit
that referenced
this pull request
May 29, 2026
) Follow-up to PR #53 (item #3 from @zy0n's review). validatePoolFlow now performs a direction-aware shape check on the provided swapQuote, in addition to the existing presence check. The shape check runs on BOTH named-pool and custom-pool refs (zy0n's "both of those logic branches" comment): 'deposit' (open / topup / topup-and-borrow): sellTokenAddress must equal WETH buyERC20Amount.tokenAddress must equal pool.collateralToken 'withdraw' (close): sellTokenAddress must equal pool.collateralToken buyERC20Amount.tokenAddress must equal WETH The direction asymmetry surfaced during implementation: open/topup recipes hardcode `inputToken = FX_ADDRESSES.WETH` and target pool.collateralToken, while close-recipe reverses (withdraws collateral, swaps to WETH). A single direction-agnostic check would reject every valid close-recipe quote, so validatePoolFlow takes a third arg. Compares are case-insensitive. Mismatches raise a clear `fxmint:` error at recipe construction time, replacing what previously surfaced as a confusing on-chain gas-estimate revert. Tests: 9 new cases in the validatePoolFlow describe block, covering both directions (deposit + withdraw), both branches (named + custom), and the checksum-vs-lowercase case-insensitivity path. Existing presence-check tests pass unchanged after adding the direction arg. Fixture sweep across the four recipe test files added the previously- missing sellTokenAddress (the fixtures were cast `as unknown as SwapQuoteData` to bypass the type check; the new shape check would otherwise throw TypeError on undefined). Co-authored-by: Kevin <kevin@openclaw.local> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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.
This PR adds the fxmint recipes: Cookbook recipes for the first private decentralized stablecoin minting on Ethereum: fxUSD on f(x) Protocol, minted from shielded RAILGUN balances.
Thin glue over already-audited f(x) position-management contracts and RAILGUN cross-contract-call primitives. There is no new on-chain code.
Full doc:
src/recipes/borrow/fx/README.mdcovers scope (6 recipes + shared step + read API), pool support (wstETH-Long / WBTC-Long), per-operation usage, calibration receipts (6 mainnet broadcasts), privacy properties, and file layout.Tests:
src/recipes/borrow/fx/__tests__/andsrc/steps/borrow/fx/__tests__/.Happy to iterate on whatever review comes back.