Loans

Borrow against a streamed H081 budget, or lend to one. A lender never receives more than the principal.

In plain words

A loan lets your agent borrow money now and pay it back from a budget that unlocks over the coming days. You set aside a private budget; the lender gets a fixed share of it as it unlocks or is spent, until the loan is repaid.

The lender never sees your balance or your account. They only see a proof: maths that shows "this budget is big enough to repay you", without showing anything else.

And a lender can never receive more than the amount they lent. Anything extra goes back to the borrower. The program on Solana enforces this, not a promise.

How a loan works

Two people (or two agents) take part. Each runs @h081lll/agent with their own key. They swap two codes, by chat, email or any other way.

  1. The lender opens a loan slot and chooses their share of each payment. They get a terms code and send it to the borrower.
  2. The borrower pledges a budget against those terms. This makes a proof code, which they send back.
  3. The lender funds the loan with that proof code. The money goes to the borrower straight away.
  4. The lender is repaid from their share of the budget, as it unlocks, until the loan is paid back.
RoleCommandsPays from
Lenderlend open, lend fund, lend status, lend claim, lend collect, lend settleThe USDG in the agent wallet itself (public), not the private balance
Borrowerloan quote, loan borrowThe budget comes from the private (shielded) balance

There is no public list of loan offers: terms always come from a lender you are in touch with. Lenders can also use the app, under Loans.

The two codes

CodeStarts withMade byWho gets itWhat it says
Terms codeh081-loan-terms:lend openThe borrowerWhich loan slot, and the lender's share of each payment
Proof codeh081-loan-proof:loan borrowThe lenderProof that a pledged budget backs this exact amount, and the account the money goes to

Both codes are made of letters and digits and carry a checksum, their kind and the network they were made for. So a typo, a cut-off copy, or a code from another network is caught before anything is sent. Check any code you are given:

any terminal
npx -y @h081lll/agent check h081-loan-terms:…
what it prints
valid loan-terms code for mainnet-beta

A bad code prints not valid and the reason, and the command exits with code 1. The local API does the same with POST /v1/codes/check.

The numbers, on one example

The lender's share is set in basis points (--pledge-bps): 100 is 1%, 2000 is 20%, 9000 (the most) is 90%. The budget you pledge is sized so that the lender's share of it is exactly the loan:

budget = loan × 10000 ÷ pledge-bps

ItemExample
Lender's share--pledge-bps 2000 = 20%
Loan (principal)10 USDG
Budget the borrower pledges10 × 10000 ÷ 2000 = 50 USDG
Origination fee (0.25%, to the protocol)0.025 USDG
Borrower receives9.975 USDG
Budget unlocks over--unlock-days 30: about 1.67 USDG a day
Lender's share unlocking20% of that: about 0.33 USDG a day, so 10 USDG after 30 days
Budget lives--days 60: the pledged budget ends after 60 days

The other 80% of the budget stays the borrower's to spend, within the budget's rules.

Step by step: Ana lends, Ben borrows

