Borro Open app

Borro Bookrunners

Send depositors to Borro. Keep the performance fee.

One link or one contract call. Deposits stay in the depositor's wallet, shares and all. Paid monthly in USDG.

The economics

Fee share, plus the Drip.

Borro's fee in USDG, and 20M $BORRO split among bookrunners by the deposits they bring. Borrowers count too: Borro's fee on the interest they pay.

Revenue splitSet in the keeper's terms
100%of Borro's fee, first 12 months
50%of the fee after that
20M$BORRO Bookrunner Drip, year one
$1,000,000
You earn in year one$66,000
Each year after$15,000
Your depositors earn31%
Year one isfee share + Bookrunner Drip

Deposits held a year; yours a fifth of all bookrunners bring. Assumes 30% rent (AAPL and GOOGL lenders today), today's Drip rate, and $BORRO at 1.75x TVL (Longbow: 1.1x). Not a promise.

  • Counted per deposit

    Each referred deposit is its own lot with its own clock. Withdrawals come off the oldest lots first.

  • Paid from $1

    Any month a code is owed $1 or more. Less carries over to the next month.

  • Settled monthly

    In USDG, to the code's wallet, by the keeper.

  • Nothing to custody

    Vault shares land in the depositor's wallet. The contract holds nothing between transactions.

Integrate

One call credits the deposit.

Approve the token to the Bookrunners contract, then deposit(vault, assets, code, minShares). The shares land in the caller's wallet.

deposit.jsdeposit(address,uint256,bytes32,uint256)
import { ethers } from "ethers";

const BUILDERS   = "0x082Ae609bdC7ccEec0C58B2361a21c49dc2aE291";
const NVDA_VAULT = "0x508E23274C39C60c345e35B1f0188bE180717CB9";
const NVDA       = "0xd0601CE157Db5bdC3162BbaC2a2C8aF5320D9EEC";

const provider = new ethers.BrowserProvider(window.ethereum); // Robinhood Chain, chain id 4663
const signer   = await provider.getSigner();

const vault    = new ethers.Contract(NVDA_VAULT, ["function previewDeposit(uint256 assets) view returns (uint256)"], provider);
const token    = new ethers.Contract(NVDA, ["function approve(address spender, uint256 amount) returns (bool)"], signer);
const builders = new ethers.Contract(BUILDERS, ["function deposit(address vault, uint256 assets, bytes32 code, uint256 minShares) returns (uint256 shares)"], signer);

const assets    = ethers.parseUnits("1.5", 18); // 1500000000000000000n
const code      = ethers.encodeBytes32String("route"); // 0x726f757465000…000
const minShares = (await vault.previewDeposit(assets)) * 995n / 1000n; // e.g. 1497000000000000000n gives 1489515000000000000n

await (await token.approve(BUILDERS, assets)).wait();
const receipt = await (await builders.deposit(NVDA_VAULT, assets, code, minShares)).wait();
// The shares are in the signer's wallet. The Referred event in the receipt carries the exact number.

Standings

The first codes registered.

Ranked by deposits referred, from the public statement. A seat promises nothing beyond the terms below.

Founding board3 seats open

The first referred deposit writes the board.

Seats show as open until a code has a referred deposit on chain. The statement is rebuilt every hour.

01Open seatThe first code with a referred deposit
02Open seatBy deposits referred
03Open seatBy deposits referred

Reference

Everything the contract does, and how to read it.

02One call, worked

Two transactions from the depositor's wallet: approve the stock token (or USDG) to the Bookrunners contract, then:

Solidity
deposit(address vault, uint256 assets, bytes32 code, uint256 minShares) returns (uint256 shares)
ArgumentMeaning
vaultOne of Borro's vaults, listed under The contract.
assetsThe deposit, in units of the vault's underlying token.
codeYour code as a bytes32 of its ASCII text, right-padded with zeros.
minSharesThe fewest shares the vault may give: previewDeposit(assets) less the slip you allow. The site allows 0.5%.
returns sharesThe vault shares, sent to the caller exactly as a deposit at the vault would. The contract keeps nothing between transactions.

