DocumentationOpen App
On this page1. Connect

mF-ONE fork walkthrough

Get test tokens, open an mF-ONE leveraged position with 75,000 frxUSD at 4×, monitor it, and exit instantly on the supplied Gearbox Mainnet fork.

The steps below show the key calls. Use the complete script to run the flow, including approvals, preview checks, and transaction confirmation. It uses a fresh test wallet; fork resets erase its balances and transactions.

1. Connect

Use Node.js 22.18 or later and install the dependencies:

npm install @gearbox-protocol/sdk viem

Set the supplied RPC and opening parameters in the complete script:

TypeScript
const rpcUrl = "https://your-fork-rpc.example"; // Replace with the Gearbox fork RPC supplied to you const chainId = 1; const creditManager: Address = "0xBAdfE155662646A1A668c00276220B02FB7f1b23"; const depositAmount = "75000"; const leverage = 400n; const slippage = 5; const account = privateKeyToAccount(generatePrivateKey()); const owner = account.address;

400n means 4× leverage; 5 means 0.05% slippage. The script verifies the fork, loads its deployment configuration, and attaches sdk in onchain mode. All clients use the same fork execution. See Connect SDK for the regular connection flow.

2. Get tokens

Request test ETH and frxUSD from the fork's faucet:

TypeScript
const ethResponse = await fetch( `${baseUrl}/api/v1/forks/${fork.executionId}/eth-faucet?to=${owner}`, ); assert.ok(ethResponse.ok, `ETH faucet failed: ${ethResponse.status}`); const faucetResponse = await fetch( `${baseUrl}/api/v1/forks/${fork.executionId}/faucet?to=${owner}&midas=true`, ); assert.ok(faucetResponse.ok, `Token faucet failed: ${faucetResponse.status}`);

The script waits for the faucet transaction and checks the wallet's actual balances before opening.

3. Open

Select mF-ONE from the SDK's strategy list and prepare the opening parameters:

