Fund a balance
$PONCHO

Poncho for agents

Protocol poncho/1 · machine guide · https://poncho.cash/protocol/agents.md

On this page

Poncho lets a program pay for an API call with no account, no card and no subscription. A provider lists a route and a price on Robinhood Chain (chain id 4663). A person funds one USDG balance in the Poncho vault, gives your key a spending cap, and you spend that balance on every listed route. Each call is paid with a signature, not a transaction, so nothing you do waits on a block. The provider redeems your signed notes on chain later, in batches.

This file is generated from the site configuration and the contract source on every release, so it cannot drift from what the contracts enforce. Values that the owner can change after deploy (the fee, the pause) are named with the view that returns them: read the chain for the live value.

At a glance

  • Chain: Robinhood Chain, chain id 4663 (0x1237), blocks about every 0.1 s.
  • Settlement token: USDG, 6 decimals, 0x5fc5360D0400a0Fd4f2af552ADD042D716F1d168. Every amount in this protocol is USDG atoms.
  • Price discovery: the provider answers an unpaid request with HTTP 402 and a JSON quote.
  • Payment: an EIP-712 SpendNote signed by your key, sent in the X-Poncho-Note request header.
  • Settlement: the provider redeems notes on PonchoVault whenever it chooses, many in one transaction.
  • Protocol fee: 2% at deploy (feeBps() is 200), taken from the provider's side. It can never pass 5%.
  • Refunds: the provider may give a settled note back inside 7 days. After that a settlement is final.
  • Spend limit: an agent key spends only up to the cap per epoch its payer granted, and only until the grant expires. The vault enforces both.

Base URLs

WhatURL
This guide, Markdownhttps://poncho.cash/protocol/agents.md
This guide, web pagehttps://poncho.cash/protocol/agents
Protocol descriptor, JSONhttps://poncho.cash/.well-known/poncho.json
The same descriptor, served livehttps://poncho.cash/api/discover
Live contract counters, JSONhttps://poncho.cash/api/status
Read-only JSON-RPC proxy, for browsershttps://poncho.cash/api/rpc
Official RPC, full history, no CORShttps://rpc.mainnet.chain.robinhood.com
Public RPC, CORS, head state onlyhttps://robinhood-rpc.publicnode.com
Explorerhttps://robinhoodchain.blockscout.com
ABIshttps://poncho.cash/protocol/abi/vault.json, registry.json, staking.json, requests.json, erc20.json
Site map for machineshttps://poncho.cash/llms.txt

The proxy at /api/rpc refuses every write method and batches of more than 5 calls. A wallet broadcasts its own transactions. From a server, call the official RPC directly.

Contracts

Robinhood Chain, chain id 4663. Deployed at block 70,430,434 on 23 Sep 2026.

ContractAddressWhat it holdsABI
PonchoVault0x50bE744e956D9d64D1a0Fd5b899874BCFad54DeCEvery deposited USDG balance, the agent key grants, a receipt per settled note, provider earnings and the fees not yet swept.vault.json
PonchoRegistry0x32c38e7fD4dFB27F84de8A4FBE7417A65d5bACA2Every listed service and route, with its price in USDG atoms.registry.json
PonchoStaking0x327aDf6A1d6a1619702FD32f65a21FAf8C7D29f6Staked $PONCHO, the fee tiers, and the USDG fee share owed to stakers.staking.json
PonchoRequests0x5487d026cF1885588A082CBfAAeB6C362982bF2CBounties in escrow, who backed them, the proposals and the awards.requests.json
USDG0x5fc5360D0400a0Fd4f2af552ADD042D716F1d168The settlement token, 6 decimals. Not a Poncho contract.erc20.json
Multicall30xcA11bde05977b3631167028862bE2a173976CA11Batched reads through aggregate3. Not a Poncho contract.standard

The loop

  1. A person deposits USDG into PonchoVault once. That balance is what you spend.
  2. The person authorizes your key with grant(agent, capPerEpoch, epochLen, expiry). Your key signs and never holds funds. A person spending with their own address as the key needs no grant.
  3. You call a provider route. With no payment attached it answers 402 Payment Required and a JSON quote.
  4. You check the quote, then sign an EIP-712 SpendNote for exactly that quote. No transaction, no gas.
  5. You repeat the request with the note in the X-Poncho-Note header. The provider verifies it off chain, checks the balance and your cap with a read, serves the call and banks the note.
  6. The provider settles banked notes on chain. USDG moves from the payer's balance to the provider's earnings, the protocol keeps its fee, and the vault writes one Settled event per note.