Worked example: 1.5 NVDA, code route

Bookrunners contract0x082Ae609bdC7ccEec0C58B2361a21c49dc2aE291
NVDA vault (bNVDA)0x508E23274C39C60c345e35B1f0188bE180717CB9
NVDA token0xd0601CE157Db5bdC3162BbaC2a2C8aF5320D9EEC
assets1500000000000000000 (1.5 NVDA, 18 decimals)
code0x726f757465000000000000000000000000000000000000000000000000000000 ("route")
previewDeposit1497000000000000000 (what the vault quoted when this example was written; read it fresh)
minShares1489515000000000000 (previewDeposit × 995 / 1000)

The code for this call, in ethers, viem or raw calldata, is in the card above. The same call lends USDG: pass the USDG vault, USDG units (6 decimals) and approve USDG instead.

Reverts: NotRegistered if the code has no payee, BadVault if the vault is not one of Borro's, TooFewShares(got, wanted) if the vault would give fewer shares than minShares.

03The contract

BorroBuildersNot on chain yet
ChainRobinhood Chain, chain id 4663
Deployedblock 88624905, deploy transaction
Owner0x8A22d6e75e9427b9D57e42a7984200029e7E173C (the Borro multisig; can list vaults and sweep, cannot touch codes or deposits)
Sourcecontracts/src/BorroBuilders.sol, Solidity 0.8.28, GPL-2.0-or-later

ABI

ABI
[
  "function register(bytes32 code)",
  "function setPayee(bytes32 code, address to)",
  "function deposit(address vault, uint256 assets, bytes32 code, uint256 minShares) returns (uint256 shares)",
  "function builders(bytes32 code) view returns (address payee)",
  "function vaults(address vault) view returns (bool listed)",
  "event Registered(bytes32 indexed code, address indexed payee)",
  "event Referred(bytes32 indexed code, address indexed who, address indexed vault, uint256 assets, uint256 shares)",
  "event VaultListed(address indexed vault, address indexed asset, bool listed)",
  "error BadCode()",
  "error Taken()",
  "error NotPayee()",
  "error BadPayee()",
  "error NotRegistered()",
  "error BadVault()",
  "error Nothing()",
  "error TooFewShares(uint256 got, uint256 wanted)"
]
NameMeaning
register(code)Claims an unclaimed, non-zero code for msg.sender, who becomes its payee. First come, first served.
setPayee(code, to)Moves a code to another wallet. Only the code's current payee can; to cannot be the zero address. Announced as another Registered, so a code's whole history is in its Registered events.
deposit(vault, assets, code, minShares)Pulls assets of the vault's asset from the caller, deposits them to the vault in the caller's name, checks shares >= minShares, emits Referred. Reentrancy-guarded.
builders(code)The wallet a code is paid to; the zero address if the code is free.
vaults(vault)Whether a vault is one deposits may go to.
RegisteredA code was claimed or moved: code, payee.
ReferredA referred deposit: code, who (the depositor), vault, assets in, shares out. The keeper counts these.
VaultListedThe owner listed or unlisted a vault.
BadCoderegister was given the zero code.
TakenThe code already has a payee.
NotPayeesetPayee from a wallet that is not the code's payee.
BadPayeesetPayee to the zero address.
NotRegistereddeposit with a code nobody has claimed.
BadVaultThe vault is not listed, or reports no asset.
Nothingdeposit with assets of zero.
TooFewShares(got, wanted)The vault gave fewer shares than minShares; the deposit is undone.

Owner only: setVault(address vault, bool listed) and sweep(address token, address to), for anything sent to the contract by mistake. Between transactions it holds nothing of anyone's.

The vaults it deposits to