TypeScript
const params: OpenStrategyParams = { collateral: [{ token: depositToken.address, balance: parseUnits(depositAmount, depositToken.decimals), }], leverage, slippage, quotaReserve: 100, // Optional: 1% extra quota };

Then prepare the transaction:

TypeScript
let sim = await sdk.opportunities.prepare.openNewStrategy(strategy, params); if (!sim.ok) throw sim.error;

The complete script resolves the approval spender, approves exactly the 75,000 frxUSD deposit, then builds, previews, checks, and executes the opening. See Open for strategy and parameter details, and Build, preview, and check for transaction validation.

4. Monitor

Read the wallet's leveraged positions and select the account just opened:

TypeScript
const positions = await sdk.positions.onchain.list({ wallet: owner, filter: { kind: "strategy" }, });

Inspect its debt, collateral value, leverage, and health factor:

TypeScript
console.table({ creditAccount: position.creditAccount, debtFrxUSD: formatUnits(position.totalDebt.value, position.totalDebt.token.decimals), valueFrxUSD: formatUnits(position.totalValue.value, position.totalValue.token.decimals), leverage: position.leverage, healthFactor: position.healthFactor / 10_000, });

The script refreshes SDK state after execution. See Monitor for collateral balances, prices, liquidation thresholds, quotas, and the HF formula.

5. Exit instantly

Request a full withdrawal and choose the instant route:

TypeScript
const prepared = await sdk.opportunities.prepare.withdrawStrategy(position, { amount: MAX_UINT256, to: owner, slippage, }); if (!prepared.ok) throw prepared.error;
TypeScript
const route = "instant"; const selected = prepared.data[route]; if (!selected) throw prepared.data.errors[route] ?? new Error("Route unavailable"); const exitSim = { ok: true as const, data: selected };

The script builds, checks, and executes this route, then verifies zero debt, no collateral above SDK dust, and frxUSD returned to the wallet. The returned amount reflects fees, slippage, and interest. The Credit Account remains open after the economic exit. See Manage → Withdraw.

Complete script

Copy this into mfone.mts, replace the rpcUrl placeholder, and run:

Bash
node mfone.mts
Show complete runnable script
TypeScript
import assert from "node:assert/strict"; import { GearboxSDK, type OpenStrategyParams } from "@gearbox-protocol/sdk"; import { verifyTestnet } from "@gearbox-protocol/sdk/dev"; import { getNetworkType, MAX_UINT256 } from "@gearbox-protocol/sdk/onchain"; import { createPublicClient, createWalletClient, erc20Abi, formatUnits, http, parseUnits, type Address, type Hex, } from "viem"; import { generatePrivateKey, privateKeyToAccount } from "viem/accounts"; import { mainnet } from "viem/chains"; const baseUrl = "https://anvil.gearbox.foundation"; const rpcUrl = "https://your-fork-rpc.example"; // Replace with the Gearbox fork RPC supplied to you const chainId = 1; const creditManager: Address = "0xBAdfE155662646A1A668c00276220B02FB7f1b23"; const depositAmount = "75000"; const leverage = 400n; const slippage = 5; const account = privateKeyToAccount(generatePrivateKey()); const owner = account.address; console.log("Test wallet:", owner); const rpc = new URL(rpcUrl); assert.equal(rpc.origin, baseUrl, "Use the supplied Gearbox fork RPC"); const forkRef = rpc.pathname.match(/^\/rpc\/([^/]+)\/?$/)?.[1]; assert.ok(forkRef, "Expected a Gearbox /rpc/<fork> URL"); const statusResponse = await fetch(`${baseUrl}/api/v1/forks/${forkRef}`); assert.ok(statusResponse.ok, `Fork status failed: ${statusResponse.status}`); const fork: { executionId: string; isActive: boolean; paused: boolean; addressProvider: Address; marketConfigurators: Address[]; } = await statusResponse.json(); assert.ok(fork.isActive && !fork.paused, "Fork is not active"); const forkRpcUrl = `${baseUrl}/rpc/${encodeURIComponent(fork.executionId)}`; const transport = http(forkRpcUrl, { timeout: 60_000 }); const publicClient = createPublicClient({ chain: mainnet, transport }); const wallet = createWalletClient({ account, chain: mainnet, transport }); assert.ok( await verifyTestnet(publicClient, { executionId: fork.executionId }), "RPC does not match the expected Gearbox fork", ); const network = getNetworkType(chainId); const sdk = new GearboxSDK({ mode: "onchain", networks: [network], maxStateAgeSeconds: Infinity, // Refresh explicitly after test transactions onchain: { chains: { [network]: { rpcURLs: [forkRpcUrl], timeout: 60_000 } } }, attach: { perChain: { [network]: { addressProvider: fork.addressProvider, marketConfigurators: fork.marketConfigurators, ignoreUpdateablePrices: true, } } }, }); await sdk.attach(); const chain = sdk.onchain.chain(chainId); const strategies = await sdk.opportunities.list({ kind: "strategy" }); assert.ok(strategies.meta.chains.some(entry => entry.chainId === chainId && entry.status === "success", ), "Strategy read failed"); const strategy = strategies.data.find(entry => entry.kind === "strategy" && entry.chainId === chainId && entry.creditManager.toLowerCase() === creditManager.toLowerCase(), ); assert.ok(strategy?.kind === "strategy", "mF-ONE strategy is not available"); const selectedStrategy = await sdk.opportunities.getStrategy({ chainId: strategy.chainId, creditManager: strategy.creditManager, }); const depositToken = selectedStrategy.data.allowedDepositTokens.find(token => token.address.toLowerCase() === "0xcacd6fd266af91b8aed52accc382b4e165586e29", ); assert.ok(depositToken, "frxUSD is not an allowed deposit token"); const params: OpenStrategyParams = { collateral: [{ token: depositToken.address, balance: parseUnits(depositAmount, depositToken.decimals), }], leverage, slippage, quotaReserve: 100, // Optional: 1% extra quota }; const amount = params.collateral[0].balance; assert.ok(amount > 0n); const ethResponse = await fetch( `${baseUrl}/api/v1/forks/${fork.executionId}/eth-faucet?to=${owner}`, ); assert.ok(ethResponse.ok, `ETH faucet failed: ${ethResponse.status}`); const faucetResponse = await fetch( `${baseUrl}/api/v1/forks/${fork.executionId}/faucet?to=${owner}&midas=true`, ); assert.ok(faucetResponse.ok, `Token faucet failed: ${faucetResponse.status}`); const faucetResult: { claimed: boolean; tx: Hex } = await faucetResponse.json(); if (faucetResult.claimed) { const faucetReceipt = await publicClient.waitForTransactionReceipt({ hash: faucetResult.tx, }); assert.equal(faucetReceipt.status, "success", "Faucet transaction reverted"); } const fundedBalance = await publicClient.readContract({ address: depositToken.address, abi: erc20Abi, functionName: "balanceOf", args: [owner], }); console.log("Wallet frxUSD:", formatUnits(fundedBalance, depositToken.decimals)); assert.ok(await publicClient.getBalance({ address: owner }) > 0n, "No test ETH received"); assert.ok(fundedBalance >= amount, "Faucet did not supply enough frxUSD"); let sim = await sdk.opportunities.prepare.openNewStrategy(strategy, params); if (!sim.ok) throw sim.error; const spender = await sdk.onchain .chain(strategy.chainId) .accounts.getApprovalAddress({ creditManager: strategy.creditManager, borrower: owner, }); const approvalHash = await wallet.writeContract({ address: depositToken.address, abi: erc20Abi, functionName: "approve", args: [spender, amount], }); const approvalReceipt = await publicClient.waitForTransactionReceipt({ hash: approvalHash }); assert.equal(approvalReceipt.status, "success", "Approval reverted"); const approvalBlock = await publicClient.getBlock({ blockNumber: approvalReceipt.blockNumber }); await chain.syncState({ blockNumber: approvalBlock.number, timestamp: approvalBlock.timestamp, ignoreUpdateablePrices: true, }); assert.ok(chain.currentBlock >= approvalReceipt.blockNumber, "SDK refresh failed"); sim = await sdk.opportunities.prepare.openNewStrategy(strategy, params); if (!sim.ok) throw sim.error; const targetToken = params.targetToken ?? selectedStrategy.data.targetCollateral.address; const openTx = await sdk.opportunities.execute.buildTx({ kind: "open", chainId: strategy.chainId, creditManager: strategy.creditManager, wallet: owner, collateral: params.collateral, ethAmount: 0n, targetToken, sim, }); const openPreview = await sdk.preview.previewOperation({ chainId: strategy.chainId, to: openTx.to, calldata: openTx.callData, value: BigInt(openTx.value), sender: owner, }); if (!openPreview.ok) throw openPreview.error; console.dir(openPreview.data, { depth: null }); const openIssues = await sdk.preview.checkOperation( { chainId: strategy.chainId, preview: openPreview.data, sender: owner }, { minHealthFactor: 10_500, minSafeHealthFactor: 10_500 }, // HF >= 1.05 ); if (openIssues.length) throw new Error(openIssues.map(issue => issue.message).join("\n")); const openRequest = { to: openTx.to, data: openTx.callData, value: BigInt(openTx.value) }; await publicClient.call({ ...openRequest, account: owner }); const openHash = await wallet.sendTransaction(openRequest); const openReceipt = await publicClient.waitForTransactionReceipt({ hash: openHash, confirmations: 2, }); assert.equal(openReceipt.status, "success", "Opening reverted"); console.log("Opened:", openHash); const openBlock = await publicClient.getBlock({ blockNumber: openReceipt.blockNumber }); await chain.syncState({ blockNumber: openBlock.number, timestamp: openBlock.timestamp, ignoreUpdateablePrices: true, }); assert.ok(chain.currentBlock >= openReceipt.blockNumber, "SDK refresh failed"); const positions = await sdk.positions.onchain.list({ wallet: owner, filter: { kind: "strategy" }, }); assert.ok(positions.meta.chains.some(entry => entry.chainId === strategy.chainId && entry.status === "success", ), "Position read failed"); const position = positions.data.find(entry => entry.kind === "strategy" && entry.creditManager.toLowerCase() === strategy.creditManager.toLowerCase(), ); assert.ok(position?.kind === "strategy", "Opened position not found"); assert.ok(!position.error, position.error); assert.ok(position.totalDebt.value > 0n, "No outstanding debt after opening"); console.table({ creditAccount: position.creditAccount, debtFrxUSD: formatUnits(position.totalDebt.value, position.totalDebt.token.decimals), valueFrxUSD: formatUnits(position.totalValue.value, position.totalValue.token.decimals), leverage: position.leverage, healthFactor: position.healthFactor / 10_000, }); console.log("Read metadata:", positions.meta.chains); console.dir(position.collaterals, { depth: null }); const suite = chain.marketRegister.findCreditManager(position.creditManager); const collateralIndex = 0; const token = position.collaterals[collateralIndex].collateral.token.address; const price = suite.market.priceOracle.mainPrice(token); // USD per token, 8 decimals const lt = suite.creditManager.liquidationThresholds.get(token); // Basis points console.log({ price, lt }); const prepared = await sdk.opportunities.prepare.withdrawStrategy(position, { amount: MAX_UINT256, to: owner, slippage, }); if (!prepared.ok) throw prepared.error; const route = "instant"; const selected = prepared.data[route]; if (!selected) throw prepared.data.errors[route] ?? new Error("Route unavailable"); const exitSim = { ok: true as const, data: selected }; const exitTx = await sdk.opportunities.execute.buildTx({ kind: "account", chainId: position.chainId, creditAccount: position.creditAccount, wallet: owner, sim: exitSim, }); const exitPreview = await sdk.preview.previewOperation({ chainId: position.chainId, to: exitTx.to, calldata: exitTx.callData, value: BigInt(exitTx.value), sender: owner, }); if (!exitPreview.ok) throw exitPreview.error; assert.equal(exitPreview.data.operation, "CloseCreditAccount"); console.dir(exitPreview.data, { depth: null }); const exitIssues = await sdk.preview.checkOperation( { chainId: position.chainId, preview: exitPreview.data, sender: owner }, { minHealthFactor: 10_500, minSafeHealthFactor: 10_500 }, ); if (exitIssues.length) throw new Error(exitIssues.map(issue => issue.message).join("\n")); const balanceBeforeExit = await publicClient.readContract({ address: depositToken.address, abi: erc20Abi, functionName: "balanceOf", args: [owner], }); const exitRequest = { to: exitTx.to, data: exitTx.callData, value: BigInt(exitTx.value) }; await publicClient.call({ ...exitRequest, account: owner }); const exitHash = await wallet.sendTransaction(exitRequest); const exitReceipt = await publicClient.waitForTransactionReceipt({ hash: exitHash }); assert.equal(exitReceipt.status, "success", "Instant exit reverted"); console.log("Exited:", exitHash); const exitBlock = await publicClient.getBlock({ blockNumber: exitReceipt.blockNumber }); await chain.syncState({ blockNumber: exitBlock.number, timestamp: exitBlock.timestamp, ignoreUpdateablePrices: true, }); assert.ok(chain.currentBlock >= exitReceipt.blockNumber, "SDK refresh failed"); const after = await sdk.positions.onchain.list({ wallet: owner, filter: { kind: "strategy" }, }); assert.ok(after.meta.chains.some(entry => entry.chainId === strategy.chainId && entry.status === "success", ), "Final position read failed"); const exited = after.data.find(entry => entry.kind === "strategy" && entry.creditAccount === position.creditAccount, ); assert.ok(exited?.kind === "strategy" && !exited.error, "Cannot verify the Credit Account"); assert.equal(exited.totalDebt.value, 0n, "Debt remains"); assert.ok(exited.collaterals.every(entry => entry.collateral.value === 0n), "Collateral above the SDK dust threshold remains"); const balanceAfterExit = await publicClient.readContract({ address: depositToken.address, abi: erc20Abi, functionName: "balanceOf", args: [owner], }); const received = balanceAfterExit - balanceBeforeExit; assert.ok(received > 0n, "No frxUSD returned"); console.log("Returned frxUSD:", formatUnits(received, depositToken.decimals)); console.log("PASS: opened, monitored, and exited through the instant route");

Sources: fork faucet, SDK preparation, fork verification.