1. Fund a balance

Approve USDG to the vault, then deposit. The deposit is the payer's; anyone may fund anyone with depositTo.

JavaScript
const amount = 25_000_000n;   // 25 USDG in atoms
await wallet.writeContract({ address: USDG, abi: erc20Abi, functionName: 'approve', args: [VAULT, amount] });
await wallet.writeContract({ address: VAULT, abi: vaultAbi, functionName: 'deposit', args: [amount] });

balanceOf(payer) is the spendable balance. withdraw(amount) takes back whatever is unspent at any time, even while the vault is paused.

2. Authorize an agent key

The key that signs notes should not be the wallet that holds the money. Generate a key for the agent and give it a cap that the vault enforces.

JavaScript
await wallet.writeContract({ address: VAULT, abi: vaultAbi, functionName: 'grant', args: [
  AGENT,
  5_000_000n,                                     // capPerEpoch: at most 5 USDG per epoch
  86_400,                                         // epochLen: seconds, from 1 hour to 30 days
  BigInt(Math.floor(Date.now() / 1000) + 30 * 86_400),   // expiry: unix seconds, in the future
] });

grantView(payer, agent) returns (capPerEpoch, spent, epochLen, epochEnds, expiry, available, live) with the epoch already rolled forward: available is what the key may still spend in this epoch and live is false once the grant has expired or was revoked. revoke(agent) deletes the grant at once. Granting to your own address reverts with SelfGrant, because a payer always spends its own balance without one.

A note signed before a revoke is still valid until its deadline if the grant is later restored. Ask providers for short deadlines.

3. Read the 402

An unpaid request to a listed route answers like this. The body is the quote.

HTTP
HTTP/1.1 402 Payment Required
Content-Type: application/json

{
  "error": "payment_required",
  "protocol": "poncho/1",
  "chainId": 4663,
  "vault": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC",
  "header": "X-Poncho-Note",
  "quote": {
    "provider": "0x6813Eb9362372EEF6200f3b1dbC3f819671cBA69",
    "serviceId": "1",
    "routeId": "1",
    "amount": "20000",
    "nonce": "126580979288226482122844773245177719890",
    "deadline": 1800000000
  }
}

These values are the ones of the worked example further down.

FieldTypeMeaning
errorstringpayment_required, or one of the codes under Errors when a note was sent and refused
protocolstringponcho/1
chainIdinteger4663
vaultaddressthe PonchoVault the note is signed against
headerstringwhere the signed note goes on the retry, X-Poncho-Note
quote.provideraddresswho is paid, and the only address that may redeem the note
quote.serviceIddecimal stringthe registry service, "0" when the route is not listed
quote.routeIddecimal stringthe registry route, "0" when the route is not listed
quote.amountdecimal stringUSDG atoms, 6 decimals. "20000" is 0.02 USDG
quote.noncedecimal stringany value the provider picks. The note hash is what is consumed, not the nonce
quote.deadlineintegerunix seconds. A note past its deadline cannot be settled
docsstringoptional link to the provider's documentation

Integers that can pass 2^53 are decimal strings because a JSON number cannot hold a uint256. X-Poncho-Quote is reserved for a provider that also repeats the quote object in a response header; the Poncho gate does not send it, so read the body.

Before you sign, check four things:

  • chainId is 4663 and vault is the vault in the contract table above. Refuse any other vault.
  • quote.amount is within your own per call ceiling. The registry lists a price per route, but the vault settles whatever amount you sign. Your check is the only one between a quote and your balance.
  • quote.provider is the owner of the service you meant to call: getService(serviceId).owner on the registry.
  • quote.deadline is in the future and not further out than you are willing to be charged.

The body's JSON schema, exactly:

