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.
- The lender opens a loan slot and chooses their share of each payment. They get a terms code and send it to the borrower.
- The borrower pledges a budget against those terms. This makes a proof code, which they send back.
- The lender funds the loan with that proof code. The money goes to the borrower straight away.
- The lender is repaid from their share of the budget, as it unlocks, until the loan is paid back.
| Role | Commands | Pays from |
|---|---|---|
| Lender | lend open, lend fund, lend status, lend claim, lend collect, lend settle | The USDG in the agent wallet itself (public), not the private balance |
| Borrower | loan quote, loan borrow | The 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
| Code | Starts with | Made by | Who gets it | What it says |
|---|---|---|---|---|
| Terms code | h081-loan-terms: | lend open | The borrower | Which loan slot, and the lender's share of each payment |
| Proof code | h081-loan-proof: | loan borrow | The lender | Proof 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:
npx -y @h081lll/agent check h081-loan-terms:…valid loan-terms code for mainnet-betaA 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
| Item | Example |
|---|---|
| Lender's share | --pledge-bps 2000 = 20% |
| Loan (principal) | 10 USDG |
| Budget the borrower pledges | 10 × 10000 ÷ 2000 = 50 USDG |
| Origination fee (0.25%, to the protocol) | 0.025 USDG |
| Borrower receives | 9.975 USDG |
| Budget unlocks over | --unlock-days 30: about 1.67 USDG a day |
| Lender's share unlocking | 20% 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.
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 2000what 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 --yesThe result gives the slot number (for example 7) and the terms code. Ana sends the terms code to Ben.
Ben checks the terms
Ben npx -y @h081lll/agent check h081-loan-terms:…what it prints valid loan-terms code for mainnet-betaBen · read the lender's share and whether the slot is still open npx -y @h081lll/agent loan quote h081-loan-terms:…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 --yesBen · 1 · see the plan (nothing is sent) npx -y @h081lll/agent loan borrow h081-loan-terms:… --budget 50 --days 60 --unlock-days 30Ben · 2 · send it npx -y @h081lll/agent loan borrow h081-loan-terms:… --budget 50 --days 60 --unlock-days 30 --yesIt 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.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-betaBefore 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 10is 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 10Ana · 2 · send it npx -y @h081lll/agent lend fund h081-loan-proof:… --principal 10 --yesBen 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 --freshAna gets repaid over the next 30 days
lend statusshows 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 7part 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", … } ] }claimtakes her share of the budget that has unlocked so far.collecttakes 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 --yesAna settles when it is repaid
Settling closes the books on the loan. After it, no shortfall can be reported, so settle only once
lend statusshows nothing outstanding.Ana · 1 · see the plan (nothing is sent) npx -y @h081lll/agent lend settle 7Ana · 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.
npx -y @h081lll/agent loan borrow h081-loan-terms:… --principal 10 --days 60 --unlock-days 30 --yesPricing 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.
8JBd7epQXxdcKRMaWM1S5PC7nsQKoZq3uCetpiJ684g4- The software adds two more guards:
loan borrow --principalrefuses a budget bigger than the loan needs, and a lender's claim is sized to what is still owed. lend statusshowsborrowerExcessUsdg: 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
| Step | Public on Solana | Stays 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 grant | The budget, its rules and the borrower's balance |
| Spending the rest of the budget | As 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).
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.
| Field | Type | Meaning |
|---|---|---|
termsCode | text, required | The lender's h081-loan-terms: code |
days | number, required | How long the pledged budget lives |
unlockDays | number, required | How long it takes to unlock (not more than days) |
principal | text | The loan, in USDG; the lender must fund exactly this |
budget | text | Or the pledged budget, in USDG |
dailyCap, maxPerTx | text | Rules for spending the rest of the budget |
agentIndex | number 0–15 | Which sub-account holds the budget (default: the first free one) |
confirm | true / false | Without true: the plan only |
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$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"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 lenderconst 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 codePOST/v1/loans/lend
Lender actions: open, fund, collect, claim, settle.
action | Other fields | What it does |
|---|---|---|
open | pledgeBps (100–9000) | Opens a slot; the result has the terms code |
fund | proofCode, optional principalUsdg | Lends exactly what the proof backs |
claim | loanRef, optional usdg | Takes the unlocked share |
collect | loanRef | Takes shares already sent to the loan |
settle | loanRef | Closes the books |
loanRef is the slot number as text, e.g. "7". To read slots use GET /v1/loans/lent.
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$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"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"])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.
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.
- You
Open a loan slot where I get 20% of each payment. Show me the plan first.
- Claude
uses
h081_lendPlan, 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?
- You
Yes.
- Claude
uses
h081_lendOpened loan slot 7. Send this terms code to the borrower:
h081-loan-terms:…
- You
Ben sent back h081-loan-proof:…. Check it and fund it if it asks for 10 USDG.
- Claude
uses
h081_lendThe 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?
- You
Ana sent me h081-loan-terms:…. What does it mean if I borrow 10 USDG, repaid over 30 days?
- Claude
uses
h081_loan_quoteAna 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?
- You
Go ahead.
- Claude
uses
h081_loan_borrowPledged. Send this proof code to Ana. Nothing is borrowed until she funds it:
h081-loan-proof:…
Next
- BasicsAllowancesBudgets with rules, the building block under every loan.
- SafetyPrivacy and safetyWhat the chain can see, and how to keep your key safe.
- ReferenceErrors and fixesEvery error code and what to do.
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.