From a807d997d0eb6f196c718881215427874c43a419 Mon Sep 17 00:00:00 2001 From: Alex Christian Date: Mon, 4 May 2026 13:33:27 -0700 Subject: [PATCH 1/2] feat: add Floe credit action provider MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds FloeActionProvider — working capital for AI agents on Base. Financial independence is the precursor to agent autonomy. Long-running agents can't do anything meaningful without their own fundable balance sheet. Floe gives them one. 3,000+ secured working capital lines issued. Zero defaults. 8 actions: Read: getMarkets, checkStatus, getBalance, checkHealth Write: instantBorrow, repay, grantDelegation, x402Fetch Deposit USDC, borrow up to 95% as a credit line. Fixed rates, per-loan isolated escrow, gas-free for delegated agents. Fund with a bank account or card — no crypto experience needed. Built-in x402 payment proxy. Includes: - Full test suite with mocked wallet and API - Example agent (typescript/examples/floe-credit-agent/) - Provider README Network: Base Mainnet Contract: 0x17946cD3e180f82e632805e5549EC913330Bb175 --- .../src/action-providers/floe/README.md | 55 ++ .../src/action-providers/floe/constants.ts | 86 +++ .../floe/floeActionProvider.test.ts | 312 ++++++++++ .../floe/floeActionProvider.ts | 583 ++++++++++++++++++ .../src/action-providers/floe/index.ts | 1 + .../src/action-providers/floe/schemas.ts | 113 ++++ .../agentkit/src/action-providers/index.ts | 1 + .../examples/floe-credit-agent/.env.example | 3 + .../examples/floe-credit-agent/README.md | 42 ++ .../examples/floe-credit-agent/index.ts | 69 +++ .../examples/floe-credit-agent/package.json | 19 + 11 files changed, 1284 insertions(+) create mode 100644 typescript/agentkit/src/action-providers/floe/README.md create mode 100644 typescript/agentkit/src/action-providers/floe/constants.ts create mode 100644 typescript/agentkit/src/action-providers/floe/floeActionProvider.test.ts create mode 100644 typescript/agentkit/src/action-providers/floe/floeActionProvider.ts create mode 100644 typescript/agentkit/src/action-providers/floe/index.ts create mode 100644 typescript/agentkit/src/action-providers/floe/schemas.ts create mode 100644 typescript/examples/floe-credit-agent/.env.example create mode 100644 typescript/examples/floe-credit-agent/README.md create mode 100644 typescript/examples/floe-credit-agent/index.ts create mode 100644 typescript/examples/floe-credit-agent/package.json diff --git a/typescript/agentkit/src/action-providers/floe/README.md b/typescript/agentkit/src/action-providers/floe/README.md new file mode 100644 index 000000000..19d6fb9ed --- /dev/null +++ b/typescript/agentkit/src/action-providers/floe/README.md @@ -0,0 +1,55 @@ +# Floe Working Capital Provider + +Financial independence is the precursor to agent autonomy. Long-running agents can't do anything meaningful without their own fundable balance sheet. Floe gives them one. + +**3,000+ secured working capital lines issued. Zero defaults.** + +Deposit USDC, borrow up to 95% as a credit line. Fixed rates, per-loan isolated escrow, gas-free. Fund with a bank account or card — no crypto experience needed. + +## Actions + +| Action | Type | Description | +|--------|------|-------------| +| `getMarkets` | Read | List available lending markets | +| `instantBorrow` | Write | Deposit USDC, borrow up to 95% working capital — auto-selects best lender | +| `repay` | Write | Repay a loan (deposit returns automatically) | +| `checkStatus` | Read | Loan health, balance, accrued interest, time to expiry | +| `getBalance` | Read | Credit balance for facilitator-delegated agents | +| `checkHealth` | Read | On-chain LTV and liquidation risk | +| `grantDelegation` | Write | Delegate credit to Floe facilitator for gas-free x402 payments (one-time) | +| `x402Fetch` | Write | Call any x402 API — Floe handles payment automatically | + +## Setup + +```typescript +import { AgentKit } from "@coinbase/agentkit"; +import { getVercelAITools } from "@coinbase/agentkit-vercel-ai-sdk"; +import { FloeActionProvider } from "@coinbase/agentkit"; + +const agentkit = await AgentKit.from({ + walletProvider, + actionProviders: [new FloeActionProvider()], +}); + +// Use with Vercel AI SDK, LangChain, or any framework +const tools = getVercelAITools(agentkit); +``` + +## Why agents need credit + +100M+ x402 machine payments since May 2025. 100,000+ agents with onchain identity. Zero credit outstanding to any of them. + +Every economy runs on credit. Agents don't have FICO — but they have something better: deterministic cashflows and verifiable execution history. Floe is the credit bureau and capital rail for AI agents. + +## Network + +- **Base Mainnet** — Coinbase's L2, built for payments +- **Contract:** [`0x17946cD3e180f82e632805e5549EC913330Bb175`](https://basescan.org/address/0x17946cD3e180f82e632805e5549EC913330Bb175) + +## Links + +- [Docs](https://floe-labs.gitbook.io/docs) +- [Bank Account → First API Call](https://floe-labs.gitbook.io/docs/agents/fiat-to-x402) — fund with fiat, no crypto needed +- [Full npm package (45 actions)](https://www.npmjs.com/package/floe-agent) +- [Dashboard](https://dev-dashboard.floelabs.xyz) +- [Website](https://floelabs.xyz) diff --git a/typescript/agentkit/src/action-providers/floe/constants.ts b/typescript/agentkit/src/action-providers/floe/constants.ts new file mode 100644 index 000000000..8aaa2ffed --- /dev/null +++ b/typescript/agentkit/src/action-providers/floe/constants.ts @@ -0,0 +1,86 @@ +import { type Address } from "viem"; + +export const SUPPORTED_NETWORKS = ["base-mainnet"]; + +export const LENDING_MATCHER_ADDRESSES: Record = { + "base-mainnet": "0x17946cD3e180f82e632805e5549EC913330Bb175", +}; + +export const FACILITATOR_ADDRESSES: Record = { + "base-mainnet": "0x58EDdE022FFDAD3Fb0Fb0E7D51eb05AaF66a31f1", +}; + +export const FACILITATOR_API = "https://credit-api.floelabs.xyz"; + +export const TOKEN_ADDRESSES: Record> = { + "base-mainnet": { + usdc: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", + weth: "0x4200000000000000000000000000000000000006", + cbbtc: "0xcbB7C0000aB88B473b1f5aFd9ef808440eed33Bf", + }, +}; + +export const USDC_DECIMALS = 6; + +export const LENDING_MATCHER_ABI = [ + { + inputs: [ + { name: "operator", type: "address" }, + { name: "borrowLimit", type: "uint256" }, + { name: "maxRateBps", type: "uint256" }, + { name: "expiry", type: "uint256" }, + { name: "onBehalfOfRestriction", type: "address" }, + ], + name: "setOperator", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { name: "agent", type: "address" }, + { name: "operator", type: "address" }, + ], + name: "getOperatorPermission", + outputs: [ + { name: "approved", type: "bool" }, + { name: "borrowLimit", type: "uint256" }, + { name: "borrowed", type: "uint256" }, + { name: "maxRateBps", type: "uint256" }, + { name: "expiry", type: "uint256" }, + { name: "onBehalfOfRestriction", type: "address" }, + ], + stateMutability: "view", + type: "function", + }, +] as const; + +export const ERC20_ABI = [ + { + inputs: [ + { name: "spender", type: "address" }, + { name: "amount", type: "uint256" }, + ], + name: "approve", + outputs: [{ name: "", type: "bool" }], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [{ name: "account", type: "address" }], + name: "balanceOf", + outputs: [{ name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { name: "owner", type: "address" }, + { name: "spender", type: "address" }, + ], + name: "allowance", + outputs: [{ name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, +] as const; diff --git a/typescript/agentkit/src/action-providers/floe/floeActionProvider.test.ts b/typescript/agentkit/src/action-providers/floe/floeActionProvider.test.ts new file mode 100644 index 000000000..0e79cc50f --- /dev/null +++ b/typescript/agentkit/src/action-providers/floe/floeActionProvider.test.ts @@ -0,0 +1,312 @@ +import { type Hex } from "viem"; +import { EvmWalletProvider } from "../../wallet-providers/evmWalletProvider"; +import { FloeActionProvider } from "./floeActionProvider"; +import { LENDING_MATCHER_ADDRESSES } from "./constants"; +import { Network } from "../../network"; + +describe("Floe Action Provider", () => { + const actionProvider = new FloeActionProvider("https://mock-api.test"); + let mockWallet: jest.Mocked; + + const MOCK_NETWORK: Network = { protocolFamily: "evm", networkId: "base-mainnet" }; + const MOCK_ADDRESS = "0x1234567890abcdef1234567890abcdef12345678"; + const MOCK_TX_HASH = "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890" as Hex; + const MOCK_RECEIPT = { status: 1, blockNumber: 123456 }; + const MOCK_SIGNATURE = "0xmocksignature"; + + beforeEach(() => { + mockWallet = { + getAddress: jest.fn().mockResolvedValue(MOCK_ADDRESS), + getNetwork: jest.fn().mockReturnValue(MOCK_NETWORK), + sendTransaction: jest.fn().mockResolvedValue(MOCK_TX_HASH), + waitForTransactionReceipt: jest.fn().mockResolvedValue(MOCK_RECEIPT), + signMessage: jest.fn().mockResolvedValue(MOCK_SIGNATURE), + readContract: jest.fn(), + } as unknown as jest.Mocked; + + global.fetch = jest.fn(); + jest.clearAllMocks(); + }); + + describe("supportsNetwork", () => { + it("returns true for base-mainnet", () => { + expect(actionProvider.supportsNetwork(MOCK_NETWORK)).toBe(true); + }); + + it("returns false for base-sepolia", () => { + expect( + actionProvider.supportsNetwork({ protocolFamily: "evm", networkId: "base-sepolia" }), + ).toBe(false); + }); + + it("returns false for unsupported networks", () => { + expect( + actionProvider.supportsNetwork({ protocolFamily: "evm", networkId: "ethereum-mainnet" }), + ).toBe(false); + }); + + it("returns false for non-evm networks", () => { + expect( + actionProvider.supportsNetwork({ protocolFamily: "solana", networkId: "base-mainnet" }), + ).toBe(false); + }); + }); + + describe("getMarkets", () => { + it("returns formatted market data", async () => { + (global.fetch as jest.Mock).mockResolvedValueOnce({ + ok: true, + json: async () => ({ + markets: [ + { collateralSymbol: "USDC", loanSymbol: "USDC" }, + ], + }), + }); + + const result = await actionProvider.getMarkets(mockWallet, {}); + expect(result).toContain("Floe Lending Markets"); + expect(result).toContain("USDC/USDC"); + }); + + it("handles empty markets", async () => { + (global.fetch as jest.Mock).mockResolvedValueOnce({ + ok: true, + json: async () => ({ markets: [] }), + }); + + const result = await actionProvider.getMarkets(mockWallet, {}); + expect(result).toContain("No active markets"); + }); + + it("handles API errors", async () => { + (global.fetch as jest.Mock).mockResolvedValueOnce({ + ok: false, + status: 500, + }); + + const result = await actionProvider.getMarkets(mockWallet, {}); + expect(result).toContain("Error fetching markets"); + }); + }); + + describe("instantBorrow", () => { + it("calls the correct API with auth headers", async () => { + (global.fetch as jest.Mock).mockResolvedValueOnce({ + ok: true, + json: async () => ({ loanId: "42" }), + }); + + const result = await actionProvider.instantBorrow(mockWallet, { + borrowAmount: "1000", + collateralAmount: "10000", + maxInterestRateBps: "800", + duration: "1209600", + }); + + expect(global.fetch).toHaveBeenCalledWith( + "https://mock-api.test/v1/credit/instant-borrow", + expect.objectContaining({ method: "POST" }), + ); + expect(result).toContain("Loan Created"); + expect(result).toContain("1000 USDC"); + expect(result).toContain("10000 USDC"); + }); + + it("handles no liquidity", async () => { + (global.fetch as jest.Mock).mockResolvedValueOnce({ + ok: false, + status: 404, + json: async () => ({ error: "no_liquidity" }), + }); + + const result = await actionProvider.instantBorrow(mockWallet, { + borrowAmount: "1000", + collateralAmount: "10000", + maxInterestRateBps: "800", + duration: "1209600", + }); + + expect(result).toContain("No lenders available"); + }); + }); + + describe("grantDelegation", () => { + it("encodes setOperator correctly and sends transaction", async () => { + const result = await actionProvider.grantDelegation(mockWallet, { + facilitatorAddress: "0x58EDdE022FFDAD3Fb0Fb0E7D51eb05AaF66a31f1", + borrowLimit: "10000", + maxRateBps: "1500", + expiryDays: "90", + }); + + expect(mockWallet.sendTransaction).toHaveBeenCalledWith( + expect.objectContaining({ + to: LENDING_MATCHER_ADDRESSES["base-mainnet"], + }), + ); + expect(result).toContain("Credit Delegation Granted"); + expect(result).toContain("10000 USDC"); + expect(result).toContain("15.00% APR"); + expect(result).toContain("90 days"); + }); + + it("rejects unsupported networks", async () => { + mockWallet.getNetwork.mockReturnValue({ + protocolFamily: "evm", + networkId: "ethereum-mainnet", + } as Network); + + const result = await actionProvider.grantDelegation(mockWallet, { + facilitatorAddress: "0x58EDdE022FFDAD3Fb0Fb0E7D51eb05AaF66a31f1", + borrowLimit: "10000", + maxRateBps: "1500", + expiryDays: "90", + }); + + expect(result).toContain("not supported"); + }); + }); + + describe("checkStatus", () => { + it("returns formatted loan status", async () => { + (global.fetch as jest.Mock).mockResolvedValueOnce({ + ok: true, + json: async () => ({ + remainingPrincipal: "500000000", + accruedInterest: "12500000", + interestRateBps: 800, + status: "active", + timeToExpiry: "10 days", + }), + }); + + const result = await actionProvider.checkStatus(mockWallet, { loanId: "42" }); + expect(result).toContain("Loan #42 Status"); + expect(result).toContain("8.00% APR"); + expect(result).toContain("active"); + }); + }); + + describe("repay", () => { + it("repays a loan successfully", async () => { + (global.fetch as jest.Mock).mockResolvedValueOnce({ + ok: true, + json: async () => ({}), + }); + + const result = await actionProvider.repay(mockWallet, { loanId: "42" }); + expect(result).toContain("Loan Repaid"); + expect(result).toContain("42"); + expect(result).toContain("Returned to your wallet"); + }); + + it("handles repay failure", async () => { + (global.fetch as jest.Mock).mockResolvedValueOnce({ + ok: false, + status: 400, + json: async () => ({ error: "loan_not_found" }), + }); + + const result = await actionProvider.repay(mockWallet, { loanId: "999" }); + expect(result).toContain("Repay failed"); + }); + }); + + describe("getBalance", () => { + it("returns formatted credit balance", async () => { + (global.fetch as jest.Mock).mockResolvedValueOnce({ + ok: true, + json: async () => ({ + creditLimit: "10000000000", + creditUsed: "3200000000", + creditAvailable: "6800000000", + activeLoans: 1, + delegationActive: true, + }), + }); + + const result = await actionProvider.getBalance(mockWallet, {}); + expect(result).toContain("Credit Balance"); + expect(result).toContain("Active"); + }); + + it("handles balance check failure", async () => { + (global.fetch as jest.Mock).mockResolvedValueOnce({ + ok: false, + status: 401, + json: async () => ({ error: "unauthorized" }), + }); + + const result = await actionProvider.getBalance(mockWallet, {}); + expect(result).toContain("Balance check failed"); + }); + }); + + describe("checkHealth", () => { + it("returns health with buffer when LTV data present", async () => { + (global.fetch as jest.Mock).mockResolvedValueOnce({ + ok: true, + json: async () => ({ + status: "active", + currentLtvBps: 8000, + liquidationLtvBps: 8800, + }), + }); + + const result = await actionProvider.checkHealth(mockWallet, { loanId: "42" }); + expect(result).toContain("Loan #42 Health"); + expect(result).toContain("🟢"); + expect(result).toContain("80.0%"); + expect(result).toContain("Buffer"); + }); + + it("returns health without buffer when LTV data absent", async () => { + (global.fetch as jest.Mock).mockResolvedValueOnce({ + ok: true, + json: async () => ({ + status: "overdue", + }), + }); + + const result = await actionProvider.checkHealth(mockWallet, { loanId: "42" }); + expect(result).toContain("🔴"); + expect(result).toContain("overdue"); + expect(result).not.toContain("Buffer"); + }); + }); + + describe("x402Fetch", () => { + it("calls proxy endpoint with auth headers", async () => { + (global.fetch as jest.Mock).mockResolvedValueOnce({ + ok: true, + status: 200, + headers: new Map([["payment-response", "0xtxhash"]]), + text: async () => '{"data": "premium content"}', + }); + + const result = await actionProvider.x402Fetch(mockWallet, { + url: "https://api.example.com/data", + }); + + expect(global.fetch).toHaveBeenCalledWith( + "https://mock-api.test/v1/proxy/fetch", + expect.objectContaining({ method: "POST" }), + ); + expect(result).toContain("x402 Response"); + }); + + it("handles insufficient balance", async () => { + (global.fetch as jest.Mock).mockResolvedValueOnce({ + ok: false, + status: 402, + json: async () => ({ error: "insufficient_balance", available: "100", required: "500" }), + }); + + const result = await actionProvider.x402Fetch(mockWallet, { + url: "https://api.example.com/data", + }); + + expect(result).toContain("Insufficient credit balance"); + }); + }); +}); diff --git a/typescript/agentkit/src/action-providers/floe/floeActionProvider.ts b/typescript/agentkit/src/action-providers/floe/floeActionProvider.ts new file mode 100644 index 000000000..66b8bbfd5 --- /dev/null +++ b/typescript/agentkit/src/action-providers/floe/floeActionProvider.ts @@ -0,0 +1,583 @@ +import { z } from "zod"; +import { encodeFunctionData, parseUnits, type Address } from "viem"; + +import { ActionProvider } from "../actionProvider"; +import { EvmWalletProvider } from "../../wallet-providers"; +import { CreateAction } from "../actionDecorator"; +import { Network } from "../../network"; +import { + SUPPORTED_NETWORKS, + LENDING_MATCHER_ADDRESSES, + LENDING_MATCHER_ABI, + FACILITATOR_API, + FACILITATOR_ADDRESSES, + USDC_DECIMALS, +} from "./constants"; +import { + FloeGetMarketsSchema, + FloeInstantBorrowSchema, + FloeRepaySchema, + FloeCheckStatusSchema, + FloeGetBalanceSchema, + FloeCheckHealthSchema, + FloeGrantDelegationSchema, + FloeFetchSchema, +} from "./schemas"; + +/** + * FloeActionProvider provides working capital for AI agents on Base via the Floe protocol. + * + * Unlike variable-rate pool-based protocols (Compound, Morpho, Aave), Floe uses + * intent-based matching with per-loan isolated escrow. Rates are fixed at match + * time and never change. Agents can also delegate credit to the Floe facilitator + * for gas-free x402 API payments. + */ +export class FloeActionProvider extends ActionProvider { + private readonly facilitatorApi: string; + + /** + * Constructs a new FloeActionProvider instance. + * + * @param facilitatorApi - Override the default Floe Credit API URL. + */ + constructor(facilitatorApi: string = FACILITATOR_API) { + super("floe", []); + this.facilitatorApi = facilitatorApi; + } + + /** + * Checks if the given network is supported by this provider. + * + * @param network - The network to check. + * @returns true if the network is supported. + */ + supportsNetwork(network: Network): boolean { + return network.protocolFamily === "evm" && SUPPORTED_NETWORKS.includes(network.networkId ?? ""); + } + + /** + * Builds auth headers for the Floe Credit API. + * + * @param wallet - The wallet provider to sign the auth message. + * @returns Headers object with wallet address, signature, and timestamp. + */ + private async buildAuthHeaders( + wallet: EvmWalletProvider, + ): Promise> { + const address = await wallet.getAddress(); + const timestamp = Math.floor(Date.now() / 1000).toString(); + const message = `Floe Credit API\nTimestamp: ${timestamp}`; + const signature = await wallet.signMessage(message); + return { + "Content-Type": "application/json", + "X-Wallet-Address": address, + "X-Signature": signature, + "X-Timestamp": timestamp, + }; + } + + /** + * Helper to call the Floe Credit API. + * + * @param path - API path (e.g. "/v1/markets"). + * @param options - Optional method, headers, and body. + * @returns The fetch Response object. + */ + private async apiCall( + path: string, + options: { method?: string; headers?: Record; body?: unknown } = {}, + ): Promise { + const url = `${this.facilitatorApi}${path}`; + const res = await fetch(url, { + method: options.method ?? "GET", + headers: options.headers ?? { "Content-Type": "application/json" }, + body: options.body ? JSON.stringify(options.body) : undefined, + }); + return res; + } + + // ── Read Actions ────────────────────────────────────────────────────── + + /** + * Lists available Floe lending markets with current rates and liquidity. + * + * @param wallet - The wallet instance. + * @param args - Empty object. + * @returns A formatted markdown table of available markets. + */ + @CreateAction({ + name: "getMarkets", + description: ` +This tool lists available lending markets on the Floe protocol. +Returns available market pairs and collateral types. +Floe offers fixed-rate, fixed-term P2P loans — unlike variable-rate pools. +No parameters needed. + `, + schema: FloeGetMarketsSchema, + }) + async getMarkets( + _wallet: EvmWalletProvider, + _args: z.infer, + ): Promise { + try { + const res = await this.apiCall("/v1/markets"); + if (!res.ok) { + return `Error fetching markets: HTTP ${res.status}`; + } + const data = await res.json(); + const markets = data.markets || []; + if (markets.length === 0) { + return "No active markets found."; + } + + const lines = ["## Floe Lending Markets\n"]; + lines.push("| Market | Collateral | Loan Token |"); + lines.push("|--------|------------|------------|"); + for (const m of markets) { + lines.push(`| ${m.collateralSymbol}/${m.loanSymbol} | ${m.collateralSymbol} | ${m.loanSymbol} |`); + } + lines.push( + "\nRates are set by P2P matching — check available offers with `instantBorrow`.", + ); + return lines.join("\n"); + } catch (error) { + return `Error fetching markets: ${error}`; + } + } + + // ── Write Actions ───────────────────────────────────────────────────── + + /** + * Borrows USDC instantly by auto-selecting the best available lender. + * + * @param wallet - The wallet instance to sign transactions. + * @param args - Borrow parameters: amount, collateral, max rate, duration. + * @returns A message with loan details or an error. + */ + @CreateAction({ + name: "instantBorrow", + description: ` +This tool borrows USDC from Floe by auto-selecting the best available lender. +It takes: +- borrowAmount: USDC to borrow in human-readable format (e.g. '1000') +- collateralAmount: Collateral in human-readable USDC (e.g. '10000' for $10K deposit) +- maxInterestRateBps: Maximum rate in basis points (e.g. '800' for 8% APR) +- duration: Loan duration in seconds (e.g. '1209600' for 14 days) +- marketId: Optional market ID (defaults to USDC/USDC) + +For the USDC/USDC market, there is no price-volatility risk — same token +in and out, up to 95% LTV. The rate is FIXED at match time. Collateral +returns automatically on repayment. + `, + schema: FloeInstantBorrowSchema, + }) + async instantBorrow( + wallet: EvmWalletProvider, + args: z.infer, + ): Promise { + try { + const headers = await this.buildAuthHeaders(wallet); + const borrowRaw = parseUnits(args.borrowAmount, USDC_DECIMALS).toString(); + const collateralRaw = parseUnits(args.collateralAmount, USDC_DECIMALS).toString(); + + const res = await this.apiCall("/v1/credit/instant-borrow", { + method: "POST", + headers, + body: { + borrowAmount: borrowRaw, + collateralAmount: collateralRaw, + maxInterestRateBps: parseInt(args.maxInterestRateBps, 10), + duration: parseInt(args.duration, 10), + marketId: args.marketId, + }, + }); + + if (!res.ok) { + const err = await res.json().catch(() => ({ error: res.statusText })); + if (res.status === 404) { + return `No lenders available matching your terms. Try increasing maxInterestRateBps or reducing borrowAmount.`; + } + return `Borrow failed: ${err.error || err.message || res.statusText}`; + } + + const data = await res.json(); + const ratePct = (parseInt(args.maxInterestRateBps, 10) / 100).toFixed(2); + const durationDays = Math.round(parseInt(args.duration, 10) / 86400); + + return [ + "## Loan Created\n", + `- **Loan ID**: ${data.loanId}`, + `- **Borrowed**: ${args.borrowAmount} USDC`, + `- **Collateral**: ${args.collateralAmount} USDC`, + `- **Rate**: ≤${ratePct}% APR (fixed)`, + `- **Duration**: ${durationDays} days`, + "", + "Collateral returns automatically on repayment.", + ].join("\n"); + } catch (error) { + return `Error borrowing: ${error}`; + } + } + + /** + * Repays a Floe loan. Collateral returns automatically. + * + * @param wallet - The wallet instance to sign transactions. + * @param args - The loan ID and optional slippage tolerance. + * @returns A confirmation message or error. + */ + @CreateAction({ + name: "repay", + description: ` +This tool repays a Floe loan. After repayment, collateral returns automatically. +It takes: +- loanId: The on-chain loan ID +- slippageBps: Optional slippage tolerance (default 500 = 5%) + `, + schema: FloeRepaySchema, + }) + async repay( + wallet: EvmWalletProvider, + args: z.infer, + ): Promise { + try { + const headers = await this.buildAuthHeaders(wallet); + const res = await this.apiCall("/v1/credit/repay", { + method: "POST", + headers, + body: { + loanId: args.loanId, + slippageBps: parseInt(args.slippageBps ?? "500", 10), + }, + }); + + if (!res.ok) { + const err = await res.json().catch(() => ({ error: res.statusText })); + return `Repay failed: ${err.error || err.message || res.statusText}`; + } + + return [ + "## Loan Repaid\n", + `- **Loan ID**: ${args.loanId}`, + "- **Status**: Fully repaid", + "- **Collateral**: Returned to your wallet", + ].join("\n"); + } catch (error) { + return `Error repaying: ${error}`; + } + } + + /** + * Checks the status of a Floe loan — health, balance, time to expiry. + * + * @param wallet - The wallet instance. + * @param args - The loan ID. + * @returns A formatted status report. + */ + @CreateAction({ + name: "checkStatus", + description: ` +This tool checks the status of a Floe loan: remaining principal, +accrued interest, health ratio, time to maturity, and early repayment costs. +It takes: +- loanId: The on-chain loan ID + `, + schema: FloeCheckStatusSchema, + }) + async checkStatus( + wallet: EvmWalletProvider, + args: z.infer, + ): Promise { + try { + const headers = await this.buildAuthHeaders(wallet); + const res = await this.apiCall(`/v1/credit/status/${args.loanId}`, { headers }); + + if (!res.ok) { + const err = await res.json().catch(() => ({ error: res.statusText })); + return `Status check failed: ${err.error || err.message || res.statusText}`; + } + + const d = await res.json(); + return [ + `## Loan #${args.loanId} Status\n`, + `- **Principal remaining**: ${d.remainingPrincipal ?? "N/A"}`, + `- **Accrued interest**: ${d.accruedInterest ?? "N/A"}`, + `- **Rate**: ${d.interestRateBps ? `${(d.interestRateBps / 100).toFixed(2)}% APR` : "N/A"}`, + `- **Health**: ${d.status ?? "N/A"}`, + `- **Time remaining**: ${d.timeToExpiry ?? "N/A"}`, + ].join("\n"); + } catch (error) { + return `Error checking status: ${error}`; + } + } + + /** + * Gets the agent's credit balance from the Floe facilitator. + * + * @param wallet - The wallet instance. + * @param args - Empty object. + * @returns Credit balance details. + */ + @CreateAction({ + name: "getBalance", + description: ` +This tool checks the agent's credit balance on the Floe facilitator. +Returns: credit limit, credit used, credit available, active loans. +Only applicable for agents using the Floe x402 facilitator (credit delegation). +No parameters needed. + `, + schema: FloeGetBalanceSchema, + }) + async getBalance( + wallet: EvmWalletProvider, + _args: z.infer, + ): Promise { + try { + const headers = await this.buildAuthHeaders(wallet); + const res = await this.apiCall("/v1/agents/balance", { headers }); + + if (!res.ok) { + const err = await res.json().catch(() => ({ error: res.statusText })); + return `Balance check failed: ${err.error || err.message || res.statusText}`; + } + + const d = await res.json(); + return [ + "## Credit Balance\n", + `- **Credit limit**: ${d.creditLimit ?? "N/A"}`, + `- **Credit used**: ${d.creditUsed ?? "N/A"}`, + `- **Available**: ${d.creditAvailable ?? "N/A"}`, + `- **Active loans**: ${d.activeLoans ?? 0}`, + `- **Delegation**: ${d.delegationActive ? "Active" : "Inactive"}`, + ].join("\n"); + } catch (error) { + return `Error checking balance: ${error}`; + } + } + + /** + * Checks the health of a Floe loan via the facilitator API. + * + * @param wallet - The wallet instance. + * @param args - The loan ID. + * @returns Health assessment with status, LTV, and buffer. + */ + @CreateAction({ + name: "checkHealth", + description: ` +This tool checks the health of a Floe loan — current LTV, +liquidation threshold, and safety buffer. +It takes: +- loanId: The on-chain loan ID + `, + schema: FloeCheckHealthSchema, + }) + async checkHealth( + wallet: EvmWalletProvider, + args: z.infer, + ): Promise { + try { + const headers = await this.buildAuthHeaders(wallet); + const res = await this.apiCall(`/v1/credit/status/${args.loanId}`, { headers }); + + if (!res.ok) { + const err = await res.json().catch(() => ({ error: res.statusText })); + return `Health check failed: ${err.error || err.message || res.statusText}`; + } + + const d = await res.json(); + const status = d.status ?? "unknown"; + const emoji = status === "active" ? "🟢" : status === "overdue" ? "🔴" : "🟡"; + + return [ + `## Loan #${args.loanId} Health\n`, + `- **Status**: ${emoji} ${status}`, + `- **Current LTV**: ${d.currentLtvBps ? `${(d.currentLtvBps / 100).toFixed(1)}%` : "N/A"}`, + `- **Liquidation LTV**: ${d.liquidationLtvBps ? `${(d.liquidationLtvBps / 100).toFixed(1)}%` : "N/A"}`, + d.currentLtvBps && d.liquidationLtvBps + ? `- **Buffer**: ${((d.liquidationLtvBps - d.currentLtvBps) / 100).toFixed(1)}%` + : "", + ] + .filter(Boolean) + .join("\n"); + } catch (error) { + return `Error checking health: ${error}`; + } + } + + /** + * Grants credit delegation to the Floe facilitator via on-chain setOperator. + * This is a one-time setup that allows the facilitator to borrow on the + * agent's behalf for gas-free x402 API payments. + * + * @param wallet - The wallet instance to sign the transaction. + * @param args - Delegation parameters. + * @returns Confirmation message or error. + */ + @CreateAction({ + name: "grantDelegation", + description: ` +This tool grants credit delegation to the Floe facilitator by calling +setOperator on the lending contract. This is a ONE-TIME setup that +allows the facilitator to borrow USDC on your behalf for gas-free +x402 API payments. + +It takes: +- facilitatorAddress: The Floe facilitator EOA address +- borrowLimit: Maximum USDC the facilitator can borrow (e.g. '10000') +- maxRateBps: Interest rate cap in basis points (e.g. '1500' for 15%) +- expiryDays: How many days the delegation lasts (e.g. '90') + +After this, the agent can call x402 APIs via the x402Fetch action +without signing any transactions or paying gas. + `, + schema: FloeGrantDelegationSchema, + }) + async grantDelegation( + wallet: EvmWalletProvider, + args: z.infer, + ): Promise { + try { + const network = wallet.getNetwork(); + const networkId = network.networkId ?? ""; + const matcherAddress = LENDING_MATCHER_ADDRESSES[networkId]; + + if (!matcherAddress) { + return `Floe is not supported on network ${networkId}. Supported: ${SUPPORTED_NETWORKS.join(", ")}`; + } + + const borrowLimitRaw = parseUnits(args.borrowLimit, USDC_DECIMALS); + const maxRateBps = BigInt(args.maxRateBps); + const expiryTimestamp = BigInt(Math.floor(Date.now() / 1000) + parseInt(args.expiryDays, 10) * 86400); + + // Use provided address or default to the known facilitator for this network + const facilitator = (args.facilitatorAddress ?? FACILITATOR_ADDRESSES[networkId]) as Address; + if (!facilitator) { + return `No facilitator address configured for network ${networkId}. Provide facilitatorAddress explicitly.`; + } + + // For onBehalfOfRestriction, use the wallet's own address + // (the facilitator routes USDC to the agent's wallet) + const agentAddress = (await wallet.getAddress()) as Address; + + const data = encodeFunctionData({ + abi: LENDING_MATCHER_ABI, + functionName: "setOperator", + args: [facilitator, borrowLimitRaw, maxRateBps, expiryTimestamp, agentAddress], + }); + + const txHash = await wallet.sendTransaction({ + to: matcherAddress, + data, + }); + + await wallet.waitForTransactionReceipt(txHash); + + const ratePct = (parseInt(args.maxRateBps, 10) / 100).toFixed(2); + + return [ + "## Credit Delegation Granted\n", + `- **Facilitator**: ${facilitator}`, + `- **Borrow limit**: ${args.borrowLimit} USDC`, + `- **Max rate**: ${ratePct}% APR`, + `- **Expires in**: ${args.expiryDays} days`, + `- **Tx**: ${txHash}`, + "", + "You can now call x402 APIs via the `x402Fetch` action — gas-free.", + ].join("\n"); + } catch (error) { + return `Error granting delegation: ${error}`; + } + } + + /** + * Calls an x402-enabled API via the Floe facilitator. The facilitator + * handles payment automatically using the agent's delegated credit. + * Gas-free for the agent. + * + * @param wallet - The wallet instance for authentication. + * @param args - The URL and optional request parameters. + * @returns The API response content. + */ + @CreateAction({ + name: "x402Fetch", + description: ` +This tool calls any x402-enabled API via the Floe facilitator proxy. +The facilitator handles USDC payment automatically using your delegated +credit — you pay nothing, sign nothing, and use zero gas. + +It takes: +- url: The x402-enabled URL to call +- method: HTTP method (default: GET) +- headers: Additional headers (optional) +- body: Request body (optional) + +Requires credit delegation (see grantDelegation) to be set up first. +Works with any of the 13,000+ x402 APIs on Base. + `, + schema: FloeFetchSchema, + }) + async x402Fetch( + wallet: EvmWalletProvider, + args: z.infer, + ): Promise { + try { + const headers = await this.buildAuthHeaders(wallet); + const res = await this.apiCall("/v1/proxy/fetch", { + method: "POST", + headers, + body: { + url: args.url, + method: args.method ?? "GET", + headers: args.headers ?? {}, + body: args.body, + }, + }); + + if (!res.ok) { + const err = await res.json().catch(() => ({ error: res.statusText })); + const errorCode = err.error || res.statusText; + + // Map common facilitator errors to actionable messages + if (res.status === 402) { + return `Insufficient credit balance. Available: ${err.available ?? "unknown"}. Required: ${err.required ?? "unknown"}. Top up collateral or increase delegation limit.`; + } + if (res.status === 403) { + return `Access denied: ${errorCode}. Check that credit delegation is active and not frozen.`; + } + return `x402 fetch failed (${res.status}): ${errorCode}`; + } + + // Check for payment metadata + const paymentResponse = res.headers.get("payment-response") || res.headers.get("x-payment-response"); + const body = await res.text(); + + const lines = ["## x402 Response\n"]; + if (paymentResponse) { + lines.push(`- **Payment**: Settled via Floe credit`); + lines.push(`- **Tx**: ${paymentResponse}`); + } else { + lines.push(`- **Payment**: None required (passthrough)`); + } + lines.push(`- **Status**: ${res.status}`); + lines.push(""); + lines.push("**Response:**"); + lines.push("```"); + lines.push(body.length > 2000 ? body.slice(0, 2000) + "\n... (truncated)" : body); + lines.push("```"); + + return lines.join("\n"); + } catch (error) { + return `Error calling x402 API: ${error}`; + } + } +} + +/** + * Factory function to create a FloeActionProvider instance. + * + * @param facilitatorApi - Optional override for the Floe Credit API URL. + * @returns A new FloeActionProvider. + */ +export const floeActionProvider = (facilitatorApi?: string) => + new FloeActionProvider(facilitatorApi); diff --git a/typescript/agentkit/src/action-providers/floe/index.ts b/typescript/agentkit/src/action-providers/floe/index.ts new file mode 100644 index 000000000..2f1a4a31e --- /dev/null +++ b/typescript/agentkit/src/action-providers/floe/index.ts @@ -0,0 +1 @@ +export * from "./floeActionProvider"; diff --git a/typescript/agentkit/src/action-providers/floe/schemas.ts b/typescript/agentkit/src/action-providers/floe/schemas.ts new file mode 100644 index 000000000..25cc7b55c --- /dev/null +++ b/typescript/agentkit/src/action-providers/floe/schemas.ts @@ -0,0 +1,113 @@ +import { z } from "zod"; + +/** + * Input schema for getting available Floe lending markets. + */ +export const FloeGetMarketsSchema = z + .object({}) + .describe("Input schema for getting available Floe lending markets"); + +/** + * Input schema for instantly borrowing USDC via Floe. + */ +export const FloeInstantBorrowSchema = z + .object({ + borrowAmount: z + .string() + .regex(/^\d+(\.\d+)?$/, "Must be a valid number") + .describe("Amount of USDC to borrow in human-readable format (e.g. '1000' for 1,000 USDC)"), + collateralAmount: z + .string() + .regex(/^\d+(\.\d+)?$/, "Must be a valid number") + .describe("Amount of collateral in human-readable format (e.g. '10000' for 10,000 USDC)"), + maxInterestRateBps: z + .string() + .regex(/^\d+$/, "Must be a whole number") + .describe("Maximum acceptable annual interest rate in basis points (e.g. '800' for 8%)"), + duration: z + .string() + .regex(/^\d+$/, "Must be a whole number") + .describe("Loan duration in seconds (e.g. '1209600' for 14 days)"), + marketId: z + .string() + .regex(/^0x[0-9a-fA-F]+$/, "Must be a valid hex market ID") + .optional() + .describe("Market ID (hex). Defaults to USDC/USDC if omitted"), + }) + .describe("Input schema for instantly borrowing USDC via Floe"); + +/** + * Input schema for repaying a Floe loan. + */ +export const FloeRepaySchema = z + .object({ + loanId: z.string().describe("The on-chain loan ID to repay"), + slippageBps: z + .string() + .regex(/^\d+$/, "Must be a whole number") + .optional() + .describe("Slippage tolerance in basis points (default: 500 = 5%)"), + }) + .describe("Input schema for repaying a Floe loan"); + +/** + * Input schema for checking Floe loan status. + */ +export const FloeCheckStatusSchema = z + .object({ + loanId: z.string().describe("The on-chain loan ID to check"), + }) + .describe("Input schema for checking Floe loan status"); + +/** + * Input schema for getting agent credit balance from the Floe facilitator. + */ +export const FloeGetBalanceSchema = z + .object({}) + .describe("Input schema for getting agent credit balance"); + +/** + * Input schema for checking Floe loan health. + */ +export const FloeCheckHealthSchema = z + .object({ + loanId: z.string().describe("The on-chain loan ID to check health for"), + }) + .describe("Input schema for checking Floe loan health"); + +/** + * Input schema for granting credit delegation to the Floe facilitator. + */ +export const FloeGrantDelegationSchema = z + .object({ + facilitatorAddress: z + .string() + .regex(/^0x[a-fA-F0-9]{40}$/, "Must be a valid Ethereum address") + .optional() + .describe("Floe facilitator address. Defaults to the known address for the current network if omitted."), + borrowLimit: z + .string() + .regex(/^\d+(\.\d+)?$/, "Must be a valid number") + .describe("Maximum USDC the facilitator can borrow on your behalf (e.g. '10000')"), + maxRateBps: z + .string() + .regex(/^\d+$/, "Must be a whole number") + .describe("Maximum interest rate cap in basis points (e.g. '1500' for 15%)"), + expiryDays: z + .string() + .regex(/^\d+$/, "Must be a whole number") + .describe("Delegation duration in days (e.g. '90' for 3 months)"), + }) + .describe("Input schema for granting credit delegation to the Floe facilitator"); + +/** + * Input schema for calling an x402 API via Floe credit. + */ +export const FloeFetchSchema = z + .object({ + url: z.string().url().describe("The x402-enabled URL to fetch"), + method: z.string().optional().describe("HTTP method (default: GET)"), + headers: z.record(z.string()).optional().describe("Additional HTTP headers"), + body: z.string().optional().describe("Request body (for POST/PUT)"), + }) + .describe("Input schema for calling an x402 API via Floe credit"); diff --git a/typescript/agentkit/src/action-providers/index.ts b/typescript/agentkit/src/action-providers/index.ts index 9f7164086..4255af6d5 100644 --- a/typescript/agentkit/src/action-providers/index.ts +++ b/typescript/agentkit/src/action-providers/index.ts @@ -17,6 +17,7 @@ export * from "./erc20"; export * from "./erc721"; export * from "./erc8004"; export * from "./farcaster"; +export * from "./floe"; export * from "./jupiter"; export * from "./messari"; export * from "./pyth"; diff --git a/typescript/examples/floe-credit-agent/.env.example b/typescript/examples/floe-credit-agent/.env.example new file mode 100644 index 000000000..7928e57e1 --- /dev/null +++ b/typescript/examples/floe-credit-agent/.env.example @@ -0,0 +1,3 @@ +PRIVATE_KEY=0x_your_private_key_here +RPC_URL=https://base-mainnet.g.alchemy.com/v2/YOUR_KEY +OPENAI_API_KEY=sk-your-openai-key diff --git a/typescript/examples/floe-credit-agent/README.md b/typescript/examples/floe-credit-agent/README.md new file mode 100644 index 000000000..1eb164920 --- /dev/null +++ b/typescript/examples/floe-credit-agent/README.md @@ -0,0 +1,42 @@ +# Floe Working Capital Agent Example + +An agent that gets a USDC credit line, calls a paid x402 API, and repays from earnings. + +**3,000+ secured working capital lines issued. Zero defaults.** + +## What it does + +1. **Deposits** USDC and borrows 95% as working capital +2. **Calls** an x402-enabled API using Floe's credit delegation (gas-free) +3. **Checks** loan health and accrued interest +4. **Repays** — deposit returns automatically + +## Prerequisites + +- Node.js 18+ +- USDC on Base (or buy from the [dashboard](https://dev-dashboard.floelabs.xyz) with a bank account or card) +- An RPC endpoint (Alchemy, Infura, or similar) + +## Setup + +```bash +cp .env.example .env +# Edit .env with your private key, RPC URL, and OPENAI_API_KEY +pnpm install +``` + +## Run + +```bash +pnpm start +``` + +## Why agents need this + +100M+ x402 machine payments since May 2025. 100,000+ agents with onchain identity. Zero credit outstanding to any of them. Every economy runs on credit — Floe is the credit layer for the agent economy. + +## Learn more + +- [Floe Docs](https://floe-labs.gitbook.io/docs) +- [Bank Account → First API Call](https://floe-labs.gitbook.io/docs/agents/fiat-to-x402) +- [Full npm package (45 actions)](https://www.npmjs.com/package/floe-agent) diff --git a/typescript/examples/floe-credit-agent/index.ts b/typescript/examples/floe-credit-agent/index.ts new file mode 100644 index 000000000..831da7847 --- /dev/null +++ b/typescript/examples/floe-credit-agent/index.ts @@ -0,0 +1,69 @@ +/** + * Floe Credit Agent — End-to-End Example + * + * Demonstrates the full credit lifecycle: + * 1. Deposit USDC, borrow USDC working capital (fixed rate, no price risk) + * 2. Call a paid x402 API using Floe's credit delegation + * 3. Check loan health and accrued interest + * 4. Repay — collateral returns automatically + * + * This is the first working capital provider in AgentKit. + * Unlike Compound/Morpho (variable-rate pools), Floe offers + * per-loan isolated escrow with no pool contagion risk. + */ + +import "dotenv/config"; +import { AgentKit, ViemWalletProvider, FloeActionProvider } from "@coinbase/agentkit"; +import { getVercelAITools } from "@coinbase/agentkit-vercel-ai-sdk"; +import { generateText } from "ai"; +import { openai } from "@ai-sdk/openai"; +import { createWalletClient, http } from "viem"; +import { privateKeyToAccount } from "viem/accounts"; +import { base } from "viem/chains"; + +async function main() { + // Validate required env vars + if (!process.env.PRIVATE_KEY) throw new Error("PRIVATE_KEY is required in .env"); + if (!process.env.RPC_URL) throw new Error("RPC_URL is required in .env"); + if (!process.env.OPENAI_API_KEY) throw new Error("OPENAI_API_KEY is required in .env"); + + // 1. Setup wallet + const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`); + const client = createWalletClient({ + account, + chain: base, + transport: http(process.env.RPC_URL), + }); + const walletProvider = new ViemWalletProvider(client); + + // 2. Create AgentKit with Floe working capital actions + const agentkit = await AgentKit.from({ + walletProvider, + actionProviders: [new FloeActionProvider()], + }); + + const tools = getVercelAITools(agentkit); + + // 3. Run the agent + console.log("🚀 Floe Credit Agent starting...\n"); + + const { text } = await generateText({ + model: openai("gpt-4o"), + tools, + maxSteps: 10, + system: `You are a DeFi agent on Base that uses Floe for fixed-rate credit. +Your task: +1. Check what lending markets are available +2. Deposit 10,000 USDC and borrow 9,500 USDC working capital at max 10% APR for 14 days +3. Check the loan status +4. Report what happened + +Be concise. Report numbers clearly.`, + prompt: "Execute the credit workflow described in your system prompt.", + }); + + console.log("\n📊 Agent output:\n"); + console.log(text); +} + +main().catch(console.error); diff --git a/typescript/examples/floe-credit-agent/package.json b/typescript/examples/floe-credit-agent/package.json new file mode 100644 index 000000000..c324d1257 --- /dev/null +++ b/typescript/examples/floe-credit-agent/package.json @@ -0,0 +1,19 @@ +{ + "name": "floe-credit-agent-example", + "private": true, + "type": "module", + "scripts": { + "start": "tsx index.ts" + }, + "dependencies": { + "@coinbase/agentkit": "workspace:*", + "@coinbase/agentkit-vercel-ai-sdk": "workspace:*", + "@ai-sdk/openai": "^3.0.0", + "ai": "^4.1.0", + "viem": "^2.21.0", + "dotenv": "^16.4.5" + }, + "devDependencies": { + "tsx": "^4.0.0" + } +} From 33df6b0cf7bf7fc9aca9f46cd04749e061a43628 Mon Sep 17 00:00:00 2001 From: Alex Christian Date: Sun, 10 May 2026 13:57:57 -0700 Subject: [PATCH 2/2] docs: update Floe positioning to Financial OS for AI Agents MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Align provider README, example README, and example code with Floe's positioning as the Financial OS for AI agents — wallet, fiat on/off-ramp, working capital, x402 payments, and portable credit in one action provider. --- .../src/action-providers/floe/README.md | 46 +++++++++++-------- .../examples/floe-credit-agent/README.md | 23 ++++++---- .../examples/floe-credit-agent/index.ts | 44 +++++++++--------- 3 files changed, 66 insertions(+), 47 deletions(-) diff --git a/typescript/agentkit/src/action-providers/floe/README.md b/typescript/agentkit/src/action-providers/floe/README.md index 19d6fb9ed..44fe56868 100644 --- a/typescript/agentkit/src/action-providers/floe/README.md +++ b/typescript/agentkit/src/action-providers/floe/README.md @@ -1,22 +1,33 @@ -# Floe Working Capital Provider +# Floe — The Financial OS for AI Agents -Financial independence is the precursor to agent autonomy. Long-running agents can't do anything meaningful without their own fundable balance sheet. Floe gives them one. +Wallet, fiat on/off-ramp, working capital, x402 payments, and portable credit — in one action provider. -**3,000+ secured working capital lines issued. Zero defaults.** +**3,000+ secured working capital lines issued. Zero defaults. 13,000+ x402 APIs reachable.** -Deposit USDC, borrow up to 95% as a credit line. Fixed rates, per-loan isolated escrow, gas-free. Fund with a bank account or card — no crypto experience needed. +## The financial loop + +``` +1. Fund → Buy USDC with a bank account or card (dashboard) +2. Deposit → Agent deposits USDC as collateral +3. Borrow → Get up to 95% back as working capital (instant_borrow) +4. Spend → Call any x402 API — Floe pays automatically (x402_fetch) +5. Repay → Pay back principal + fixed fee, deposit returns +6. Trust → Every repayment builds the agent's credit record +``` + +No price-volatility risk. No crypto complexity. Gas-free for delegated agents. ## Actions -| Action | Type | Description | -|--------|------|-------------| -| `getMarkets` | Read | List available lending markets | -| `instantBorrow` | Write | Deposit USDC, borrow up to 95% working capital — auto-selects best lender | -| `repay` | Write | Repay a loan (deposit returns automatically) | +| Action | Type | What it does | +|--------|------|--------------| +| `getMarkets` | Read | Available lending markets and terms | +| `instantBorrow` | Write | Deposit USDC, borrow up to 95% — auto-selects best lender | +| `repay` | Write | Repay loan — deposit returns automatically | | `checkStatus` | Read | Loan health, balance, accrued interest, time to expiry | -| `getBalance` | Read | Credit balance for facilitator-delegated agents | -| `checkHealth` | Read | On-chain LTV and liquidation risk | -| `grantDelegation` | Write | Delegate credit to Floe facilitator for gas-free x402 payments (one-time) | +| `getBalance` | Read | Credit balance + utilization (for facilitator-delegated agents) | +| `checkHealth` | Read | Current LTV and liquidation risk | +| `grantDelegation` | Write | Delegate to Floe facilitator for gas-free x402 payments (one-time) | | `x402Fetch` | Write | Call any x402 API — Floe handles payment automatically | ## Setup @@ -31,15 +42,14 @@ const agentkit = await AgentKit.from({ actionProviders: [new FloeActionProvider()], }); -// Use with Vercel AI SDK, LangChain, or any framework const tools = getVercelAITools(agentkit); ``` -## Why agents need credit +## Why 100M+ x402 machine payments since May 2025. 100,000+ agents with onchain identity. Zero credit outstanding to any of them. -Every economy runs on credit. Agents don't have FICO — but they have something better: deterministic cashflows and verifiable execution history. Floe is the credit bureau and capital rail for AI agents. +Every economy runs on credit. Agents don't have FICO — but they have something better: deterministic cashflows and verifiable execution history. Floe is building the credit bureau and capital rail for all AI agents. ## Network @@ -48,8 +58,8 @@ Every economy runs on credit. Agents don't have FICO — but they have something ## Links -- [Docs](https://floe-labs.gitbook.io/docs) -- [Bank Account → First API Call](https://floe-labs.gitbook.io/docs/agents/fiat-to-x402) — fund with fiat, no crypto needed +- [Quickstart](https://floe-labs.gitbook.io/docs/getting-started/quickstart) — from zero to first API call - [Full npm package (45 actions)](https://www.npmjs.com/package/floe-agent) -- [Dashboard](https://dev-dashboard.floelabs.xyz) +- [Dashboard](https://dev-dashboard.floelabs.xyz) — fund with fiat, manage agents +- [Docs](https://floe-labs.gitbook.io/docs) - [Website](https://floelabs.xyz) diff --git a/typescript/examples/floe-credit-agent/README.md b/typescript/examples/floe-credit-agent/README.md index 1eb164920..7d2fbe077 100644 --- a/typescript/examples/floe-credit-agent/README.md +++ b/typescript/examples/floe-credit-agent/README.md @@ -1,14 +1,14 @@ -# Floe Working Capital Agent Example +# Floe Financial OS — Agent Example -An agent that gets a USDC credit line, calls a paid x402 API, and repays from earnings. +The full financial loop: fund → borrow → spend → repay. One provider, one agent. **3,000+ secured working capital lines issued. Zero defaults.** ## What it does 1. **Deposits** USDC and borrows 95% as working capital -2. **Calls** an x402-enabled API using Floe's credit delegation (gas-free) -3. **Checks** loan health and accrued interest +2. **Calls** x402 APIs — Floe handles payment automatically (gas-free) +3. **Checks** credit status, loan health, accrued interest 4. **Repays** — deposit returns automatically ## Prerequisites @@ -16,6 +16,7 @@ An agent that gets a USDC credit line, calls a paid x402 API, and repays from ea - Node.js 18+ - USDC on Base (or buy from the [dashboard](https://dev-dashboard.floelabs.xyz) with a bank account or card) - An RPC endpoint (Alchemy, Infura, or similar) +- An OpenAI API key ## Setup @@ -31,12 +32,18 @@ pnpm install pnpm start ``` -## Why agents need this +## The Floe Stack -100M+ x402 machine payments since May 2025. 100,000+ agents with onchain identity. Zero credit outstanding to any of them. Every economy runs on credit — Floe is the credit layer for the agent economy. +| # | Component | What this example shows | +|---|---|---| +| 01 | Agent Wallet | ViemWalletProvider setup | +| 02 | Fiat on-ramp | Fund via [dashboard](https://dev-dashboard.floelabs.xyz) (card, bank, Apple/Google Pay) | +| 03 | Secured credit | `instantBorrow` — deposit USDC, get 95% working capital | +| 04 | x402 payments | `x402Fetch` — call any paid API, Floe handles payment | +| 05 | Credit bureau | Every repayment builds the agent's credit record | ## Learn more -- [Floe Docs](https://floe-labs.gitbook.io/docs) -- [Bank Account → First API Call](https://floe-labs.gitbook.io/docs/agents/fiat-to-x402) +- [Quickstart](https://floe-labs.gitbook.io/docs/getting-started/quickstart) - [Full npm package (45 actions)](https://www.npmjs.com/package/floe-agent) +- [Dashboard](https://dev-dashboard.floelabs.xyz) diff --git a/typescript/examples/floe-credit-agent/index.ts b/typescript/examples/floe-credit-agent/index.ts index 831da7847..cd9240d00 100644 --- a/typescript/examples/floe-credit-agent/index.ts +++ b/typescript/examples/floe-credit-agent/index.ts @@ -1,15 +1,15 @@ /** - * Floe Credit Agent — End-to-End Example + * Floe Financial OS — End-to-End Example * - * Demonstrates the full credit lifecycle: - * 1. Deposit USDC, borrow USDC working capital (fixed rate, no price risk) - * 2. Call a paid x402 API using Floe's credit delegation - * 3. Check loan health and accrued interest - * 4. Repay — collateral returns automatically + * The full financial loop: + * 1. Check available markets (getMarkets) + * 2. Deposit USDC, borrow 95% as working capital (instantBorrow) + * 3. Call a paid x402 API — Floe handles payment (x402Fetch) + * 4. Check credit status (checkStatus) + * 5. Repay — deposit returns automatically (repay) * - * This is the first working capital provider in AgentKit. - * Unlike Compound/Morpho (variable-rate pools), Floe offers - * per-loan isolated escrow with no pool contagion risk. + * Floe is the Financial OS for AI agents — wallet, fiat on/off-ramp, + * working capital, x402 payments, and portable credit in one provider. */ import "dotenv/config"; @@ -23,9 +23,9 @@ import { base } from "viem/chains"; async function main() { // Validate required env vars - if (!process.env.PRIVATE_KEY) throw new Error("PRIVATE_KEY is required in .env"); - if (!process.env.RPC_URL) throw new Error("RPC_URL is required in .env"); - if (!process.env.OPENAI_API_KEY) throw new Error("OPENAI_API_KEY is required in .env"); + if (!process.env.PRIVATE_KEY) throw new Error("PRIVATE_KEY is required — see .env.example"); + if (!process.env.RPC_URL) throw new Error("RPC_URL is required — see .env.example"); + if (!process.env.OPENAI_API_KEY) throw new Error("OPENAI_API_KEY is required — see .env.example"); // 1. Setup wallet const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`); @@ -36,7 +36,7 @@ async function main() { }); const walletProvider = new ViemWalletProvider(client); - // 2. Create AgentKit with Floe working capital actions + // 2. Create AgentKit with Floe Financial OS const agentkit = await AgentKit.from({ walletProvider, actionProviders: [new FloeActionProvider()], @@ -44,22 +44,24 @@ async function main() { const tools = getVercelAITools(agentkit); - // 3. Run the agent - console.log("🚀 Floe Credit Agent starting...\n"); + // 3. Run the financial loop + console.log("🚀 Floe Financial OS Agent starting...\n"); const { text } = await generateText({ model: openai("gpt-4o"), tools, maxSteps: 10, - system: `You are a DeFi agent on Base that uses Floe for fixed-rate credit. -Your task: + system: `You are an AI agent on Base with access to the Floe Financial OS. + +Your task — execute the full financial loop: 1. Check what lending markets are available 2. Deposit 10,000 USDC and borrow 9,500 USDC working capital at max 10% APR for 14 days -3. Check the loan status -4. Report what happened +3. Check the loan status and remaining credit +4. Report what happened — include the loan ID, rate, and credit available -Be concise. Report numbers clearly.`, - prompt: "Execute the credit workflow described in your system prompt.", +Be concise. Report numbers clearly. This demonstrates the complete +fund → borrow → spend → repay lifecycle.`, + prompt: "Execute the financial loop described in your instructions.", }); console.log("\n📊 Agent output:\n");