JSON
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "title": "Poncho 402 payment required",
  "type": "object",
  "required": [
    "error",
    "protocol",
    "chainId",
    "vault",
    "quote"
  ],
  "properties": {
    "error": {
      "const": "payment_required"
    },
    "protocol": {
      "const": "poncho/1"
    },
    "chainId": {
      "type": "integer",
      "const": 4663
    },
    "vault": {
      "type": "string",
      "description": "The PonchoVault the note is signed against, 0x hex."
    },
    "header": {
      "type": "string",
      "default": "X-Poncho-Note",
      "description": "Where the signed note goes on the retry."
    },
    "docs": {
      "type": "string",
      "description": "Optional link to the provider documentation."
    },
    "quote": {
      "type": "object",
      "required": [
        "provider",
        "amount",
        "nonce",
        "deadline"
      ],
      "properties": {
        "provider": {
          "type": "string",
          "description": "Who is paid, and the only address that may redeem the note."
        },
        "serviceId": {
          "type": "string",
          "description": "PonchoRegistry service id, decimal. \"0\" when the route is not listed."
        },
        "routeId": {
          "type": "string",
          "description": "PonchoRegistry route id, decimal. \"0\" when the route is not listed."
        },
        "amount": {
          "type": "string",
          "description": "USDG atoms, 6 decimals, decimal string."
        },
        "nonce": {
          "type": "string",
          "description": "Any value the provider picks, decimal string. The note hash is what is consumed."
        },
        "deadline": {
          "type": "integer",
          "description": "Unix seconds after which the note cannot be redeemed."
        }
      }
    }
  }
}

4. Sign the note

The note is EIP-712 typed data.

  • Domain: name "Poncho", version "1", chainId 4663, verifyingContract the vault.
  • Primary type: SpendNote.
  • Type string, exactly:
Text
SpendNote(address payer,address agent,address provider,uint256 serviceId,uint256 routeId,uint256 amount,uint256 nonce,uint64 deadline)
  • SpendNote type hash, keccak256 of that string: 0x63b4fe9ae6466a2a5023863a71a673905241a0740072403cf45824e59209d77f
  • Domain type hash, keccak256 of EIP712Domain(string name,string version,uint256 chainId,address verifyingContract): 0x8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f
  • Digest: keccak256(0x1901 || domainSeparator || structHash). The vault keys its receipt by this digest, and hashNote(note) on the vault returns it, so you can check your encoder against the chain with one eth_call.
FieldTypeWhere it comes from
payeraddressthe balance you spend: the person who granted your key
agentaddressyour key, the signer. Equal to payer when a person pays with their own address
provideraddressquote.provider, unchanged
serviceIduint256quote.serviceId, unchanged
routeIduint256quote.routeId, unchanged
amountuint256quote.amount, unchanged
nonceuint256quote.nonce, unchanged
deadlineuint64quote.deadline, unchanged
JavaScript
const signature = await agent.signTypedData({
  domain: { name: 'Poncho', version: '1', chainId: 4663, verifyingContract: body.vault },
  types: { SpendNote: [
    { name: 'payer', type: 'address' }, { name: 'agent', type: 'address' }, { name: 'provider', type: 'address' },
    { name: 'serviceId', type: 'uint256' }, { name: 'routeId', type: 'uint256' }, { name: 'amount', type: 'uint256' },
    { name: 'nonce', type: 'uint256' }, { name: 'deadline', type: 'uint64' },
  ] },
  primaryType: 'SpendNote',
  message: {
    payer: PAYER, agent: agent.address, provider: body.quote.provider,
    serviceId: BigInt(body.quote.serviceId), routeId: BigInt(body.quote.routeId), amount: BigInt(body.quote.amount),
    nonce: BigInt(body.quote.nonce), deadline: BigInt(body.quote.deadline),
  },
});

The signature is 65 bytes, r || s || v, with v 27 or 28 and a low s. When agent is a contract (a smart account), the vault checks the signature with ERC-1271 instead of recovering it.

5. Retry with the note

Send the same request again with one extra header. Its value is the base64url encoding, without padding, of this JSON:

JSON
{"note":{"payer":"0x…","agent":"0x…","provider":"0x…","serviceId":"1","routeId":"1","amount":"20000","nonce":"…","deadline":"…"},"signature":"0x…"}

Every note field is a string: addresses as 0x hex, the five integers as decimal. The signature is lower case 0x hex. base64url keeps the value to one token that no proxy rewrites.

The provider decodes the header, recovers the signer, checks it is agent, checks the payer's balance and your key's remaining cap with a read, serves the call and banks the note. Retry a 402 once, with a note. A second refusal after a signed note is an answer, not a reason to loop.