Ana lends 10 USDG to Ben. Both have run npx -y @h081lll/agent init and funded their agent wallets. Ana's agent wallet holds at least 10 USDG (lending uses the wallet's public USDG, not the private balance) and about 0.15 SOL; Ben has 50 USDG in his private balance and about 0.1 SOL for the pledge (most of it refunded). Every command first shows its plan; nothing is sent without --yes.

  1. Ana opens a loan slot

    She asks for 20% of each payment from Ben's budget.

    Ana · 1 · see the plan (nothing is sent)
    npx -y @h081lll/agent lend open --pledge-bps 2000
    what it prints
    Plan (nothing was sent):
      lend open
      sent by: agent key; SOL needed: 0.01
      public on chain: agent wallet as lender
      ! The loan program needs the slot owner to sign, so the agent key sends every lender action: the agent wallet is public as the lender.
      details: {…}
    Add --yes to send it.
    Ana · 2 · send it
    npx -y @h081lll/agent lend open --pledge-bps 2000 --yes

    The result gives the slot number (for example 7) and the terms code. Ana sends the terms code to Ben.

  2. Ben checks the terms

    Ben
    npx -y @h081lll/agent check h081-loan-terms:…
    what it prints
    valid loan-terms code for mainnet-beta
    Ben · read the lender's share and whether the slot is still open
    npx -y @h081lll/agent loan quote h081-loan-terms:…
  3. Ben pledges a budget and gets the proof code

    Ben wants 10 USDG. At Ana's 20% share, a 50 USDG budget backs exactly 10 USDG (see the numbers above), so he pledges 50. It unlocks over 30 days and lives for 60 days. Deposit first if the private balance is short.

    Ben · if needed, deposit the budget first
    npx -y @h081lll/agent deposit 50 --yes
    Ben · 1 · see the plan (nothing is sent)
    npx -y @h081lll/agent loan borrow h081-loan-terms:… --budget 50 --days 60 --unlock-days 30
    Ben · 2 · send it
    npx -y @h081lll/agent loan borrow h081-loan-terms:… --budget 50 --days 60 --unlock-days 30 --yes

    It prints a proof code starting h081-loan-proof: and the exact loan it asks for (10 USDG). Ben sends the code to Ana. Nothing is borrowed yet: the money moves only when Ana funds it.

  4. Ana checks the proof and funds the loan

    Ana
    npx -y @h081lll/agent check h081-loan-proof:…
    what it prints
    valid loan-proof code for mainnet-beta

    Before sending, the plan checks the proof is for her slot, in USDG, not expired, and backed by a live budget pledged to her. It shows the exact principal, what Ben receives and the account it goes to. --principal 10 is optional: it makes the command refuse if the proof asks for any other amount.

    Ana · 1 · see the plan (nothing is sent)
    npx -y @h081lll/agent lend fund h081-loan-proof:… --principal 10
    Ana · 2 · send it
    npx -y @h081lll/agent lend fund h081-loan-proof:… --principal 10 --yes
  5. Ben receives the money

    9.975 USDG (10 minus the 0.25% fee) arrives in Ben's agent wallet. That wallet is public, like any Solana wallet.

    Ben
    npx -y @h081lll/agent balance --fresh
  6. Ana gets repaid over the next 30 days

    lend status shows each loan: what is repaid, what is still owed, what she can claim now, and anything that belongs to Ben.

    Ana
    npx -y @h081lll/agent lend status
    npx -y @h081lll/agent lend status --loan-ref 7
    part of the result (example)
    {
      "slots": [
        {
          "loanRef": "7",
          "state": "funded",
          "principalUsdg": "10",
          "repaidUsdg": "3.33",
          "outstandingUsdg": "6.67",
          "claimableUsdg": "0.33",
          "collectableUsdg": "0",
          "borrowerExcessUsdg": "0",
          "payoutTo": "…",
          "expiresAt": "2027-01-10T09:30:00.000Z",
          …
        }
      ]
    }

    claim takes her share of the budget that has unlocked so far. collect takes shares that Ben's payments from the pledged budget already sent to the loan.

    Ana
    # the lender's share that has unlocked so far (see the plan first: leave out --yes)
    npx -y @h081lll/agent lend claim 7 --yes
    # or only part of it
    npx -y @h081lll/agent lend claim 7 --usdg 1 --yes
    # shares the borrower's payments already sent to the loan
    npx -y @h081lll/agent lend collect 7 --yes
  7. Ana settles when it is repaid

    Settling closes the books on the loan. After it, no shortfall can be reported, so settle only once lend status shows nothing outstanding.

    Ana · 1 · see the plan (nothing is sent)
    npx -y @h081lll/agent lend settle 7
    Ana · 2 · send it
    npx -y @h081lll/agent lend settle 7 --yes

Naming the loan instead of the budget

With --budget, the loan is the most that budget backs: 50 USDG at 20% backs 10 USDG. A borrower can instead name the loan with --principal. The budget is then sized for them, and a --budget bigger than the loan needs is refused, because it would let the lender take more than they lent.

name the loan (run it without --yes first to see the plan)
npx -y @h081lll/agent loan borrow h081-loan-terms:… --principal 10 --days 60 --unlock-days 30 --yes

Pricing a loan by its amount needs @h081lll/agent 0.1.1 or newer (npx -y @h081lll/agent@latest). In 0.1.0 a plan or quote made with --principal reported "at most 0 USDG"; use --budget there.

Protection: a lender never gets more than they lent