Nine stock vaults and the USDG vault. The underlying token is what you approve to the Bookrunners contract, and what assets is counted in.

VaultVault addressUnderlying tokenDecimals

Registering from code

The app registers for you at /app#builders. From code, with the ABI above: register(encodeBytes32String("route")) from the wallet to be paid, then setPayee(code, to) to pay another wallet.

Finding your deposits

Every referred deposit is a Referred log on the contract. On Blockscout, the Logs tab lists them; your code is the first indexed topic. From an RPC, filter from block 88624905:

JavaScript
// topic0 is Referred's signature hash, topic1 your code as bytes32
const logs = await provider.getLogs({
  address: "0x082Ae609bdC7ccEec0C58B2361a21c49dc2aE291",
  fromBlock: 88624905,
  topics: [
    "0x0ceba4fd933fd5fe81eae9e5fd1c015a5178d13c4d7db4a0b84e4a1062eb5511", // Referred(bytes32,address,address,uint256,uint256)
    ethers.encodeBytes32String("route"),
  ],
});

04Statement API

What each code brought, earned and was paid, rebuilt by the keeper from the chain's events and republished every hour. Public, no key.

URL
GET https://borro.markets/builders.json

An example, with made-up numbers:

JSON
{
  "contract": "0x082ae609bdc7cceec0c58b2361a21c49dc2ae291",
  "terms": { "fullMonths": 12, "after": 0.5, "payoutDay": "monthly" },
  "updatedAt": 1760515200,
  "lastDay": "2026-10-14",
  "codes": {
    "route": {
      "payee": "0x1111111111111111111111111111111111111111",
      "lots": 42,
      "referred": {
        "0x508E23274C39C60c345e35B1f0188bE180717CB9": "63000000000000000000",
        "0xF8e0ab443098c0738489B5BAFFbd32A1341F2289": "2500000000"
      },
      "valueUsdg": "14428.400000",
      "owed": "38.215000",
      "paid": "12.900000",
      "payouts": [
        { "at": 1759363200, "tx": "0x6f1e…", "usdg": "12.900000", "month": "2026-09" }
      ]
    }
  },
  "days": [
    { "day": "2026-10-13", "byCode": { "route": "1.187500" } },
    { "day": "2026-10-14", "byCode": { "route": "1.302500" } }
  ]
}
FieldMeaning
contractThe Bookrunners contract the statement was built from, lowercase.
termsfullMonths: months a lot is paid at 100%. after: the share after that. payoutDay: how often payouts go out.
updatedAtUnix seconds of the chain block the statement was built at.
lastDayThe last whole UTC day counted, or null before the first.
codes.<code>.payeeThe wallet the code is paid to, as the contract names it now.
codes.<code>.lotsHow many referred deposits the code has had.
codes.<code>.referredPer vault, the vault shares still counted for the code, in the vault's share units (a string of wei).
codes.<code>.valueUsdgWhat those shares were worth at the last day's close, in USDG.
codes.<code>.owedEverything the code has earned so far, in USDG; paid is how much of that has gone out.
codes.<code>.payoutsEach payout: block time, transaction, USDG, the month it covered.
daysThe last 90 UTC days: what each code earned that day.
codes.<code>.depositFee, borrowFeeOnce borrows are counted: what deposits and borrows have earned the code so far, in USDG. owed is their sum.
codes.<code>.borrowersOnce borrows are counted: how many wallets have signed for the code.
totalsOnce borrows are counted: the same sums over every code, and the last day the borrows were counted.

Every USDG amount is a string with six decimals. Share amounts are strings of whole share units. Before the keeper's first run the route answers 404 with { "error": "no builders statement published yet" }.

05Borrows: one signature

A borrower credits your code with a free signature, not a transaction. The app asks for it the first time a wallet that came by your link borrows; an integrator or an agent sends it here. One code per wallet: the first binding stands, and its 12 months start at the signature.