Errors

What a provider running the Poncho gate answers. A refusal that still wants payment carries a fresh quote in the same body, so you can sign again without another round trip.

StatuserrorreasonWhat to do
402payment_requiredNo note was sent. Check the quote, sign it, retry once
402note_rejectedbad-noteThe header did not decode to a note. Encode it again
402note_rejectedbad-signatureThe signer is not note.agent. Check the domain, the type and the field order
402note_rejectedwrong-providernote.provider is not the service owner. Copy quote.provider unchanged
402note_rejectedwrong-amountnote.amount is not the route price. Sign the quoted amount
402note_rejectedwrong-routeserviceId or routeId is not the route you called
402note_rejectedwrong-deadlineThe deadline is further out than the provider accepts. Sign the quoted deadline
400quote_expiredThe deadline is too close for the provider to settle. Ask for a new quote
409note_spentThis note hash was already served or settled. Ask for a new quote
402insufficient_balanceThe payer's vault balance does not cover this and the unsettled notes before it. Deposit
402cap_exceededYour key's cap for this epoch is used up. Wait for the next epoch or ask for a larger cap
403grant_expiredYour key has no live grant from this payer. Ask the payer to authorize it again
503not_for_saleThe service is paused or the route is turned off
503chain_unavailableThe provider could not read the chain. Try again later

Worked example: one paid call

Every value below is computed, not typed. It is a test vector: check your encoder against it before you pay anyone. The keys are the well known test keys 1, 2 and 3. Never fund them. The vault address is a stand-in that no contract uses, so this note can never settle anywhere. A real note uses the vault in the contract table.

PartValue
payer, private key 0x…010x7E5F4552091A69125d5DfCb7b8C2659029395Bdf
agent, private key 0x…02, the signer0x2B5AD5c4795c026514f8317c7a215E218DcCD6cF
provider, private key 0x…030x6813Eb9362372EEF6200f3b1dbC3f819671cBA69
vault, the stand-in verifyingContract0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC

The agent calls a search route and gets the quote:

HTTP
GET /v1/search?q=graphene HTTP/1.1
Host: provider.example

HTTP/1.1 402 Payment Required
Content-Type: application/json

{
  "error": "payment_required",
  "protocol": "poncho/1",
  "chainId": 4663,
  "vault": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC",
  "header": "X-Poncho-Note",
  "quote": {
    "provider": "0x6813Eb9362372EEF6200f3b1dbC3f819671cBA69",
    "serviceId": "1",
    "routeId": "1",
    "amount": "20000",
    "nonce": "126580979288226482122844773245177719890",
    "deadline": 1800000000
  }
}

It fills in payer and agent and signs this typed data with eth_signTypedData_v4:

JSON
{
  "types": {
    "EIP712Domain": [
      {
        "name": "name",
        "type": "string"
      },
      {
        "name": "version",
        "type": "string"
      },
      {
        "name": "chainId",
        "type": "uint256"
      },
      {
        "name": "verifyingContract",
        "type": "address"
      }
    ],
    "SpendNote": [
      {
        "name": "payer",
        "type": "address"
      },
      {
        "name": "agent",
        "type": "address"
      },
      {
        "name": "provider",
        "type": "address"
      },
      {
        "name": "serviceId",
        "type": "uint256"
      },
      {
        "name": "routeId",
        "type": "uint256"
      },
      {
        "name": "amount",
        "type": "uint256"
      },
      {
        "name": "nonce",
        "type": "uint256"
      },
      {
        "name": "deadline",
        "type": "uint64"
      }
    ]
  },
  "primaryType": "SpendNote",
  "domain": {
    "name": "Poncho",
    "version": "1",
    "chainId": 4663,
    "verifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC"
  },
  "message": {
    "payer": "0x7E5F4552091A69125d5DfCb7b8C2659029395Bdf",
    "agent": "0x2B5AD5c4795c026514f8317c7a215E218DcCD6cF",
    "provider": "0x6813Eb9362372EEF6200f3b1dbC3f819671cBA69",
    "serviceId": "1",
    "routeId": "1",
    "amount": "20000",
    "nonce": "126580979288226482122844773245177719890",
    "deadline": "1800000000"
  }
}

The digest, step by step:

StepValue
domain separator0x1a667e504f67d4f60d0b51fb916b527da9bca80d8fdfbd2bbe21a695821a8eaa
struct hash0x9dabc24f8a3f9a33664209f4e85cd2ebce99f98e82709c9a17587a3438b108e6
digest, the note hash0x471de91944284d7133f5973fcfefd7a8b665df6d388ebb39ea5560f628c9b504
signature by the agent key0x02f9c2589ccaa441b1bda619c8adc11e60b1f7b826fb3eed6588e297e47da4b85b5e76d013292a7d8e20947f3846fb636e6d8db8a604dbc27fa38f073b14061a1b

The header carries this JSON:

JSON
{"note":{"payer":"0x7E5F4552091A69125d5DfCb7b8C2659029395Bdf","agent":"0x2B5AD5c4795c026514f8317c7a215E218DcCD6cF","provider":"0x6813Eb9362372EEF6200f3b1dbC3f819671cBA69","serviceId":"1","routeId":"1","amount":"20000","nonce":"126580979288226482122844773245177719890","deadline":"1800000000"},"signature":"0x02f9c2589ccaa441b1bda619c8adc11e60b1f7b826fb3eed6588e297e47da4b85b5e76d013292a7d8e20947f3846fb636e6d8db8a604dbc27fa38f073b14061a1b"}

The retry, with the header:

HTTP
GET /v1/search?q=graphene HTTP/1.1
Host: provider.example
X-Poncho-Note: eyJub3RlIjp7InBheWVyIjoiMHg3RTVGNDU1MjA5MUE2OTEyNWQ1RGZDYjdiOEMyNjU5MDI5Mzk1QmRmIiwiYWdlbnQiOiIweDJCNUFENWM0Nzk1YzAyNjUxNGY4MzE3YzdhMjE1RTIxOERjQ0Q2Y0YiLCJwcm92aWRlciI6IjB4NjgxM0ViOTM2MjM3MkVFRjYyMDBmM2IxZGJDM2Y4MTk2NzFjQkE2OSIsInNlcnZpY2VJZCI6IjEiLCJyb3V0ZUlkIjoiMSIsImFtb3VudCI6IjIwMDAwIiwibm9uY2UiOiIxMjY1ODA5NzkyODgyMjY0ODIxMjI4NDQ3NzMyNDUxNzc3MTk4OTAiLCJkZWFkbGluZSI6IjE4MDAwMDAwMDAifSwic2lnbmF0dXJlIjoiMHgwMmY5YzI1ODljY2FhNDQxYjFiZGE2MTljOGFkYzExZTYwYjFmN2I4MjZmYjNlZWQ2NTg4ZTI5N2U0N2RhNGI4NWI1ZTc2ZDAxMzI5MmE3ZDhlMjA5NDdmMzg0NmZiNjM2ZTZkOGRiOGE2MDRkYmMyN2ZhMzhmMDczYjE0MDYxYTFiIn0

The provider answers 200 with the result and banks the note. Later it redeems it with settle or in a batch. At the fee it pays on that day the payer's balance falls by 0.02 USDG, the provider's earnings rise by the amount less the fee, and receiptOf of the digest reads back with a non zero settledAt. The same note can never settle twice.

Settling, for a provider

Register once, add routes, then verify and bank notes as calls are paid.

  • settle(note, sig) redeems one note. settleMany(notes, sigs) redeems up to 128, and one note that fails reverts the whole batch.
  • trySettleMany(notes, sigs) is the batch to run: a note that fails on the payer's side is skipped with SettleSkipped(noteHash, payer, reason) and the rest settle. It returns (settledMask, settledAmount). A note naming another provider still reverts the batch.
  • Only note.provider may redeem a note. withdrawEarnings(amount) pulls what has settled.
  • refund(noteHash) gives the payer the gross amount back inside 7 days. Only the provider can call it.

settle checks, in this order: the vault is not paused; the caller is note.provider; the provider is not the zero address; the amount is above zero; the deadline has not passed; the note hash has never settled; the recovered signer is note.agent; when agent is not payer, the grant is live and the amount fits in what is left of the epoch; the payer's balance covers the amount.

A payer may withdraw between signing and settlement. That is allowed: the settlement then reverts, or is skipped by trySettleMany. Settle often, and check balances before you serve.

SettleSkipped reasons:

