Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
097094e
feat(borrow): scaffold borrow category with empty barrels (fxmint prep)
May 7, 2026
a3544dc
feat(borrow/fx): add fx-mint-util — addresses, ABIs, pool registry, c…
May 7, 2026
db079ff
feat(borrow/fx): add FxMintOpen/ClosePositionStep classes
May 7, 2026
9447436
feat(borrow/fx): add FxMintOpen/CloseRecipe classes
May 7, 2026
7d4fe15
docs(borrow/fx): add integrator-facing README
May 7, 2026
ac703cd
feat(fx): add collateralDecimals to pool registry; remove misleading …
May 8, 2026
2f89190
feat(fx): factor repay-side math into computeFxRepay
May 8, 2026
634dd7a
fix(fx): make borrow fee dynamic in FxMintOpenPositionStep; plumb col…
May 8, 2026
debf329
fix(fx): plumb collateralDecimals through FxMintClosePositionStep
May 8, 2026
1301242
feat(fx): add FxMintAdjustPositionStep (signed deltas) for adjust ops
May 8, 2026
034f767
feat(fx): FxMintOpenRecipe — borrowFeeRatio dynamic + per-pool flow v…
May 8, 2026
6f28fd1
feat(fx): FxMintCloseRecipe — auto-branch + per-pool validation + dec…
May 8, 2026
eaa40d4
feat(fx): add FxMintTopupRecipe (collateral-only top-up)
May 8, 2026
a394d09
feat(fx): add FxMintTopupAndBorrowRecipe (lever-up in one tx)
May 8, 2026
4593cd3
feat(fx): add FxMintBorrowMoreRecipe (mint debt against existing posi…
May 8, 2026
6adf4f4
feat(fx): add FxMintRepayDebtRecipe (partial repay for risk management)
May 8, 2026
10a4976
feat(fx): add getFxPool read API + Pool.getLiquidateRatios ABI fragment
May 8, 2026
df6b50a
feat(fx): add getFxPosition read function
May 8, 2026
7a1bcb8
feat(fx): export v0.1 surface from index — new recipes + adjust step …
May 8, 2026
02aec92
chore(fx): v0.1 cleanup batch — consolidated post-review minor fixes
May 11, 2026
18d6b23
docs(fx): v0.1 README — new recipes, read API, file layout
May 11, 2026
f9ae7c7
docs(fx): scaffold v0.1 calibration receipt table
May 11, 2026
ab9b2f4
fix(fx): FxMintAdjustPositionStep — orphan fxUSD pass-through on repa…
May 12, 2026
76a5cb2
docs(fx): fill v0.1 calibration receipts — 6 mainnet broadcasts
May 12, 2026
00fed54
feat(fx): add rebalance debt ratio to FxPool read API
May 12, 2026
60d059d
Update README.md
Squabble9 May 12, 2026
7922fc2
docs(fx): typo — Ecery → Every
May 13, 2026
eda5bd4
chore(fx): address pre-upstream-PR review — viem→ethers, TS 4.9, tigh…
May 14, 2026
1a61888
fix(fx): restore Address type brand so downstream viem consumers compile
May 14, 2026
5eb46e1
refactor(fx): name destructured tuple positions in fx-position-reader
May 25, 2026
9ef3c4f
docs(fx): add rebalance ratios to README field list
May 25, 2026
f60eb83
chore(fx): drop typescript bump + yarn.lock churn from PR
zy0n May 26, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,8 @@ src/test/test-config-overrides.test.ts
# Quickstart assets
test.db
artifacts-*

# Local brainstorming artifacts — design docs and implementation plans
# generated during interactive sessions, kept on disk but never committed
# (so they don't end up in the upstream PR diff).
docs/superpowers/
101 changes: 101 additions & 0 deletions src/api/borrow/fx/__tests__/fx-position-reader.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import chai from 'chai';
import { JsonRpcProvider } from 'ethers';
import { getFxPool, getFxPosition } from '../fx-position-reader';

const { expect } = chai;

describe('fx-position-reader — getFxPool [requires RUN_FORK_TESTS=1]', function () {
this.timeout(30_000);

if (!process.env.RUN_FORK_TESTS) {
it.skip('skipped — set RUN_FORK_TESTS=1 to run', () => {});
return;
}

// Use a public RPC if MAINNET_RPC_URL isn't set — publicnode worked
// in Task 1's discovery. Fork tests are read-only so any reliable
// public RPC works.
const rpcUrl =
process.env.MAINNET_RPC_URL ?? 'https://ethereum-rpc.publicnode.com';
const provider = new JsonRpcProvider(rpcUrl);

it('reads wstETH-Long pool state', async () => {
const pool = await getFxPool('wstETH-Long', provider);
expect(pool.name).to.equal('wstETH-Long');
expect(pool.collateralDecimals).to.equal(18n);
// Chai's greaterThan doesn't accept bigint; compare-then-assert.
expect(pool.debtCapacity > 0n).to.equal(true);
// Mainnet expectations from Task 1 discovery (May 2026):
expect(pool.borrowFeeRatio).to.equal(5_000_000n);
expect(pool.repayFeeRatio).to.equal(2_000_000n);
expect(pool.liquidationDebtRatio).to.equal(950_000_000_000_000_000n); // 0.95e18
expect(pool.liquidationBonusRatio).to.equal(40_000_000n); // 4e7
});

it('reads WBTC-Long pool state', async () => {
const pool = await getFxPool('WBTC-Long', provider);
expect(pool.name).to.equal('WBTC-Long');
expect(pool.collateralDecimals).to.equal(8n);
expect(pool.debtCapacity > 0n).to.equal(true);
expect(pool.liquidationDebtRatio).to.equal(950_000_000_000_000_000n);
});

it('returns name=undefined for custom pool refs', async () => {
const pool = await getFxPool(
{
address: '0x6Ecfa38FeE8a5277B91eFdA204c235814F0122E8', // wstETH-Long
collateralToken: '0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0',
collateralDecimals: 18n,
},
provider,
);
expect(pool.name).to.equal(undefined);
expect(pool.address.toLowerCase()).to.equal(
'0x6Ecfa38FeE8a5277B91eFdA204c235814F0122E8'.toLowerCase(),
);
});
});

describe('fx-position-reader — getFxPosition [requires RUN_FORK_TESTS=1]', function () {
this.timeout(30_000);

if (!process.env.RUN_FORK_TESTS) {
it.skip('skipped — set RUN_FORK_TESTS=1 to run', () => {});
return;
}

const rpcUrl =
process.env.MAINNET_RPC_URL ?? 'https://ethereum-rpc.publicnode.com';
const provider = new JsonRpcProvider(rpcUrl);

it('reads a known wstETH-Long position', async () => {
// positionId 1903 is the v0 calibration mint (tx 0x3f7c7c42... from
// README's calibration table). Verified on mainnet via
// `cast call <pool> 'getPosition(uint256)' 1903`: rawColls ≈ 5.86e13,
// rawDebts ≈ 3.58e16 — both > 0, so we can assert actual values
// rather than just shape. If this position is ever closed/burned the
// assertions degrade gracefully (call returns zeros, the > 0n
// assertions fail informatively).
const knownPositionId = 1903n;

const position = await getFxPosition(
knownPositionId,
'wstETH-Long',
provider,
);
expect(position.positionId).to.equal(knownPositionId);
expect(position.collateralDecimals).to.equal(18n);
expect(position.pool.address.toLowerCase()).to.equal(
'0x6Ecfa38FeE8a5277B91eFdA204c235814F0122E8'.toLowerCase(),
);
// Real value assertions — position 1903 was minted with nonzero
// collateral and debt and (as of Task 13) hasn't been closed.
// chai 4.x's .greaterThan typings don't accept bigint; compare-then-assert.
expect(position.rawColls > 0n).to.equal(true);
expect(position.rawDebts > 0n).to.equal(true);
expect(position.debt > 0n).to.equal(true);
expect(position.debt).to.equal(position.rawDebts); // debt is an alias for rawDebts
expect(position.debtRatio > 0n).to.equal(true);
expect(position.collateralAmount > 0n).to.equal(true);
});
});
Loading
Loading