JavaScript
// EIP-712, signed by the borrowing wallet (ethers v6): no gas, nothing moves
const timestamp = Math.floor(Date.now() / 1000);
const domain  = { name: "Borro Builders", version: "1", chainId: 4663 };
const types   = { BuilderBinding: [
  { name: "statement", type: "string" }, { name: "account", type: "address" }, { name: "code", type: "string" },
  { name: "chainId", type: "uint256" }, { name: "timestamp", type: "uint256" },
] };
const message = { statement: "Credit my Borro borrowing to builder code route", account, code: "route", chainId: 4663, timestamp };
const signature = await signer.signTypedData(domain, types, message);

await fetch("https://borro.markets/builders/bind", {
  method: "POST", headers: { "content-type": "application/json" },
  body: JSON.stringify({ account, code: "route", timestamp, signature }),
}); // 201 { ok: true, binding: { account, code, timestamp, at } }
FieldMeaning
accountThe borrowing wallet. Its own key signs; a contract wallet cannot bind yet.
codeYour code, as registered.
timestampUnix seconds when it was signed. Taken for 10 minutes.
signatureThe wallet's signature of the typed data above.
Answers201 kept. 400 a bad shape, time or signature, or a wallet with no ETH and no transaction. 404 a code nobody registered. 409 the wallet is bound already.
ReadingGET /builders/bind/<wallet> for one wallet, GET /builders/bindings for all of them, signatures included.

You earn Borro's fee on the interest the wallet pays in Borro's markets, shorts included: all of it for 12 months, half after. A fee is never paid twice: the part a referred deposit already earns stays that deposit's bookrunner's.

06Agents

An agent credits a code the same way: it calls deposit on the Bookrunners contract from the wallet it acts for, with the ABI and calldata above.

PathWhat it does
deposit on the contractA vault deposit with your code attached. The shares go to the wallet the agent acts for.
MCP supply toolBorro's MCP server (https://borro.markets/mcp, set up at /agents) prepares a plain vault deposit and takes no bookrunner code. An agent that wants the credit builds the deposit call itself.
MCP bind_builder toolReturns the typed data for the wallet to sign, then submits the signature: the wallet's borrowing is credited to your code.

07Terms

  1. Get your code with the wallet that will be paid

    Anyone can. A code is first come, first served, and only its payee can change where it is paid.

  2. Share your link

    borro.markets/?ref=<code>. For 90 days after a visit, that visitor's deposits carry your code. Integrators can call the contract directly instead.

  3. Every referred deposit is an on-chain event

    Anyone can check it. Each is a lot credited to you while it is in the vault; withdrawals come off a wallet's oldest lots first, so the lots still at 100% stay counted.

  4. You are paid your lots' part of the performance fee

    The fee is the vault's own, 10% of the rent. All of it for a lot's first 365 days, half of it after that.

  5. Borrowers count too

    A borrower who signs for your code earns you Borro's fee on the interest it pays: all of it for 12 months from the signature, half after.

  6. Size counts

    The fee share pays from the first dollar you bring. The Bookrunner Drip is for size: a code shares a day's Drip when the deposits it referred add up to $50,000 or more, all its users together.

  7. Paid monthly in USDG

    To the code's payee, by the keeper, in every month a code is owed at least $1. Less carries over to the next month.

  8. The statement is public

    What each code brought, earned and was paid, updated hourly, in the app and at borro.markets/builders.json.

What to know

Rent floats with demand

If nobody borrows the stock there is no rent, no fee and nothing to share. No income is promised.

Lenders pay nothing extra

The split is between Borro and you, out of a fee the vaults already take.

The $BORRO Drip stays with the depositor

None of it goes to the bookrunner.

Your cut comes out of the $BORRO buyback

It is the part of the fee that would otherwise buy back and burn $BORRO. In a lot's 100% window, none of its fee funds a buyback.

08Get in touch

Placing a card, building on the contract, or a question about the statement: write to team@borro.markets.