ReasonConstantThe check settle reverts withWhat it means
1ZERO_AMOUNTZeroAmountThe note is for zero USDG.
2AMOUNT_TOO_LARGEAmountTooLargeThat amount is above what the contract can hold.
3EXPIREDNoteExpiredThat note is past its deadline. Ask the provider for a new quote.
4ALREADY_SETTLEDNoteAlreadySettledThat note has already been redeemed.
5BAD_SIGNATUREBadSignatureThe signature does not match the agent key on the note.
6NO_GRANTNoGrantThat key has no grant on this balance.
7GRANT_EXPIREDGrantExpiredThat agent key has expired. Authorize it again.
8CAP_EXCEEDEDCapExceededThat is over what the key may spend this epoch.
9INSUFFICIENT_BALANCEInsufficientBalanceThe payer balance is too low for that note.

Contract errors

The vault reverts with custom errors. The selector is the first 4 bytes of the revert data.

ErrorSelectorWhat it means
InsufficientBalance(address,uint256,uint256)0xdb42144dThe payer balance is too low for that note.
NoGrant(address,address)0x6e67cd62That key has no grant on this balance.
GrantExpired(uint64)0x06bbb9b1That agent key has expired. Authorize it again.
CapExceeded(uint256,uint128,uint128)0x917462dfThat is over what the key may spend this epoch.
BadSignature(bytes32,address)0xe49c6ba0The signature does not match the agent key on the note.
NoteExpired(uint64)0x70495aa6That note is past its deadline. Ask the provider for a new quote.
NoteAlreadySettled(bytes32)0x03a5b4e0That note has already been redeemed.
NotProvider(address,address)0x344b0904Only the provider a note names may redeem or refund it.
ZeroAmount()0x1f2a2005Enter an amount above zero.
AmountTooLarge(uint256)0x08083b2aThat amount is above what the contract can hold.
EmptyBatch()0xc2e5347dSend at least one note.
BatchTooLarge(uint256,uint256)0xbb1cb70bThat is more rows than one call may carry.
LengthMismatch(uint256,uint256)0xab8b67c6The batch has a different number of notes and signatures.
EnforcedPause()0xd93c0665That is paused by the Poncho owner right now.
SelfGrant()0x0b67ee90A payer already spends its own balance. Grant to another key.
BadEpochLength(uint32,uint32,uint32)0x5c1f5e40The epoch has to be between one hour and thirty days.
BadExpiry(uint64)0x115d3d42Set an expiry in the future.
NoteNotSettled(bytes32)0xa358395fThat note was never redeemed, so there is nothing to give back.
NoteAlreadyRefunded(bytes32)0xe68b20d5That note has already been given back.
RefundWindowClosed(bytes32,uint64)0x25354f59The seven day refund window on that note has closed.
InsufficientEarnings(address,uint256,uint256)0xb9fc9860That is more than the earnings you can pull.

ABI fragments

Human readable fragments, the form viem parseAbi and ethers Interface take. The full ABIs are the JSON files listed under Base URLs.

PonchoVault, what an agent and a provider call:

Text
function balanceOf(address) view returns (uint256)
function deposit(uint256 amount)
function depositTo(address payer, uint256 amount)
function withdraw(uint256 amount)
function grant(address agent, uint128 capPerEpoch, uint32 epochLen, uint64 expiry)
function revoke(address agent)
function grantView(address payer, address agent) view returns (uint128 capPerEpoch, uint128 spent, uint32 epochLen, uint64 epochEnds, uint64 expiry, uint128 available, bool live)
function hashNote((address payer, address agent, address provider, uint256 serviceId, uint256 routeId, uint256 amount, uint256 nonce, uint64 deadline) note) view returns (bytes32)
function DOMAIN_SEPARATOR() view returns (bytes32)
function receiptOf(bytes32) view returns (address payer, address provider, uint128 amount, uint128 fee, uint64 settledAt, bool refunded)
function settle((address payer, address agent, address provider, uint256 serviceId, uint256 routeId, uint256 amount, uint256 nonce, uint64 deadline) note, bytes sig)
function settleMany((address payer, address agent, address provider, uint256 serviceId, uint256 routeId, uint256 amount, uint256 nonce, uint64 deadline)[] notes, bytes[] sigs)
function trySettleMany((address payer, address agent, address provider, uint256 serviceId, uint256 routeId, uint256 amount, uint256 nonce, uint64 deadline)[] notes, bytes[] sigs) returns (uint256 settledMask, uint256 settledAmount)
function refund(bytes32 noteHash)
function earningsOf(address) view returns (uint256)
function withdrawEarnings(uint256 amount)
function feeBps() view returns (uint16)
function feeBpsFor(address provider) view returns (uint16)
function quote(address provider, uint256 amount) view returns (uint256 fee, uint256 net)
function paused() view returns (bool)
event Settled(bytes32 indexed noteHash, address indexed payer, address indexed provider, address agent, uint256 serviceId, uint256 routeId, uint256 amount, uint256 fee)
event SettleSkipped(bytes32 indexed noteHash, address indexed payer, uint8 reason)
event Refunded(bytes32 indexed noteHash, address indexed payer, address indexed provider, uint256 amount, uint256 fee)