Since a program upgrade on 10 November 2026, the loan program on Solana enforces this. Once the lender has received the full principal, any further share is not paid to the lender. It belongs to the borrower and goes back to the borrower's payout account: the same account the loan was paid into. Until it is sent there, it waits in the loan slot, still the borrower's.

the loan program (stream_loan) on Solana mainnet
8JBd7epQXxdcKRMaWM1S5PC7nsQKoZq3uCetpiJ684g4
  • The software adds two more guards: loan borrow --principal refuses a budget bigger than the loan needs, and a lender's claim is sized to what is still owed.
  • lend status shows borrowerExcessUsdg: money in the loan that is the borrower's.

Timing: days and unlock days

--unlock-days
How long the pledged budget takes to unlock, evenly. The lender's share unlocks at the same pace, so this is roughly how long repayment takes.
--days
How long the pledged budget lives. It must be at least --unlock-days. Leave some room after the unlock, so the lender has time to claim.
After the end date
What is still unpaid 7 days after the budget ends counts as a shortfall in lend status.
Funding
A lender cannot fund a proof whose budget has already ended.

Risks

For the lender

  • Repayment is only your share of each payment from the borrower's pledged budget, until the principal is repaid. What is still unpaid 7 days after the budget ends is a shortfall.
  • Your agent wallet is public as the lender, and the principal and the borrower's payout account are public.
  • Set H081_MAX_LEND (base units: 1 USDG = 1000000) to refuse any loan above a ceiling.

For the borrower

  • The pledged budget is locked to the loan: your lender's share comes out of every payment from it, and out of what you take back from it.
  • The loan arrives in your agent wallet's public USDG account, so anyone can see that it arrived. Deposit it to make it private.
  • Paying from a pledged budget needs pay … --allow-pledged, because it puts the loan slot and the lender's share on the chain next to the payment, which can link the recipient to your wallet.

What the public chain sees

