Wrap any Circle Agent Wallet with yield-bearing idle USDC. One call. Ten minutes.
import { wrapAgent } from '@floatrouter/sdk';
const flo = wrapAgent(myAgent, { strategy: 'balanced', vault: 'USYC' });
const safePay = flo.wrapPayment(executePayment);
// ↑ FLOAT auto-recalls from USYC if your wallet is short, then pays.That's it. The rest of this README is the long version.
npm install @floatrouter/sdk
# or
pnpm add @floatrouter/sdk
# or
yarn add @floatrouter/sdk⚠
@floatrouter/sdkis ESM-only. A freshnpm init -ydefaults to CommonJS, which will fail to import this package withTS1479/ERR_REQUIRE_ESM. Fix one of two ways:
- Set
"type": "module"in yourpackage.json(and use.jsfiles with ESimportsyntax), or- Use the
.mjsextension on the file that imports from the SDK.If you're using TypeScript, also set
"module": "NodeNext"and"moduleResolution": "NodeNext"intsconfig.json.
Peer requirements:
- Node.js 18+ (uses native
fetch) - Circle Wallet CLI, logged in. Install: see Circle's CLI docs. Verify with
circle whoami, then runcircle wallet login --testnetonce before using the SDK. - (Optional)
CIRCLE_API_KEYenv var if you want the HTTP submission path - (One-time)
circle terms accept— the SDK will never accept Circle's Terms on your behalf, by design
import { wrapAgent } from '@floatrouter/sdk';
const myAgent = {
walletId: 'a1b2c3d4-e5f6-…', // your Circle Agent Wallet ID
address: '0xYourAgentAddress',
chain: 'ARC-TESTNET',
};
const flo = wrapAgent(myAgent, { strategy: 'balanced', vault: 'USYC' });The returned object exposes four things:
| Method | What it does |
|---|---|
flo.park(amount) |
Manually park amount USDC into the USYC vault |
flo.withdraw(amount) |
Manually recall amount USDC back to your wallet |
flo.wrapPayment(fn) |
Wrap a payment fn so it auto-recalls from the vault if your wallet is short |
flo.gatewayRecall(params) |
Cross-chain recall via Circle Gateway (burn-attest-mint, ~500ms cross-chain) |
Plus flo.client and flo.adapter if you need the underlying primitives directly.
function wrapAgent(
agent: { walletId: string; address?: string; chain: string },
options?: {
strategy?: 'aggressive' | 'balanced' | 'conservative';
vault?: 'USYC';
}
): FloatedAgent;| Field | Required | Notes |
|---|---|---|
walletId |
yes | Circle Agent Wallet UUID |
address |
optional | 0x… address. If omitted, the SDK resolves it via the Circle CLI. |
chain |
yes | One of: 'ARC-TESTNET', 'ETHEREUM', 'BASE', 'AVALANCHE', 'ARBITRUM', 'OPTIMISM' |
The chain is mapped internally to the correct USDC contract address.
Determines your liquid reserve ratio — the share of total balance the SDK tries to keep on-wallet (vs. parked).
| Preset | Liquid reserve | Use case |
|---|---|---|
aggressive |
40% | High-frequency trading agents that act often |
balanced |
35% (default) | Most trading/portfolio agents |
conservative |
55% | Treasury / settlement agents that act rarely but in bulk |
Currently only 'USYC' is supported. Future vaults will land here.
The most useful method. Wraps any payment function you already have so it auto-recalls from the vault if your liquid balance is short.
executor is a function you write that performs the actual payment using whatever method you prefer — Circle CLI, ethers.js, viem, fetch to a custom endpoint, anything. FLOAT doesn't dictate how you move USDC; it just guarantees the wallet has the balance before your code runs.
import { wrapAgent } from '@floatrouter/sdk';
const flo = wrapAgent(myAgent, { strategy: 'balanced', vault: 'USYC' });
// 1. Define your existing payment logic. The shape is up to you —
// whatever args you need, returning whatever your downstream wants.
async function payVendor(amount: number, recipient: string) {
// ↓ Your real payment code goes here. Examples:
// - call your existing Circle CLI / ethers helper
// - hit a backend endpoint that handles the on-chain submit
// - use flo.adapter directly: await flo.adapter.transfer(recipient, amount)
console.log(`paying ${amount} USDC to ${recipient}`);
}
// 2. Wrap it. The wrapped fn has the same signature as the original.
const safePay = flo.wrapPayment(payVendor);
// 3. Call it like normal. FLOAT will recall from USYC first if needed.
await safePay(50, '0xRecipient');
// ↑ if wallet has < $50 liquid, FLOAT recalls the deficit from the
// vault, waits for confirmation, then runs `payVendor`.Recall is a single onchain instruction on Arc — typically <5 seconds end-to-end. If the vault has insufficient deposits to cover the deficit, the wrapped fn throws before attempting the payment, so you never get a half-completed state.
Manual control if you want to bypass the auto-routing logic.
await flo.park(100); // park $100 USDC into the vault
// …time passes, yield accrues…
await flo.withdraw(50); // recall $50 back to walletBoth confirm onchain before returning.
When your agent on Chain A needs USDC, but you've parked into a vault on Chain B, Circle Gateway lets you recall cross-chain in ~500ms (via off-chain attestation + same-block mint on the destination).
await flo.gatewayRecall({
amount: 100,
sourceChain: 'BASE',
sourceVaultAddress: '0xYourBaseVault',
sourceUsdcAddress: '0x036CbD53842c5426634e7929541eC2318f3dCF7e', // USDC on Base
sourceCLI: baseAgentAdapter, // a separate CircleCliAdapter for the source chain
});
// ↑ Burns USDC on Base, gets attestation from Gateway API,
// mints fresh USDC on your `agent.chain`. Settled in ~1 block.This is the 6-step burn-attest-mint flow:
- Build burn intent
- Sign EIP-712 typed data via Circle CLI on source chain
- POST signed intent to
https://gateway-api-testnet.circle.com/v1/transfer - Receive attestation + operator signature
- Submit
gatewayMint(bytes, bytes)on destination chain - Wait for confirmation
The strategy preset chooses your liquid reserve ratio — but the SDK has more knobs internally, surfaced via the FloatClient primitive if you need them:
import { FloatClient } from '@floatrouter/sdk';
const client = new FloatClient({
vaultAddress: '0xfAe6a9D5b0835ca7e9B090eCe0f57C14899BeDA6',
usdcAddress: '0x3600000000000000000000000000000000000000',
agentWalletId: myAgent.walletId,
circleCLI: new CircleCliAdapter({ walletId: myAgent.walletId, chain: 'ARC-TESTNET' }),
liquidReserve: 0.40,
// … more options
});A reference implementation of the full scoring engine — RLAIF Critic, audit-watcher, second-brain compile loop — lives in the companion float-yield-router repo, which uses this SDK and adds these inputs on top:
| Input | Effect |
|---|---|
| Agent state | EXECUTING / COOLDOWN agents never get parked |
| Market volatility | High vol pushes the parkability score down |
| Idle time | Longer idle pushes score up |
| Last action time | Cooldown windows after park / withdraw |
| Recent error count | Errors in the last hour penalize score |
parkThreshold (per strategy) |
Score must exceed this to PARK |
withdrawThreshold |
Score must drop below this to WITHDRAW |
If you want the full managed orchestrator on top of this SDK, the RLAIF Critic in the companion repo tunes these parameters per-agent in real time.
See examples/wrapAgentDemo.ts for the canonical 10-line integration.
For end-to-end onchain tests (you'll need a logged-in Circle CLI session + an Arc Testnet wallet):
test-e2e-arc.ts— full park + withdraw + payment cycle against Arc Testnettest-e2e-vault.ts— vault-only cycle (no payment), proves approve → park → recall
To run them locally, clone this repo:
git clone https://github.com/ronkenx9/floatrouter-sdk.git
cd floatrouter-sdk
npm install && npm run build
node test-e2e-arc.jsThe Circle CLI requires interactive Terms acceptance. Run circle terms accept once.
The SDK will never accept Terms on your behalf — that's an intentional safety boundary.
Re-run circle wallet login --testnet.
Your in-memory parkedBalance cache is stale (another agent on the same wallet may have already withdrawn). The dashboard's adapter does a pre-flight RPC read to avoid this; if you're using the raw SDK, do the same:
import { rpcCall } from '@floatrouter/sdk';
// eth_call vault.deposits(agent) before each withdrawArc Testnet is fast — most TXs confirm in <5s. If you hit PENDING > 25s, the SDK auto-calls circle transaction accelerate. If that fails, check the Circle CLI is logged in and your wallet has gas.
You're calling the Circle CLI too frequently. The dashboard's adapter has a per-wallet rate limiter; if you're calling the SDK directly from multiple agents sharing one wallet, serialize them or add a await sleep(600) between calls.
Pre-1.0. APIs may change between minor versions.
MIT — see LICENSE.