PonchoRegistry, reading the marketplace:

Text
function serviceCount() view returns (uint256)
function routeCount() view returns (uint256)
function getService(uint256 id) view returns ((address owner, uint64 createdAt, uint8 category, bool paused, bool flagged, string slug, string name, string summary, string endpoint, string docs))
function getServices(uint256 from, uint256 count) view returns (uint256[] ids, (address owner, uint64 createdAt, uint8 category, bool paused, bool flagged, string slug, string name, string summary, string endpoint, string docs)[] list)
function getRoute(uint256 id) view returns ((uint256 serviceId, uint128 price, bool active, string method, string path, string summary))
function getRoutes(uint256[] ids) view returns ((uint256 serviceId, uint128 price, bool active, string method, string path, string summary)[] list)
function routeIdsOf(uint256 serviceId) view returns (uint256[])
function idBySlug(string) view returns (uint256)
function servicesOf(address owner_) view returns (uint256[])

USDG and any ERC-20:

Text
function balanceOf(address account) view returns (uint256)
function allowance(address owner, address spender) view returns (uint256)
function approve(address spender, uint256 value) returns (bool)
function decimals() view returns (uint8)

Reading the marketplace

PonchoRegistry holds every service and route. Ids start at 1. getServices(from, count) returns up to 100 at a time; routeIdsOf(serviceId) and getRoutes(ids) fill in the routes. Batch these through Multicall3 at 0xcA11bde05977b3631167028862bE2a173976CA11 so the whole registry is one or two calls.

A service carries owner, createdAt, category, paused, flagged, slug, name, summary, endpoint and docs. A route carries serviceId, price (USDG atoms), active, method, path and summary. Categories are a fixed list: 0 Search, 1 Data, 2 Creative, 3 Developer, 4 Finance, 5 Commerce, 6 Social, 7 Compute, 8 Other.

Listing is permissionless. A service in the registry is not reviewed or endorsed by anyone. flagged means the Poncho site hides it; the chain keeps it and it can still be paid.

Bounties for what nobody sells

PonchoRequests is the demand side. post(title, detail, deadline, bounty) escrows at least 1 USDG, with a deadline from 1 day to 365 days out. Anyone may topUp(id, amount). A registry service owner calls propose(id, serviceId). The requester calls award(id, serviceId) while the request is open, and that service owner pulls the bounty less the fee with withdrawEarnings. After the deadline an open request can be reclaimed: reclaim(id) for the requester, then reclaimFor(id, backer) for each backer, which anyone may call.

Limits worth knowing

  • Amounts are atoms, as a decimal string or a bigint, never a float.
  • The fee comes out of the provider's side. The payer pays exactly the signed amount.
  • A provider that stakes $PONCHO pays a lower fee, up to 50% off, by tier. Until the token is set on PonchoStaking every discount is zero.
  • An epoch lasts from 1 hour to 30 days. A grant's expiry must be in the future when it is set.
  • While the vault is paused, deposits, grants and settlements stop. Withdrawals, revokes, refunds and earnings withdrawals never stop.
  • eth_getLogs on this chain works over the full range with topic filters but fails past 10,000 matches: split the range. Log entries carry blockTimestamp: 0x0, so read timestamps from the blocks.
  • There is no archive state and no debug_trace* on this chain.
  • The contracts are not audited.

Connect a wallet

Poncho never holds your keys. Connecting shares your address only.