StepPublic on SolanaStays private
Lender actions (open, fund, claim, collect, settle)The lender's agent wallet, as the lender. At funding: the principal and the borrower's payout account (Ben's agent wallet)Nothing about the borrower's private balance
Pledge (borrow)That the borrower's agent wallet made a grantThe budget, its rules and the borrower's balance
Spending the rest of the budgetAs for any budget (see Allowances)The borrower's payments, unless paid with --allow-pledged

With the local API

Start it with npx -y @h081lll/agent serve. Writes return a plan unless the body has "confirm": true, and a confirmed write needs an Idempotency-Key header. See Local API.

GET/v1/loans/quote

Read a lender's terms code (read-only).

Needs the token#

Query: termsCode (required). Returns the lender's share (pledgeBps), the loan slot and its state on chain.

POST/v1/loans/borrow

Pledge a budget to a lender's terms and get the proof code.

Needs the tokenPlan first · sends only with "confirm": true#

FieldTypeMeaning
termsCodetext, requiredThe lender's h081-loan-terms: code
daysnumber, requiredHow long the pledged budget lives
unlockDaysnumber, requiredHow long it takes to unlock (not more than days)
principaltextThe loan, in USDG; the lender must fund exactly this
budgettextOr the pledged budget, in USDG
dailyCap, maxPerTxtextRules for spending the rest of the budget
agentIndexnumber 0–15Which sub-account holds the budget (default: the first free one)
confirmtrue / falseWithout true: the plan only
macOS / Linux (bash)
TOKEN=$(cat ~/.h081/api-token)
API=http://127.0.0.1:8081/v1

# read the lender's terms (nothing is sent)
curl -s -G -H "Authorization: Bearer $TOKEN" --data-urlencode "termsCode=h081-loan-terms:…" $API/loans/quote

# pledge a budget: first the plan, then "confirm": true. The result carries the proof code for the lender.
curl -s -X POST -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{"termsCode":"h081-loan-terms:…","budget":"50","days":60,"unlockDays":30}' $API/loans/borrow
curl -s -X POST -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" -H "Idempotency-Key: borrow-1" \
  -d '{"termsCode":"h081-loan-terms:…","budget":"50","days":60,"unlockDays":30,"confirm":true}' $API/loans/borrow
Windows PowerShell
$h = @{ Authorization = "Bearer $(Get-Content $HOME\.h081\api-token)" }
$api = "http://127.0.0.1:8081/v1"
$terms = "h081-loan-terms:…"

# read the lender's terms
Invoke-RestMethod -Headers $h "$api/loans/quote?termsCode=$([uri]::EscapeDataString($terms))"

# pledge a budget: the plan, then send it
$body = @{ termsCode = $terms; budget = "50"; days = 60; unlockDays = 30 }
Invoke-RestMethod -Method Post -Headers $h -ContentType application/json -Body ($body | ConvertTo-Json) "$api/loans/borrow"
$body.confirm = $true
$h["Idempotency-Key"] = "borrow-1"
Invoke-RestMethod -Method Post -Headers $h -ContentType application/json -Body ($body | ConvertTo-Json) "$api/loans/borrow"
Python (requests, with API, H as in the lender example)
terms = "h081-loan-terms:…"
print(requests.get(f"{API}/loans/quote", params={"termsCode": terms}, headers=H).json()["result"])

body = {"termsCode": terms, "budget": "50", "days": 60, "unlockDays": 30}
print(requests.post(f"{API}/loans/borrow", json=body, headers=H).json()["status"])   # "plan"
sent = requests.post(f"{API}/loans/borrow", json={**body, "confirm": True},
                     headers={**H, "Idempotency-Key": "borrow-1"}).json()
print(sent["result"]["proofCode"])                   # give this to the lender
TypeScript (SDK)
const termsCode = 'h081-loan-terms:…';
console.log(await bank.loanQuote(termsCode));

const ask = { budget: '50', days: 60, unlockDays: 30 };
console.log((await bank.borrow(termsCode, ask)).result);                   // the plan
const sent = await bank.borrow(termsCode, { ...ask, confirm: true, idempotencyKey: 'borrow-1' });
console.log(sent.result);                                                  // carries the proof code

POST/v1/loans/lend

Lender actions: open, fund, collect, claim, settle.

Needs the tokenPlan first · sends only with "confirm": true#

actionOther fieldsWhat it does
openpledgeBps (100–9000)Opens a slot; the result has the terms code
fundproofCode, optional principalUsdgLends exactly what the proof backs
claimloanRef, optional usdgTakes the unlocked share
collectloanRefTakes shares already sent to the loan
settleloanRefCloses the books

loanRef is the slot number as text, e.g. "7". To read slots use GET /v1/loans/lent.

macOS / Linux (bash)
TOKEN=$(cat ~/.h081/api-token)
API=http://127.0.0.1:8081/v1

# open a loan slot: first the plan (no "confirm"), then send it
curl -s -X POST -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{"action":"open","pledgeBps":2000}' $API/loans/lend
curl -s -X POST -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" -H "Idempotency-Key: open-1" \
  -d '{"action":"open","pledgeBps":2000,"confirm":true}' $API/loans/lend

# fund it with the borrower's proof code: the plan (exact principal, payout account), then "confirm": true
curl -s -X POST -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{"action":"fund","proofCode":"h081-loan-proof:…","principalUsdg":"10"}' $API/loans/lend
curl -s -X POST -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" -H "Idempotency-Key: fund-7" \
  -d '{"action":"fund","proofCode":"h081-loan-proof:…","principalUsdg":"10","confirm":true}' $API/loans/lend

# follow it, then claim the unlocked share (leave out "confirm" to see the plan first)
curl -s -H "Authorization: Bearer $TOKEN" "$API/loans/lent?loanRef=7"
curl -s -X POST -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" -H "Idempotency-Key: claim-7-1" \
  -d '{"action":"claim","loanRef":"7","confirm":true}' $API/loans/lend
Windows PowerShell
$h = @{ Authorization = "Bearer $(Get-Content $HOME\.h081\api-token)" }
$api = "http://127.0.0.1:8081/v1"

# open a loan slot: the plan, then send it
Invoke-RestMethod -Method Post -Headers $h -ContentType application/json -Body '{"action":"open","pledgeBps":2000}' "$api/loans/lend"
$h["Idempotency-Key"] = "open-1"
Invoke-RestMethod -Method Post -Headers $h -ContentType application/json -Body '{"action":"open","pledgeBps":2000,"confirm":true}' "$api/loans/lend"

# fund it with the borrower's proof code: the plan, then send it
Invoke-RestMethod -Method Post -Headers $h -ContentType application/json -Body '{"action":"fund","proofCode":"h081-loan-proof:…","principalUsdg":"10"}' "$api/loans/lend"
$h["Idempotency-Key"] = "fund-7"
Invoke-RestMethod -Method Post -Headers $h -ContentType application/json -Body '{"action":"fund","proofCode":"h081-loan-proof:…","principalUsdg":"10","confirm":true}' "$api/loans/lend"

# follow it
Invoke-RestMethod -Headers $h "$api/loans/lent?loanRef=7"
Python (requests)
import pathlib, requests

API = "http://127.0.0.1:8081/v1"
TOKEN = (pathlib.Path.home() / ".h081" / "api-token").read_text().strip()
H = {"Authorization": f"Bearer {TOKEN}"}

def lend(body, key=None):
    if key is None:                                   # the plan: nothing is sent
        return requests.post(f"{API}/loans/lend", json=body, headers=H).json()
    return requests.post(f"{API}/loans/lend", json={**body, "confirm": True},
                         headers={**H, "Idempotency-Key": key}).json()

print(lend({"action": "open", "pledgeBps": 2000})["result"])
opened = lend({"action": "open", "pledgeBps": 2000}, key="open-1")
print(opened["result"])                               # carries the terms code for the borrower

print(lend({"action": "fund", "proofCode": "h081-loan-proof:…"})["result"])   # the exact principal, the payout account
lend({"action": "fund", "proofCode": "h081-loan-proof:…"}, key="fund-7")

print(requests.get(f"{API}/loans/lent", params={"loanRef": "7"}, headers=H).json()["result"])
TypeScript (SDK)
import { H081 } from '@h081lll/agent';

const bank = await H081.open();

console.log((await bank.lend('open', { pledgeBps: 2000 })).result);       // the plan
const opened = await bank.lend('open', { pledgeBps: 2000 }, { confirm: true, idempotencyKey: 'open-1' });
console.log(opened.result);                                                // carries the terms code

const proofCode = 'h081-loan-proof:…';
console.log((await bank.lend('fund', { proofCode })).result);              // the plan
await bank.lend('fund', { proofCode }, { confirm: true, idempotencyKey: 'fund-7' });

console.log(await bank.lendStatus('7'));
await bank.lend('claim', { loanRef: '7' }, { confirm: true, idempotencyKey: 'claim-7-1' });

GET/v1/loans/lent

This agent's loan slots as a lender.

Needs the token#

Optional query loanRef for one slot. The same result as lend status.

With Claude

Claude can do either side. Opening and funding a loan return a plan first. The borrower's pledge is made as soon as Claude calls it, so ask Claude to explain the terms before it pledges. Nothing is borrowed until the lender funds.

Example conversation · wording variesAna, the lender
  1. You

    Open a loan slot where I get 20% of each payment. Show me the plan first.

  2. Claude

    uses h081_lend

    Plan, nothing sent yet: open a new loan slot owned by your agent wallet, with a 20% share of each payment. It costs a little SOL for the slot. No USDG moves until you fund it. Your agent wallet will be public as the lender.

    Open it?

  3. You

    Yes.

  4. Claude

    uses h081_lend

    Opened loan slot 7. Send this terms code to the borrower:

    h081-loan-terms:…

  5. You

    Ben sent back h081-loan-proof:…. Check it and fund it if it asks for 10 USDG.

  6. Claude

    uses h081_lend

    The proof is valid, for slot 7, backed by a budget pledged to you, and asks for exactly 10 USDG. Ben would receive 9.975 USDG after the 0.25% fee. Nothing sent yet. Fund it?

Example conversation · wording variesBen, the borrower
  1. You

    Ana sent me h081-loan-terms:…. What does it mean if I borrow 10 USDG, repaid over 30 days?

  2. Claude

    uses h081_loan_quote

    Ana takes 20% of each payment from the pledged budget. To borrow 10 USDG I would set aside a 50 USDG budget from your private balance, unlocking over 30 days. Ana gets her 10 back from it over those 30 days. You would receive 9.975 USDG in your agent wallet once she funds it.

    Shall I pledge it, with the budget living 60 days?

  3. You

    Go ahead.

  4. Claude

    uses h081_loan_borrow

    Pledged. Send this proof code to Ana. Nothing is borrowed until she funds it:

    h081-loan-proof:…

Next

New here? The 5-minute quickstart does a first private payment end to end. The package is @h081lll/agent on npm. Questions: @H081CAP on X.