# Remesso SDK documentation > Recurring and agent payments on Celo, in TypeScript. Every page of https://docs.remesso.xyz for remesso-sdk 0.3.1, in the order of its sidebar. Each page starts with a level-1 heading and its URL. # Introduction > What remesso-sdk is, what you can build with it, and how a payment moves from a signature to a webhook. Source: https://docs.remesso.xyz/docs `remesso-sdk` is a TypeScript library for payments in stablecoins on Celo, between people and between agents. A payer signs once and Remesso's executor sends each payment when it is due; an agent pays from its own wallet or within a policy a person set; and the recipient checks every payment on the chain and hears about it from a signed webhook. To build one agent that sells and another that pays it, start with [Build an agent-to-agent app](https://docs.remesso.xyz/docs/guides/agent-to-agent). For several workers, signed invoices and recovery after a crash, see [Run an agent service in production](https://docs.remesso.xyz/docs/guides/production-sdk). ```bash npm install remesso-sdk viem ``` ## What you can build - **Payments on a schedule.** A payer authorises a fixed amount to a fixed address on a fixed cadence: a monthly retainer, a weekly allowance, payroll, a remittance home. Remesso sends each payment when it is due. Nobody can change the recipient, the amount or the cadence afterwards; cancelling is the only edit. - **Payments by an agent, within limits.** A person grants their AI agent's key a policy: one token, a cap per payment and per day, an expiry, optionally a list of the only recipients allowed. The agent pays from the person's wallet, and the contract refuses anything outside the policy. A payment retried with the same idempotency key pays once. - **Getting paid.** Ask a wallet to pay you with a payment request, share a link that opens the payer's form filled in, and see when the payment arrives. - **Knowing you were paid.** Payment events and signed webhooks tell you about every payment to an address, without running a node or an indexer. - **Credit for the volume you bring.** Your own ERC-8021 builder code rides on every payment of the schedules your app creates. ## How a payment moves ```mermaid sequenceDiagram autonumber participant P as Payer's wallet participant C as Remesso contract participant E as Remesso executor participant R as Recipient participant W as Your webhook P->>C: approve and createSchedule (signed once) loop each time a payment is due E->>C: executeRun(schedule) C->>R: the amount, less the 0.25% fee E-->>W: payment.received (signed) end Note over W: payment.finalized follows once Celo finalizes the block ``` The payer's signature is the authority. The contract checks every payment against what was signed: the recipient, the amount, the cadence, the number of payments and the expiry. Remesso's executor can only ask the contract to send a payment that is due; it cannot redirect one, enlarge one or send one early unless the payer allowed it. ## The building blocks | | What it is | Who signs | Who pays gas | |---|---|---|---| | [Schedules](https://docs.remesso.xyz/docs/guides/schedules) | A payer's standing instruction, held by the `RemessoExecutorV5` contract | The payer, once | The payer to create it, Remesso's executor for each payment | | [Agent payments](https://docs.remesso.xyz/docs/guides/agent-payments) | Spending limits a person grants an agent, held by `RemessoAgentAllowance` | The person for the policy, the agent for each payment | The agent | | [Payment requests](https://docs.remesso.xyz/docs/guides/payment-requests) | "Please pay me", with states, stored by Remesso's backend | Nobody: a request moves no money | Nobody | | [Payment events](https://docs.remesso.xyz/docs/guides/payment-events) and [webhooks](https://docs.remesso.xyz/docs/guides/webhooks) | Every payment to or from an address, read from the chain by Remesso's indexer | Nobody | Nobody | ## Before you start - You need Node 20 or later, and a little USDT on Celo for your first payment. - The SDK runs on a server or in a script with a private key. In a browser it works with the user's own wallet. - Wallet-funded sends and schedules follow the executor's per-run limits. Policy payments follow their signed spending caps. See [fees, limits and gas](https://docs.remesso.xyz/docs/guides/fees-and-gas). ## Where to go next - **[Quickstart](https://docs.remesso.xyz/docs/quickstart):** send a payment and receive its webhook in about 15 minutes. - **[Core concepts](https://docs.remesso.xyz/docs/concepts):** schedules, the executor, policies, requests, events and finality. - **[Use cases](https://docs.remesso.xyz/docs/use-cases):** subscriptions, payroll, remittances, an allowance and an AI agent's budget, built end to end. - **[API reference](https://docs.remesso.xyz/docs/reference/client):** every option, method and return value. - **[Build with AI](https://docs.remesso.xyz/docs/build-with-ai):** these docs as a skill for Claude Code, Codex and Cursor, or as one text file for any chat. --- # Quickstart > Send a 1 USDT payment on a schedule and receive its signed webhook, from an empty folder, in about 15 minutes. Source: https://docs.remesso.xyz/docs/quickstart This walkthrough takes you from an empty folder to a real payment and the signed webhook that reports it. It takes about 15 minutes, most of it waiting for the payment to be picked up. You will run two programs: a **receiver**, which serves a webhook endpoint, and a **payer**, which creates a schedule that pays the receiver 1 USDT once. ## What you need - Node 20 or later. - About **1.05 USDT on Celo** in a wallet you control, for the payment and the fee. A little CELO for gas helps; without it, the SDK pays gas in USDT. - `cloudflared`, to give your laptop a public HTTPS address for the webhook ([install it](https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/downloads/)). Any public HTTPS URL works instead. ## 1. Create the project The SDK is ES modules only, and `tsx` runs TypeScript files without a build step. ```bash mkdir remesso-quickstart && cd remesso-quickstart npm init -y && npm pkg set type=module npm install remesso-sdk viem npm install -D tsx ``` ## 2. Make two keys One key pays and the other receives. Save this as `keys.ts` and run `npx tsx keys.ts` twice: ```ts title="keys.ts" import { generatePrivateKey, privateKeyToAccount } from "viem/accounts"; const key = generatePrivateKey(); console.log(key, privateKeyToAccount(key).address); ``` Send about 1.05 USDT to the payer's address on Celo. The receiver needs nothing: receiving costs no gas. Then export the keys in each terminal you use: ```bash export PAYER_KEY=0x… RECEIVER_KEY=0x… RECEIVER=0x… ``` ## 3. Run the receiver In a first terminal, open a public address that forwards to port 8787. A Cloudflare quick tunnel needs no account and prints a `trycloudflare.com` URL: ```bash cloudflared tunnel --url http://localhost:8787 ``` In a second terminal, save this as `receiver.ts`. It serves the webhook endpoint and prints each payment it is told about: ```ts title="receiver.ts" import { createServer } from "node:http"; import { createRemesso, formatAmount, webhookHandler } from "remesso-sdk"; import { toNodeListener } from "remesso-sdk/node"; import { privateKeyToAccount } from "viem/accounts"; const receiver = createRemesso({ account: privateKeyToAccount(process.env.RECEIVER_KEY as `0x${string}`), }); // Remesso returns the signing secret once your endpoint has answered its // challenge, so the handler reads it when each delivery arrives. let secret: string | undefined; const handler = webhookHandler({ recipient: receiver.address, secret: () => secret, onPayment: (m) => console.log(m.type, formatAmount(m.data.amount, m.data.token), m.data.final ? "final" : "not final yet", m.data.txHash), }); const server = createServer(toNodeListener(handler)); await new Promise((done) => server.listen(8787, done)); const sub = await receiver.webhooks.subscribe({ url: process.env.PUBLIC_URL! }); secret = sub.secret; // returned once: store it if the receiver must survive a restart console.log("subscribed", sub.subscription, "for", receiver.address); ``` Run it with the tunnel's URL: ```bash PUBLIC_URL=https://….trycloudflare.com npx tsx receiver.ts ``` When you subscribe, Remesso first posts a challenge to your URL. The handler answers it only for the address you configured, and Remesso then returns the signing secret, once. You should see `subscribed`. > **Tip** > > A quick tunnel gets a new URL every time it starts, and an address can hold five subscriptions. Remove old ones with `receiver.webhooks.revoke({ url })`. ## 4. Pay In a third terminal, save this as `pay.ts` and run `npx tsx pay.ts`. It sends two transactions: an approval for the contract, then the schedule itself, one payment of 1 USDT. ```ts title="pay.ts" import { createRemesso, formatAmount } from "remesso-sdk"; import { privateKeyToAccount } from "viem/accounts"; const payer = createRemesso({ account: privateKeyToAccount(process.env.PAYER_KEY as `0x${string}`), }); const s = await payer.schedules.create({ to: process.env.RECEIVER!, amount: "1", token: "USDT", every: "week", runs: 1, }); console.log("schedule", s.id, "pays", formatAmount(s.recipientGets, "USDT"), s.hash); ``` The output shows the schedule's id and what the recipient gets: `0.9975 USDT`, which is 1 USDT less the 0.25% fee. ## 5. Watch the payment arrive Go back to the receiver's terminal and wait. 1. Remesso finds a schedule created outside its own app within about **5 minutes**, and sends the payment within a minute of that. 2. A minute or two later the handler prints `payment.received` with `not final yet`: 0.9975 USDT. 3. About **25 to 30 minutes** later, `payment.finalized` follows, once Celo has finalized the block. A delivery that fails is retried with the same `webhook-id` for 24 hours, so a real handler deduplicates on it. ## No public URL? Poll instead The same information is available without a webhook. Payment events come from Remesso's indexer, and `watch` yields each new one: ```ts title="watch.ts" import { createRemesso, formatAmount } from "remesso-sdk"; import { privateKeyToAccount } from "viem/accounts"; const receiver = createRemesso({ account: privateKeyToAccount(process.env.RECEIVER_KEY as `0x${string}`), }); for await (const e of receiver.events.watch({ role: "to", kinds: ["run", "policy_payment"] })) { console.log(e.kind, formatAmount(e.amount!, e.token!), e.final ? "final" : "not final yet", e.txHash); } ``` ## What just happened - The payer **signed a mandate**: one payment of 1 USDT to the receiver. The contract holds it, and nothing can change its recipient or amount. - Remesso's **executor** sent the payment when it was due. It paid the gas; the payer paid only for the approval and the schedule. - Remesso's **indexer** read the payment from the chain and delivered a **signed webhook**, then a second one when the block became final. ## Next steps - [Core concepts](https://docs.remesso.xyz/docs/concepts) explains schedules, the executor and finality in more depth. - [Pay on a schedule](https://docs.remesso.xyz/docs/guides/schedules) covers cadences, triggers, pausing and cancelling. - [Webhooks](https://docs.remesso.xyz/docs/guides/webhooks) shows how to run the handler in Express, Next.js, Deno or Cloudflare Workers. - [Use cases](https://docs.remesso.xyz/docs/use-cases) builds complete products on these pieces. --- # Installation and setup > Install remesso-sdk, create a client for a server, a browser wallet or read-only use, and keep its keys safe. Source: https://docs.remesso.xyz/docs/installation This page covers installing the SDK, the three ways to create a client, and how to handle the keys it signs with. ## Requirements - **Node 20 or later.** The SDK also runs in Deno through `npm:` specifiers. - **ES modules.** The package is ESM. In a Node project, set `"type": "module"` in `package.json` or use `.mjs` and `.mts` files. Node 20.19 and 22.12 or later can also `require()` it from CommonJS. - **viem 2.21 or later** as a peer dependency. The SDK signs and reads through viem, and accepts viem accounts and clients. - **Celo mainnet.** The SDK refuses to sign on any other chain. There is no testnet. ## Install **npm** ```bash npm install remesso-sdk viem ``` **pnpm** ```bash pnpm add remesso-sdk viem ``` **yarn** ```bash yarn add remesso-sdk viem ``` **Deno** ```ts import { createRemesso } from "npm:remesso-sdk"; ``` The package has two entry points: | Import | For | |---|---| | `remesso-sdk` | Everything that runs anywhere: the client, webhook verification, token and fee helpers. | | `remesso-sdk/node` | Node only: `fileStore` (agent payment records on disk), `safeFetchText` and `toNodeListener`. | ## Create a client Every feature hangs off one client, created with `createRemesso`. What you pass decides what it can do. ### With a private key On a server, in a script or in an agent, give it a viem account. The client can then pay, create schedules, sign requests and subscribe to webhooks. ```ts title="client.ts" import { createRemesso } from "remesso-sdk"; import { privateKeyToAccount } from "viem/accounts"; const remesso = createRemesso({ account: privateKeyToAccount(process.env.REMESSO_KEY as `0x${string}`), }); console.log(remesso.address); // the account's address ``` ### With a browser wallet In a web app, pass the user's own wallet as a viem wallet client on Celo. The user approves each transaction in their wallet. Agent payments are the exception: they are signed before they are sent, so they need a local account like the one above. ```ts title="browser.ts" import { createRemesso } from "remesso-sdk"; import { createWalletClient, custom, type EIP1193Provider } from "viem"; import { celo } from "viem/chains"; declare const ethereum: EIP1193Provider; // window.ethereum, injected by the wallet const [address] = await ethereum.request({ method: "eth_requestAccounts" }); const wallet = createWalletClient({ account: address, chain: celo, transport: custom(ethereum) }); const remesso = createRemesso({ walletClient: wallet }); ``` ### Read-only Without an account, the client reads: schedules, policies, requests, payment events and agents. Methods that need a signer throw a `no_account` error, and methods that default to "this account" need an address. ```ts title="reader.ts" import { createRemesso } from "remesso-sdk"; const reader = createRemesso(); const schedule = await reader.schedules.get(4); const { events } = await reader.events.list({ address: schedule.to, role: "to" }); console.log(schedule.runsExecuted, "of", schedule.maxRuns, "payments sent;", events.length, "events"); ``` ## Options The options you will use most: | Option | Default | What it does | |---|---|---| | `account` | none | A viem account that signs. | | `walletClient` | none | A viem wallet client instead of an account. | | `rpcUrl` | `https://forno.celo.org` | A Celo mainnet RPC. The SDK checks it answers chain 42220 before signing anything. | | `store` | memory | Where agent payments are recorded so a retry pays once. Use `fileStore()` in production. | | `builderCodes` | none | Your own ERC-8021 codes. See [builder codes](https://docs.remesso.xyz/docs/guides/builder-codes). | Every option is in the [client reference](https://docs.remesso.xyz/docs/reference/client#createremessooptions). ## Keys and safety The SDK signs with whatever key you give it, and Remesso has no way to reverse a payment. - **Keep keys out of code.** Read them from the environment or a secrets manager, and never commit them. - **One key per role.** The wallet that pays on a schedule, the agent's key and the receiving address do different jobs. Keeping them apart limits what one leaked key can do. - **Hold only what you need.** A schedule spends through an ERC-20 approval, and an agent spends within its policy. Both are limits you set: keep them close to what you intend to pay. - **The receiving key is optional.** Receiving needs only an address. You need its key only to subscribe to webhooks, answer or decline requests, or sign as that address. ## Check your setup This reads the chain and Remesso's backend without signing anything: ```ts title="check.ts" import { CHAIN_ID, createRemesso, EXECUTOR, FEE_BPS } from "remesso-sdk"; const reader = createRemesso(); console.log("chain", CHAIN_ID, "executor", EXECUTOR, "fee", FEE_BPS / 100, "%"); console.log("rpc answers chain", await reader.publicClient.getChainId()); ``` ## Next steps - [Core concepts](https://docs.remesso.xyz/docs/concepts): how schedules, the executor and finality fit together. - [Quickstart](https://docs.remesso.xyz/docs/quickstart): a first payment and its webhook. --- # Build with AI > Give Claude Code, Codex, Cursor or any AI assistant the whole Remesso documentation, as a skill or as one text file. Source: https://docs.remesso.xyz/docs/build-with-ai Every page of this documentation is also published for AI assistants. Install the Remesso skills and your coding assistant reads the right page before it writes Remesso code, or give any chat the whole documentation as one text file. ## What's available | | What it is | Use it with | |---|---|---| | The `remesso-sdk` skill | Every page of this documentation, and the rules for writing code with the SDK | Claude Code, Codex, Cursor and other assistants that load skills | | [`llms-full.txt`](https://docs.remesso.xyz/llms-full.txt) | The whole documentation in one text file | Any chat: attach the file or give it the URL | | [`llms.txt`](https://docs.remesso.xyz/llms.txt) | An index of the pages, each linked as Markdown | Tools that read [llms.txt](https://llmstxt.org) | | Each page as Markdown | The page's URL with `.md` added, such as [`/docs/build-with-ai.md`](https://docs.remesso.xyz/docs/build-with-ai.md) | Putting one page into a prompt | All of them are generated from these pages each time the documentation changes, so they describe the same version of the SDK. ## Install the skills ### Claude Code Add Remesso's plugin. It carries the skills and the Remesso wallet: ```text /plugin marketplace add https://bitbucket.org/crackedstudios/remesso.git /plugin install remesso@remesso ``` To add only the skills, copy them into Claude Code's skills folder: ```bash npx -y remesso-wallet skills ~/.claude/skills ``` ### Codex, Cursor and others ```bash npx -y remesso-wallet skills ``` This copies the skills to `~/.agents/skills`, where Codex and Cursor look. Give it a folder to put them somewhere else, such as a project's own skills folder. Restart the assistant to load them, and run the command again to update them. ### Any chat Give the assistant `https://docs.remesso.xyz/llms-full.txt`, or attach the file. It holds every page, with the links between pages as full URLs. ## The skills | Skill | For | |---|---| | `remesso-sdk` | Writing and reviewing code that uses `remesso-sdk` | | `remesso` | An agent that pays and gets paid through Remesso's endpoints and contracts directly, without the SDK | | `remesso-x402` | Paying Remesso's paid HTTP services over x402 | | `remesso-wallet` | The tools of the Remesso wallet, described below | An assistant loads a skill when your request matches it, so you don't need to name one. ## Prompts to try - "Using remesso-sdk, ask 0x… to pay 5 USDT every week for four weeks, and print the link to send them." - "Add a route to my Express server that receives Remesso webhooks and marks an order paid only after verifying the payment on the chain." - "My agent sells translations. When another agent pays my invoice from its own wallet, deliver the order exactly once, and make retries safe." - "Start a local fork with `npx remesso-sdk dev` and run my payment code against it." ## Let the assistant pay, too The skills help an assistant write code. The Remesso wallet lets an assistant make payments itself: it is an MCP server with tools such as `pay`, `request_payment` and `pay_x402`, and it spends only within a [spending policy](https://docs.remesso.xyz/docs/guides/agent-payments) you sign on-chain, with a cap per payment and per day and an expiry. You set the policy at [remesso.xyz/agent-wallet](https://remesso.xyz/agent-wallet). It comes with the Claude Code plugin above. In other MCP hosts, add it as a server that runs: ```bash npx -y remesso-wallet ``` --- # Core concepts > Schedules, the executor, triggers, fees, agent policies, payment requests, payment events, finality and webhooks, and how they fit together. Source: https://docs.remesso.xyz/docs/concepts Remesso has a small number of moving parts. This page explains each one and what it guarantees, so the guides that follow read as variations on the same model. ## Celo mainnet, stablecoins only Remesso runs on Celo mainnet (chain id `42220`) and nowhere else. Payments are in stablecoins and local-currency tokens: USDT, USDC, USDm and USA₮ (US dollars), Ripio's wARS, wBRL, wCOP, wMXN, wCLP and wPEN (Argentine, Colombian, Mexican and Chilean pesos, Brazilian reais and Peruvian soles), and cNGN (Nigerian naira). Tokens have different decimals: USDT has 6 and wARS has 18. The SDK takes amounts as decimal strings, like `"2.50"`, and returns base units as `bigint`. The full list is in [tokens and limits](https://docs.remesso.xyz/docs/reference/tokens-and-limits). ## Schedules: a payer signs once A **schedule** is a standing instruction a payer signs on-chain: this amount, to this address, this often, this many times. It lives in the `RemessoExecutorV5` contract, created with two transactions from the payer's wallet: an ERC-20 approval for the contract, then the schedule itself. What the payer signs is final: - **The recipient, the amount and the cadence never change.** Not by Remesso, not by the contract's owner, not by the payer. Cancelling is the only edit. - **It ends.** A schedule has a number of payments and an expiry, at most a year after it starts. - **It can be paused and resumed** by the payer, and **cancelled** for good. - **It spends through the approval.** Revoking the approval stops every future payment, so the payer always keeps a way out. A payment is gone once it is sent. Remesso can't reverse it, and neither can the payer. ## The executor sends each payment Remesso's **executor** is a backend service with a key of its own. Every minute it finds schedules that are due and asks the contract to run them. The contract checks each request against what the payer signed. The executor pays the gas for each payment; the payer pays none after creating the schedule. A few rules shape when a payment goes out: - **The first payment follows creation within minutes.** A schedule created in the Remesso app is known at once. One created anywhere else, such as with the SDK, is discovered by a sync that runs every five minutes. - **Each payment must be worth between 1 and 1,000 dollars**, or the same in local currency. A schedule outside those bounds is valid on-chain but the executor never runs it, so the SDK refuses one before you sign. See [fees, limits and gas](https://docs.remesso.xyz/docs/guides/fees-and-gas). - **A payment that can't go out waits.** If the payer's balance or approval is short when a payment is due, the executor skips it, and it goes out once the payer tops up. Payments aren't caught up in a burst. ## Triggers: bringing a payment forward A payer can grant a **trigger**: an address allowed to bring the next payment forward. The payment is the same one, with the same recipient and amount. A trigger moves a payment earlier and never adds one. Granting `"remesso"` lets Remesso's executor do it, which is what makes `trigger-run` (a paid early send) usable for that schedule. ## Fees Remesso takes **0.25% of each scheduled payment**, out of the payment and rounded up. When a payer sends 1 USDT, the recipient receives 0.9975. The fee is pinned in each schedule when it is signed. Agent payments carry no fee, and receiving costs nothing. When you want the recipient to get an exact amount, ask for it with `receive` and the SDK works out what to send. Payment requests work the same way: a requested amount is what the recipient receives. ## Agent policies: spending within limits A **policy** lets a person's agent pay from the person's wallet, within limits the person sets. It lives in the `RemessoAgentAllowance` contract, which has no owner and no admin. A policy has: - one token; - a cap per payment, and a cap per day: a 24-hour window that opens with the first payment after the previous window has closed; - an expiry; - optionally, a list of the only addresses the agent may pay; - a budget: the person's token approval for the allowance contract, shared by their policies for that token. The agent's key calls `pay` and pays its own gas. The contract refuses any payment outside the policy, and the person can revoke the policy at any time. **Idempotency** keeps retries safe. Give each payment you intend an idempotency key, such as an invoice id. A retry with the same key reports the first attempt's outcome and never pays twice, even if the first attempt timed out after it was sent. Without a key, an identical payment within ten minutes is refused. ## Payment requests: asking to be paid A **payment request** asks a wallet to pay you: how much, in which token, how often. It costs nothing to send. It appears in the payer's Remesso inbox and comes with a link that opens their payment form filled in. A request can't move money: the payer still signs in their own wallet. A request has a state: | State | Meaning | |---|---| | `pending` | Waiting for the payer. Requests stay live for 30 days. | | `accepted` | A schedule or payment answered it on the requested terms. | | `accepted_with_changes` | It was answered, but some terms differ from what was asked. | | `declined` | The payer, or their agent, turned it down with a signature. | | `expired` | Not answered in time. | **Accepted is not paid.** A request counts as paid only when a payment with a transaction is recorded against it. ## Payment events and finality Remesso's **indexer** reads the contracts every minute and records each payment as a **payment event**: payments sent on a schedule, schedules created, agent payments, and policies created or revoked. You can list them by address, or have them delivered as webhooks. Two fields matter most: - **`final`** turns true once the block is at or below Celo's finalized block, about 25 to 30 minutes after the payment. Before that, a payment is very likely to stand but not yet certain. Ship goods or grant access on `final` when the amount warrants it. - **`removed`** turns true if a chain reorganisation undid the block. The event is kept and marked, never deleted. Every change to an event gives it a new `seq`, so reading from your last `seq` misses nothing. ## Webhooks A **webhook** subscription sends an address's payment events to your HTTPS endpoint as they happen. Deliveries follow the Standard Webhooks format and are signed with a secret only you and Remesso know. There are three types: `payment.received`, `payment.finalized` and `payment.removed`. Delivery is at least once: a failed delivery is retried with the same `webhook-id` for 24 hours. Deduplicate on that id. ## Builder codes Celo credits on-chain activity to the app that caused it, through ERC-8021 attribution codes on each transaction. Every transaction the SDK sends carries Remesso's codes. Add your own **builder code** and it rides along too, including on every payment Remesso sends for the schedules your app creates. See [builder codes](https://docs.remesso.xyz/docs/guides/builder-codes). ## Reads can lag behind writes Celo's public RPC is several nodes behind a load balancer. For a few seconds after a transaction lands, a read can come from a node that hasn't seen it. The SDK waits for things it has just created to become visible, and retries an answer to a request while the backend can't see the schedule yet. In your own code, expect a balance or a status read straight after a transaction to be a block behind, and read again. ## What is on-chain and what isn't | On-chain, enforced by contracts | Off-chain, run by Remesso | |---|---| | Schedules and everything they pin | The executor that sends due payments | | Agent policies and their limits | Payment requests and their states | | Every payment, and its recipient and amount | The indexer, payment events and webhooks | The contracts are the authority. If Remesso's backend stopped, no schedule could pay more, pay someone else or pay earlier than its payer allowed; scheduled payments would simply wait until it came back. Agent payments go straight to the contract, so they keep working. ## Next steps - [Pay on a schedule](https://docs.remesso.xyz/docs/guides/schedules) - [Agent payments](https://docs.remesso.xyz/docs/guides/agent-payments) - [Payment requests](https://docs.remesso.xyz/docs/guides/payment-requests) --- # Build an agent-to-agent app > One agent asks another to pay, gets paid from a wallet or a spending policy, and checks the payment on the chain before it delivers. Source: https://docs.remesso.xyz/docs/guides/agent-to-agent One agent sells a service and another buys it. The seller asks to be paid, the buyer pays from its own wallet or from a spending policy a person gave it, and the seller checks the payment on the chain before it delivers. This guide builds both sides. The APIs on this page need `remesso-sdk` 0.3.0 or later. ## Two ways to fund a payment | Funding | Whose money moves | Setup | Remesso's fee | |---|---|---|---| | `wallet` | The buying agent's own wallet | The agent holds the token and a little CELO for gas | 0.25%, added to what the buyer pays, so the seller receives the exact amount | | `policy` | A person's or a treasury's wallet | Its owner creates a [spending policy](https://docs.remesso.xyz/docs/guides/agent-payments) that names the agent | None | In both, `amount` is what the seller receives: `amount: "1"` with `token: "USDT"` delivers exactly 1 USDT. A wallet payment is a [schedule](https://docs.remesso.xyz/docs/guides/schedules) with a single payment. The SDK creates it, approves the token and runs it at once: three transactions, about 370,000 gas together. A policy payment is one transaction, about 90,000 gas. Remesso indexes both, so requests, events and webhooks treat them the same way. ## Create the clients ```ts import { createRemesso } from "remesso-sdk"; import { fileRemittanceStore, fileStore, safeFetchText } from "remesso-sdk/node"; import { privateKeyToAccount } from "viem/accounts"; // The buying agent. Its journals record each payment before it is sent, so a // retry after a crash or a timeout never pays twice. const buyer = createRemesso({ account: privateKeyToAccount(process.env.BUYER_KEY as `0x${string}`), store: fileStore("./state/policy-payments.json"), remittanceStore: fileRemittanceStore("./state/wallet-payments.json"), fetchText: safeFetchText, }); // The selling agent. const seller = createRemesso({ account: privateKeyToAccount(process.env.SELLER_KEY as `0x${string}`), }); ``` Keep the journal files. Without them the SDK keeps its records in memory, and a retry after a restart could pay a second time. Each file serves one process. For several workers, see [sharing journals across workers](https://docs.remesso.xyz/docs/guides/production-sdk#share-journals-across-workers). ## 1. The seller asks to be paid The seller asks the wallet whose money will move: the buyer's own address for wallet funding, or the policy owner's address for policy funding. ```ts const invoice = await seller.requests.create({ payer: buyer.address, amount: "1", token: "USDT", runs: 1, from: "Translation agent", note: "Translate order 1042", }); const requestId = invoice.id; ``` A request moves no money, and anyone can create one. When the buyer needs proof that the seller asked, use a [signed invoice](https://docs.remesso.xyz/docs/guides/production-sdk#sign-an-invoice) instead. Recurring requests are for [schedules](https://docs.remesso.xyz/docs/guides/schedules); `remittances.send` pays one-off requests only. ## 2. The buyer pays from its own wallet ```ts const sent = await buyer.remittances.send({ funding: "wallet", to: seller.address, amount: "1", token: "USDT", requestId, idempotencyKey: `request:${requestId}`, }); console.log(sent.status, sent.stage, sent.hash); ``` The request must name this wallet as the payer and these exact terms. The SDK approves and runs the payment only once Remesso has recorded that this payment answers the request, since that is what the seller checks; if the request was declined or answered by another payment in the meantime, the payment is cancelled instead. A `confirmed` result carries `hash`, the transaction that paid. A `pending` result may still be paid. Call `send` again with the same key and terms, at any time: the SDK continues the same payment from its journal and never creates a second one. Don't retry a pending payment under a new key; that is a second payment. `stage` says where it stands: `creation`, `approval`, `execution` or `cancellation`. If the payment can't be completed, for instance because the wallet's balance dropped, the SDK cancels its schedule, so that it can't be paid later either. Only once the cancellation has landed does it report that nothing was paid: a `refused` error whose `details.cancelled` is `true`. Until then the result is `pending`, with a `cancellationHash`. ## 3. Or the buyer pays from a spending policy The person or treasury creates a policy once, as in [agent payments](https://docs.remesso.xyz/docs/guides/agent-payments), and the request names that owner as the payer: ```ts const sent = await buyer.remittances.send({ funding: "policy", policyId, to: seller.address, amount: "1", token: "USDT", requestId, idempotencyKey: `request:${requestId}`, }); console.log(sent.status, sent.payer); // payer is the policy's owner ``` The payment carries the request's reference in its memo, which is how Remesso knows it answers this request. The contract enforces the policy's caps per payment and per day, its recipient list and its expiry. ## Pay a registered agent When the seller has an ERC-8004 identity with a Remesso payee profile (see [finding agents](https://docs.remesso.xyz/docs/guides/find-agents)), `agents.pay` takes the wallet to pay from the registry: ```ts declare const sellerAgentId: bigint; // the seller's ERC-8004 id const paid = await buyer.agents.pay(sellerAgentId, { funding: "wallet", requestId, expectedWallet: seller.address, }); console.log(paid.counterparty, paid.status, paid.hash); ``` The SDK checks the token the profile accepts, the request reference it requires and that the request pays the registered wallet, then reads the registry again just before it sends. With `expectedWallet`, it refuses if the registered wallet isn't the one your application approved. It never takes a wallet from the profile's JSON. Without a request, pass `amount`, `token` and an `idempotencyKey` instead; the profile must accept payments without a reference. For a policy, set `funding: "policy"` and optionally `policyId`. ## 4. The seller checks the payment before it delivers Checking a payment needs no key, no webhook and nothing from Remesso's indexer: ```ts declare const txHash: `0x${string}`; const proof = await reader.receipts.verify({ txHash, from: buyer.address, to: seller.address, token: "USDT", amount: "1", scheduleId, finality: "finalized", }); console.log(proof.eventId, proof.amount, proof.finality); ``` `verify` reads the transaction's receipt and checks that it holds a payment of exactly this token and amount, from this payer to this recipient, made by Remesso's contracts, in a block that is still on the chain. A schedule's amounts are what arrived, after the fee. For a policy payment, pass `policyId` and `memo` instead of `scheduleId`. With `finality: "finalized"` it also refuses until Celo has finalized the block, about 25 minutes after the payment. `included` answers at once, but a payment that is only included can still be undone by a reorganization. Before delivering, store `proof.eventId` under a unique constraint, in the same database transaction that marks the order fulfilled, so one payment can never fulfil two orders. [Payment events](https://docs.remesso.xyz/docs/guides/payment-events) and [webhooks](https://docs.remesso.xyz/docs/guides/webhooks) tell you when to check: something arrived. `verify` tells you it is the payment you expect. ## Run the example The repository's `examples/agent-to-agent.ts` runs both sides from the command line: ```bash deno run --allow-all --config examples/sdk.deno.json --unstable-sloppy-imports examples/agent-to-agent.ts request deno run --allow-all --config examples/sdk.deno.json --unstable-sloppy-imports examples/agent-to-agent.ts pay deno run --allow-all --config examples/sdk.deno.json --unstable-sloppy-imports examples/agent-to-agent.ts verify ``` - `request`, as the seller: set `SELLER_KEY`, `PAYER_ADDRESS` and `AMOUNT`, and optionally `TOKEN`. It prints the `REQUEST_ID`. - `pay`, as the buyer: set `BUYER_KEY`, `FUNDING` (`wallet` or `policy`), `REQUEST_ID`, `SELLER_ADDRESS` and `AMOUNT`, and optionally `TOKEN` and `SELLER_AGENT_ID`. Policy funding also needs `POLICY_ID`. It shows what it would do; add `--send` to pay. `POLICY_STORE` and `WALLET_STORE` set where the journals live. - `verify`, as the seller: set `TX_HASH`, `PAYER_ADDRESS`, `SELLER_ADDRESS`, `AMOUNT`, `TOKEN`, and `SCHEDULE_ID` or `POLICY_ID` (with `REQUEST_ID` for the memo). It waits for finality unless you set `FINALITY=included`. The example pays on Celo mainnet unless `RPC_URL` points at a local fork. --- # Run an agent service in production > Workers that share one key, quotes, deadlines, signed invoices, recovery after a crash, local development, managed signers and agent tools. Source: https://docs.remesso.xyz/docs/guides/production-sdk The [agent-to-agent guide](https://docs.remesso.xyz/docs/guides/agent-to-agent) covers one buyer and one seller. This page covers what a service needs around them: several workers sharing a key, quotes and deadlines, signed invoices, recovery after a crash, and local development. The APIs on this page need `remesso-sdk` 0.3.0 or later. ## Try both funding models locally With Foundry's `anvil` installed and Node 20 or later: ```bash npx remesso-sdk dev --smoke ``` This forks Celo mainnet into a local Anvil, funds public test accounts on it, starts an in-memory stand-in for Remesso's backend with a webhook receiver, pays once from a wallet and once from a policy, checks both, and cleans up. Without `--smoke` it keeps running and prints the settings and test keys to use. Every transaction goes to the local fork; Celo mainnet only supplies the state it starts from. Never fund the public test keys on a real network. The stand-in backend isn't Remesso's: its timing, cron and finality are not production's. The same environment is available to a test suite: ```ts import { startDev } from "remesso-sdk/dev"; const dev = await startDev(); try { console.log(dev.sampleInvoice.id, dev.receiverUrl); await dev.smoke(); } finally { await dev.close(); } ``` ## Share journals across workers Every process that signs with the same key must use the same journals and the same lock, or two workers could each pay the same order. With PostgreSQL (`postgres@3.4.5`), one call gives all four: ```ts import postgres from "postgres"; import { createRemesso } from "remesso-sdk"; import { createPostgresStorage } from "remesso-sdk/postgres"; import { privateKeyToAccount } from "viem/accounts"; const sql = postgres(process.env.DATABASE_URL!, { max: 8, connection: { synchronous_commit: "on" }, }); const storage = createPostgresStorage(sql, { namespace: "my-agent-service" }); await storage.initialize(); // Once, before the workers start. const agent = createRemesso({ account: privateKeyToAccount(process.env.AGENT_KEY as `0x${string}`), store: storage.paymentStore, remittanceStore: storage.remittanceStore, invoiceStore: storage.invoiceStore, coordinator: storage.coordinator, }); ``` `createRemesso` refuses a `coordinator` without the three stores: with journals in memory, each worker would keep its own record of keys, and a retry on another worker would pay again. A session lock is held from reading the journal to broadcasting, so connect directly or through session pooling; transaction pooling can't hold it. If the connection is lost, the SDK stops before signing or sending anything more. `initialize()` creates the SDK's own tables and turns on row-level security; signed transactions are stored in them, so keep them readable only by the server role that owns them. The package's `POSTGRES.md` has complete examples. ### When a transaction isn't sent Every transaction is saved before it is broadcast. If a broadcast fails, the SDK retries it at once, and before signing anything new it sends again any saved transaction the network still doesn't have, lowest nonce first. So one lost broadcast can't hold up every transaction after it. A saved transaction that must no longer land is cancelled instead of sent: the SDK signs a transaction that does nothing at the same nonce, and only one of the two can ever be mined. The no-op pays more than the original, so a node still holding the original takes the no-op instead. This happens to a policy payment still unsent close to its `validUntil`, including one whose deadline passes while it is being saved, and to a contract write (a schedule or a policy) whose caller was told it couldn't be broadcast; one saved in the last 30 seconds is given that long to arrive first. ## Quote and bound a payment ```ts const prepared = await payer.remittances.prepare({ funding: "wallet", to: receiver.address, amount: "1", token: "USDT", idempotencyKey: "order-123", maxDebit: "1.01", // The most the payer's balance may lose, fee included; gas is separate. }, { ttlSeconds: 60 }); console.log(prepared.quote.protocolFee, prepared.quote.approval); console.log(prepared.quote.gas.steps, prepared.quote.gas.notes); const sent = await payer.remittances.send(prepared.input); console.log(sent.status, sent.hash); ``` `quote()` signs nothing. It reports whether the payment can be sent now, why not if it can't, the fee, the approval needed, and simulations and gas estimates for each step. It reserves nothing: another payment can still use the balance, the daily budget or the allowance. A wallet payment's last step can't be simulated before its schedule and approval exist, so its gas is an estimate, not a measurement. `prepare()` refuses a payment that can't be sent, and returns input with the token, the policy and `maxDebit` fixed, and `startBy` set to when the quote expires. ## Deadlines - `startBy`: the payment must start before this time. Once it has started, a retry continues it at any time: a payment that went through is reported as paid, never as expired. - `validUntil`: the payment must land before this time. - From a wallet, the schedule expires a second earlier, so the contract refuses a late payment. - From a policy, nothing is broadcast in the last 30 seconds; a payment still unsent then is cancelled at its nonce, as above. - Neither starts within minutes of the deadline. A retry can't change it. A payment cancelled or expired this way is reported as not paid only once that is certain, as a `refused` error with `details.expired` or `details.cancelled`. Then, and only then, a new key may pay again. ## Sign an invoice A signed invoice is the recipient's EIP-712 signature over the exact payer, recipient, token, amount, order id, a random nonce, and when it was issued and expires. The payer can check it was signed by the recipient before paying. `create()` also creates a backend request with the same terms and signs it. `sign()` signs without one. ```ts const invoice = await receiver.invoices.create({ payer: payer.address, amount: "1", token: "USDT", orderId: "order-123", expiresAt: Math.floor(Date.now() / 1000) + 3600, }); const terms = await payer.invoices.verify(invoice); const sent = await payer.invoices.pay(invoice, { funding: "wallet", maxDebit: "1.01", }); console.log(terms.id, sent.status, sent.hash); ``` For policy funding the invoice names the policy owner as the payer, and the agent pays with `invoices.pay(invoice, { funding: "policy", policyId })`. `pay` quotes the payment before anything else, and refuses one that can't be made now or that would debit more than `maxDebit`. It then reserves the invoice for this payment route, signer and key, so no other route can pay it as well. A retry after the invoice expired still reports a payment made while it was valid. Keep that reservation after a payment whose outcome is uncertain. The default reservation store is memory; workers share the Postgres one. An invoice made with `sign()` and no request is tied to its payment only by the payment's memo, which carries `invoiceReference(invoice id)`. A schedule's payment has no memo, so such an invoice is paid from a policy; to pay from a wallet, make the invoice with `create()`. ### Verify the payment ```ts import type { SignedInvoice } from "remesso-sdk"; import type { Hex } from "viem"; declare const invoice: SignedInvoice; declare const paymentHash: Hex; const evidence = await receiver.invoices.verifyPayment(invoice, { txHash: paymentHash, scheduleId, finality: "finalized", }); console.log(evidence.invoiceId, evidence.orderId, evidence.payment.eventId); ``` Pass `policyId` instead of `scheduleId` for a policy payment. `verifyPayment` checks the payment against the invoice's exact terms, and that it is this invoice's payment: - with a request, the policy payment carries the request's reference, or the schedule is the one the request was answered with; - without one, the policy payment carries `invoiceReference`. A subscription's payment of the same amount doesn't count. It also checks that the payment landed while the invoice was valid; it can confirm an expired invoice that was paid in time. It is finalized by default and refuses while the RPC can't say whether the block is final. ### Fulfil each order once Commit the fulfilment and its deduplication together. The Postgres inbox refuses a payment, an invoice or an order that was already fulfilled: ```ts import type { SignedInvoice } from "remesso-sdk"; import { createPostgresStorage } from "remesso-sdk/postgres"; import type { Hex } from "viem"; declare const storage: ReturnType; declare const invoice: SignedInvoice; declare const paymentHash: Hex; const terms = await receiver.invoices.verify(invoice, { allowExpired: true }); const verified = await receiver.invoices.verifyPayment(invoice, { txHash: paymentHash, scheduleId, finality: "finalized", }); await storage.inbox.processOnce({ eventId: verified.payment.eventId, invoiceId: verified.invoiceId, issuer: terms.issuer, orderId: terms.orderId, }, async (tx) => { await tx.unsafe( "update orders set paid_at = clock_timestamp() where issuer = $1 and id = $2", [terms.issuer.toLowerCase(), terms.orderId], ); // Insert an outbox row here for emails, shipping or calls to other services. }); ``` Make every business write through `tx`: if the callback throws, the claim and the writes roll back together. An email or an HTTP call can't roll back, so queue those in an outbox in the same transaction. ## Recover payments after a crash ```ts const operation = await agent.remittances.get("order-123", { funding: "wallet" }); console.log(operation.status, operation.stage, operation.error); const pending = await agent.remittances.listPending(); await agent.remittances.reconcile("order-123", { funding: "wallet", waitMs: 30_000 }); const recovered = await agent.remittances.recoverPending({ limit: 25, waitMs: 5_000 }); console.log(pending.length, recovered.length); ``` `get()` and `listPending()` only read: the saved payment and what the chain says about it. `listPending()` leaves out payments that settled more than two hours ago. `reconcile()` and `recoverPending()` continue payments that have started: they send saved transactions, and finish a wallet payment's remaining steps. They never start a payment, and never start a new one under an old key. A payment saved but never signed, for example because signing failed, is left for its own caller to retry. The statuses are `prepared`, `awaiting_approval`, `signed`, `broadcast`, `included`, `finalized`, `failed`, `cancelled`, `paused`, `expired` and `needs_reconciliation`. A wallet payment whose step reverted while its schedule is still live is `needs_reconciliation`, not `failed`: it could still be paid, and reconciling cancels it. A transaction whose nonce was used by something else, with no receipt to show what, also needs a person to look at its hash; the SDK never pays it again. `recoverPending()` makes one pass, over 25 payments by default; run it from your own scheduler. If a key exists in both journals, pass `funding`. ```ts const settled = await agent.remittances.wait("order-123", { funding: "wallet", finality: "finalized", timeoutMs: 30 * 60_000, signal: AbortSignal.timeout(30 * 60_000), }); console.log(settled.proof?.eventId); ``` `wait()` only watches. It throws for a payment that failed, was cancelled, paused or expired. ### Jobs `storage.jobs` is a queue for your own work: enqueue under a key that never changes, claim with `SKIP LOCKED`, and acknowledge with the claim's token. A job is delivered at least once, so use its key as the payment's idempotency key. A claim lasts five minutes by default, longer than a payment's wait; renew it with `extend` for longer work. Payment keys belong to one signing key, so the workers that take payment jobs must all sign with the same key. ## Managed signers A key held by a custody service plugs in through callbacks that sign and never broadcast: ```ts import { createRemesso } from "remesso-sdk"; import { managedSigner, type ManagedTransactionRequest, type ManagedTypedDataRequest, } from "remesso-sdk/signers"; import type { Address, Hex } from "viem"; declare const provider: { address: Address; signTransaction(request: ManagedTransactionRequest): Promise; signTypedData(request: ManagedTypedDataRequest): Promise; }; const account = managedSigner({ address: provider.address, signTransaction: (request) => provider.signTransaction(request), signTypedData: (request) => provider.signTypedData(request), }); const agent = createRemesso({ account }); // In production, add the shared journals above. ``` The SDK passes the exact transaction, its Celo encoding and its hash, including a fee currency. It refuses anything back that differs: other fields, another signer, or a high-s signature, which Celo nodes reject. The SDK keeps simulation, journaling, nonces and broadcasting. No custody provider has been tested with it yet. ## Agent tools `remesso-sdk/tools` gives an agent framework tools with JSON schemas and structured results. They return no wallet objects or signed bytes, and a URL in an error keeps only its host. Tools that pay are off by default: ```ts import { createAgentTools } from "remesso-sdk/tools"; const tools = createAgentTools(agent); console.log(tools.list()); const result = await tools.call("remesso_quote", { funding: "wallet", to: receiver.address, amount: "1", token: "USDT", }); console.log(result); ``` Allow a payment from your own records of what was ordered and approved, with `authorizeWrite({ name, input })`; text from a model or a tool description is never permission to spend. `enableWrites: true` is for hosts that already check this themselves. When a payment's outcome is uncertain, the tool says so: reconcile its key rather than making a new one. The repository's MCP server uses these tools and the same journals. Its tools that pay stay off unless the host sets `REMESSO_ENABLE_AGENT_TOOLS_WRITES=1`: ```bash npm --prefix mcp install npm --prefix mcp run build npm --prefix mcp start ``` --- # Pay on a schedule > Create a schedule the payer signs once, read it back, and pause, resume or cancel it. Source: https://docs.remesso.xyz/docs/guides/schedules A schedule is a payment the payer signs once: a fixed amount, to a fixed address, on a fixed cadence. Remesso's executor sends each payment when it is due. This page covers creating a schedule with the SDK, checking on it, and managing it afterwards. After signing, nothing can change the recipient, the amount or the cadence: not Remesso, not the contract's owner, not the payer. The payer can pause, resume or cancel. Cancelling is the only edit. ## Before you start You need: - a client with an account that holds the token you pay in. See [Installation](https://docs.remesso.xyz/docs/installation). - gas for two transactions, an approval and the schedule itself. CELO works for every token. With no CELO, USDT, USDC, USDm and USA₮ can pay their own gas. See [Fees, limits and gas](https://docs.remesso.xyz/docs/guides/fees-and-gas). - at least the executor's minimum per payment: the equivalent of 1 US dollar, such as 1 USDT or 1,500 wARS. ## Create a schedule `schedules.create` approves the contract for the schedule's total, creates the schedule, and returns its id. This program pays 5 USDT a month, twelve times: ```ts title="pay-monthly.ts" import { createRemesso, formatAmount } from "remesso-sdk"; import { privateKeyToAccount } from "viem/accounts"; const payer = createRemesso({ account: privateKeyToAccount(process.env.PAYER_KEY as `0x${string}`), }); const s = await payer.schedules.create({ to: process.env.RECIPIENT!, amount: "5", token: "USDT", every: "month", runs: 12, }); console.log(`schedule ${s.id}: ${formatAmount(s.recipientGets, s.token)} arrives each month`); console.log(`approval ${s.approveHash}, schedule ${s.hash}`); for (const w of s.warnings) console.warn(w); ``` Two transactions go out, in order: 1. **An approval.** The SDK adds this schedule's total (here 60 USDT) to the approval you have already given the contract. It never replaces it, because another schedule may rely on it. Pass `approve: false` if you manage approvals yourself. 2. **The schedule.** The SDK reads its id from the transaction's own `ScheduleCreated` log. Before either is signed, the SDK checks everything the contract or the executor would refuse. See [What the SDK refuses before signing](https://docs.remesso.xyz/docs/guides/schedules#what-the-sdk-refuses-before-signing). ### Options | Option | Required | What it is | |---|---|---| | `to` | Yes, unless `request` gives it | The recipient's address. | | `amount` | One of `amount` or `receive` | What leaves your wallet each payment, as a decimal string: `"1.50"`. | | `receive` | One of `amount` or `receive` | What the recipient receives each payment, after the fee. The SDK works out what to send. | | `token` | No | A symbol or an address. Default `USDT`, or the request's token. | | `every` | No | `"week"`, `"fortnight"`, `"month"`, `"quarter"`, or a number of seconds, at least 3,600. Default `"week"`, or the request's cadence. | | `runs` | Yes, unless `request` gives it | How many payments. | | `firstRunAt` | No | A `Date` for the first payment. Default: now. | | `trigger` | No | Who may bring a payment forward: `"remesso"` or an address. Default: nobody. | | `triggers` | No | How many early sends the trigger may make. Default: one per payment. | | `request` | No | The id of a payment request this schedule answers. Its terms fill whatever you leave out. | | `approve` | No | `false` to skip the approval. Default: approve this schedule's total on top of what is already approved. | ## Send an amount, or make an amount arrive The 0.25% fee comes out of each payment. Choose which side of it you fix: ```ts // 1 USDT leaves your wallet. The recipient receives 0.9975 USDT. await payer.schedules.create({ to: "0xRecipient", amount: "1", token: "USDT", every: "week", runs: 4 }); // 1 USDT arrives. Your wallet sends 1.002507 USDT. await payer.schedules.create({ to: "0xRecipient", receive: "1", token: "USDT", every: "week", runs: 4 }); ``` Use `receive` when the recipient is owed an exact amount, such as an invoice. The per-payment limits apply to what leaves your wallet in both cases. [Fees, limits and gas](https://docs.remesso.xyz/docs/guides/fees-and-gas) shows the arithmetic. ## Choose the cadence and the first payment `every` takes a cadence name or a number of seconds: | `every` | Interval | |---|---| | `"week"` | 7 days | | `"fortnight"` | 14 days | | `"month"` | 30 days | | `"quarter"` | 90 days | | a number | that many seconds, at least 3,600 | `"weekly"`, `"monthly"` and the other `-ly` forms are accepted too. A month is always 30 days, as in the Remesso app. ```ts // Every three days, ten times. await payer.schedules.create({ to: "0xRecipient", amount: "20", every: 3 * 86_400, runs: 10 }); // Monthly in USDC, the first payment a week from now. await payer.schedules.create({ to: "0xRecipient", amount: "250", token: "USDC", every: "month", runs: 6, firstRunAt: new Date(Date.now() + 7 * 86_400_000), }); ``` A schedule lasts at most a year. The SDK sets its expiry to one interval and an hour after the last payment's slot, so `runs` payments must fit within 365 days: 12 monthly payments or 52 weekly ones fit, 13 monthly ones don't. For longer arrangements, create a new schedule before the old one ends. `firstRunAt` can't be in the past. ## What you get back `create` returns a `CreatedSchedule`: | Field | What it is | |---|---| | `id` | The schedule's id on the contract, a `bigint`. | | `hash` | The `createSchedule` transaction. | | `approveHash` | The approval transaction, or `null` with `approve: false`. | | `to`, `token` | The recipient, checksummed, and the token's symbol. | | `amountIn` | What leaves your wallet each payment, in base units. | | `recipientGets` | What the recipient receives each payment, in base units, after the fee. | | `feeBps` | The commission this schedule pinned, in basis points (25 is 0.25%). | | `runs`, `intervalSeconds` | How many payments, and how far apart. | | `firstRunAt`, `expiresAt` | When the first payment is due, and when the schedule ends. | | `request` | With `request`: how the request took the answer (`status` and `signed`), or `error`. | | `warnings` | Things to fix that didn't stop the schedule, such as a balance below one payment. | Check `warnings`. A schedule whose payer can't cover a payment is valid, and each payment fails until the wallet is funded. ## When the first payment goes out The first payment is due at `firstRunAt`, which is now unless you set it. Remesso's executor sends due payments every minute. It learns about schedules created outside the Remesso app through a sync that reads the contract every five minutes. So the sync finds a new schedule within about five minutes, and its first payment goes out within a minute of that. ```mermaid sequenceDiagram participant Payer as Your code participant Contract as RemessoExecutorV5 participant Remesso as Remesso's executor participant Recipient Payer->>Contract: approve, then createSchedule (signed once) Remesso->>Contract: finds the schedule (within about 5 minutes) loop Each time a payment is due Remesso->>Contract: executeRun(id) Contract->>Recipient: the amount, less the 0.25% fee end ``` After that: - **Each payment is due one interval after the previous one actually went out.** The cadence is measured from the payment, not from the original timetable. - **Missed payments aren't caught up.** If a payment can't go, because the wallet is short, the approval is too low or the schedule is paused, it stays due and is retried. When it does go, the next one is due an interval later. A payer is never charged for two periods at once. - **The schedule stops** after `runs` payments, at its expiry, or when it is cancelled. To know when a payment arrived, watch [payment events](https://docs.remesso.xyz/docs/guides/payment-events) or receive [webhooks](https://docs.remesso.xyz/docs/guides/webhooks). ## Let a payment be sent early A trigger may bring one payment forward, ahead of its date. Grant it when you create the schedule: ```ts const s = await payer.schedules.create({ to: "0xRecipient", amount: "10", every: "month", runs: 12, trigger: "remesso", // Remesso's executor, whichever key it is at the time triggers: 3, // at most three early sends }); await payer.schedules.setTrigger(s.id, "remesso", 1); // one early send left await payer.schedules.setTrigger(s.id, null); // nobody can send early any more ``` `trigger` is `"remesso"` or an address: - `"remesso"` stores Remesso's sentinel address, `0x0000000000000000000000000000000000000001`. It means whichever key is Remesso's executor at the time, so a key rotation moves the grant. Anyone can then pay 0.01 USDC, USDT or USA₮ over x402 to `trigger-run` and have Remesso send one payment early. See the [HTTP API](https://docs.remesso.xyz/docs/reference/http-api). - An address, such as your own agent's, lets that key call `runNow` on the contract itself and pay the gas. An early send can't change the recipient or the amount, and it doesn't add a payment. It uses up the next one: if a payment is due next week and is sent today, the one after keeps its own date. Early sends are at most one a minute, never past the expiry, and stop when the count runs out. Sending a payment that is already due uses no count. `setTrigger` takes the count as its third argument, and it defaults to 0, so pass a count whenever you grant a trigger. A schedule created through the SDK is found by Remesso's sync within about five minutes. Until then, only a trigger can move it. `trigger: "remesso"` lets a first payment go out without waiting for the sync. ## Answer a payment request When someone has sent you a [payment request](https://docs.remesso.xyz/docs/guides/payment-requests), create the schedule from it. Its terms fill everything you leave out, and the SDK reports the schedule against the request: ```ts const s = await payer.schedules.create({ request: requestId }); if (s.request && "status" in s.request) console.log("the request reads", s.request.status); else console.log("created, but not reported against the request:", s.request?.error); ``` The request's amount is what the recipient receives, so the SDK sends the amount plus the fee. The SDK refuses the request if it was sent to a different wallet, if it is no longer pending, or if you pass a `to` it didn't ask for. If reporting fails after the schedule is created, `create` still returns the schedule, with the reason in `s.request.error`. ## Read a schedule back ```ts import { formatAmount } from "remesso-sdk"; const s = await payer.schedules.get(scheduleId); console.log(`${s.runsExecuted} of ${s.maxRuns} paid, next due ${s.nextRunAt.toISOString()}`); console.log(`${formatAmount(s.recipientGets, s.token)} arrives each time`); const { due, funded, approved } = await payer.schedules.status(scheduleId); if (due && !funded) console.warn("a payment is due and the wallet holds less than one payment"); if (!approved) console.warn("the approval is below one payment: approve the contract again"); const ids = await payer.schedules.list(); // every schedule this wallet created ``` - **`get(id)`** returns the schedule as the contract stores it: `sender`, `to`, `token`, `amountIn`, `recipientGets`, `feeBps`, `intervalSeconds`, `maxRuns`, `runsExecuted`, `nextRunAt`, `lastRunAt`, `expiresAt`, `active`, `cancelled`, `trigger` and `triggersLeft`. Right after a schedule is created, a node can still answer from an earlier block, so `get` keeps asking for a few seconds. An id that doesn't exist throws [`not_found`](https://docs.remesso.xyz/docs/reference/errors#not_found). - **`status(id)`** asks the contract whether a payment can go now. `due` means a payment is due and the schedule is active, unexpired and not finished. `funded` means the payer's balance covers one payment, and `approved` means the approval does. - **`list(sender?)`** returns the ids of every schedule a sender has created, by default this account's. A read-only client can pass any address. ## Pause, resume or cancel Only the wallet that created a schedule can change it. ```ts await payer.schedules.setActive(scheduleId, false); // pause await payer.schedules.setActive(scheduleId, true); // resume await payer.schedules.cancel(scheduleId); // for good ``` - **Pausing** stops payments until you resume. Payments that fall due while it is paused aren't caught up: after you resume, the due payment goes out and the cadence restarts from it. - **Cancelling** is permanent. A cancelled schedule can't be resumed. Create a new one instead. - **The approval stays** after a cancel. Lower it yourself if nothing else needs it. Revoking the token approval also stops every future payment from that token, which is the backstop if you can't reach the contract. Each of these is a transaction. It pays gas in CELO, or in the schedule's token if you hold no CELO and the token can pay gas. ## What the SDK refuses before signing Each refusal is a [`RemessoError`](https://docs.remesso.xyz/docs/reference/errors) with a `code`, thrown before anything is signed: | Situation | Code | |---|---| | `to` is missing or isn't an address | [`invalid`](https://docs.remesso.xyz/docs/reference/errors#invalid) | | `to` is your own wallet: a schedule paying itself only loses the fee | [`invalid`](https://docs.remesso.xyz/docs/reference/errors#invalid) | | Both `amount` and `receive`, or neither | [`invalid`](https://docs.remesso.xyz/docs/reference/errors#invalid) | | `runs` is missing or not a whole number | [`invalid`](https://docs.remesso.xyz/docs/reference/errors#invalid) | | `every` is under 3,600 seconds, or not a cadence name | [`invalid`](https://docs.remesso.xyz/docs/reference/errors#invalid) | | `firstRunAt` is in the past | [`invalid`](https://docs.remesso.xyz/docs/reference/errors#invalid) | | The token isn't one Remesso knows | [`invalid`](https://docs.remesso.xyz/docs/reference/errors#invalid) | | The contract doesn't take the token for new schedules | [`refused`](https://docs.remesso.xyz/docs/reference/errors#refused) | | The amount is outside the executor's per-payment range | [`refused`](https://docs.remesso.xyz/docs/reference/errors#refused) | | The payments don't fit in a year | [`refused`](https://docs.remesso.xyz/docs/reference/errors#refused) | | The request was sent to another wallet, isn't pending, or asks for another `to` | [`refused`](https://docs.remesso.xyz/docs/reference/errors#refused) | | The RPC isn't Celo mainnet | [`wrong_chain`](https://docs.remesso.xyz/docs/reference/errors#wrong_chain) | | The client has no account | [`no_account`](https://docs.remesso.xyz/docs/reference/errors#no_account) | | Neither CELO nor the token can pay the gas | [`no_gas`](https://docs.remesso.xyz/docs/reference/errors#no_gas) | The SDK also simulates each transaction first, and a refusal from the contract arrives as `refused` with the contract's reason in words. A transaction that is mined and then reverts throws [`reverted`](https://docs.remesso.xyz/docs/reference/errors#reverted): nothing moved. The per-payment range matters most. A schedule outside it is valid on-chain and would never run, because the executor won't send it: ```ts import { RemessoError } from "remesso-sdk"; try { await payer.schedules.create({ to: "0xRecipient", amount: "0.50", token: "USDT", every: "week", runs: 4 }); } catch (e) { if (e instanceof RemessoError && e.code === "refused") console.error(e.message); // under the 1 USDT minimum else throw e; } ``` ## Next steps - [Payment events](https://docs.remesso.xyz/docs/guides/payment-events) and [webhooks](https://docs.remesso.xyz/docs/guides/webhooks): know when each payment arrives. - [Payment requests](https://docs.remesso.xyz/docs/guides/payment-requests): let the recipient ask for the schedule. - [Fees, limits and gas](https://docs.remesso.xyz/docs/guides/fees-and-gas): what each payment costs, and every token's range. --- # Agent payments > Let an agent pay from a person's wallet within limits the person sets on-chain, and pay exactly once per idempotency key. Source: https://docs.remesso.xyz/docs/guides/agent-payments An agent payment moves tokens from a person's wallet, sent by their agent's key, inside a policy the person signed on-chain. This page covers both sides: the person who sets the policy, and the agent that pays under it. There are two roles: - **The person** owns the money. They create a **policy** on the `RemessoAgentAllowance` contract that names the agent's address: one token, a cap per payment, a cap per day, an expiry, and optionally the only recipients allowed. - **The agent** holds its own key and calls `pay`. The contract moves the tokens straight from the person's wallet to the recipient, and refuses anything outside the policy. Agent payments carry no fee: what the agent pays is what arrives. The contract has no owner and no admin. Nobody but a policy's owner can change it, and nobody but its agent can spend under it. ## Create a policy The person creates the policy, in code or in the Remesso app at [remesso.xyz/agent-wallet](https://remesso.xyz/agent-wallet): ```ts const { id, hash, approveHash } = await person.policies.create({ agent: "0xAgentAddress", token: "USDT", perPayment: "5", perDay: "20", days: 30, budget: "100", }); console.log(`policy ${id}: approval ${approveHash}, policy ${hash}`); ``` Like a schedule, this is two transactions: an approval for the allowance contract, then the policy itself. | Option | Required | What it is | |---|---|---| | `agent` | Yes | The agent's address: the only key that can pay under the policy. | | `token` | Yes | A symbol or an address. A policy spends one token. | | `perPayment` | Yes | The most one payment can be, as a decimal string. Above zero, and at most `perDay`. | | `perDay` | Yes | The most the agent can pay in one day, as a decimal string. | | `days` | No | How long the policy lasts, in days. Default 30, at most 365. | | `recipients` | No | If given, the only addresses the agent may pay, up to 50. | | `budget` | Yes | Added to the person's approval for the allowance contract, as a decimal string. | ### What the budget is, and isn't The budget is not a total for this policy. It is added to the approval the person gives the allowance contract for that token, and that one approval is shared by all their policies for the token. It caps what all of them can spend together. A policy has no total of its own: it is bounded by its caps per payment and per day until it expires, and by the person's balance and approval. So size the caps and the expiry for what you are willing to lose, and keep the approval to the budget you mean. ### How the day is counted The daily cap counts within a 24-hour window. The window opens with the first payment after the previous window closed. It is not a rolling 24 hours, so across a window boundary up to twice `perDay` can move in a short span. `perDay` bounds each window. The expiry and the approval bound the rest. ## Manage a policy As the person: ```ts import { formatAmount } from "remesso-sdk"; const p = await person.policies.get(policyId); console.log(p.active ? `can pay up to ${formatAmount(p.spendable, p.token)} right now` : "revoked or expired"); await person.policies.setRecipient(policyId, "0xNewSupplier", true); // allow a recipient await person.policies.setRecipient(policyId, "0xOldSupplier", false); // take one off await person.policies.revoke(policyId); // ends it now, for good ``` - **`get(id)`** returns the policy with what it can spend right now. `active` means not revoked and not expired. `remainingToday` is what is left in the current window. `spendable` is the largest payment allowed this moment, counting the per-payment cap, today's remainder, and the owner's balance and approval. - **`find(agent?)`** returns the ids of every policy naming an agent, newest first. It defaults to this account, so an agent can find its own. - **`revoke(id)`** ends a policy now. It is one-way. New limits or a new key means a new policy, so a revoked one can never be quietly brought back. - **`setRecipient(id, recipient, allowed)`** edits the allowlist of a policy created with `recipients`. A policy created without recipients lets the agent pay anyone, and adding recipients later doesn't change that. To restrict an open policy, revoke it and create one with `recipients`. Each of these writes needs the owner's key. It pays gas in CELO, or in the policy's token if the owner holds no CELO and the token can pay gas. ## Pay as the agent ```ts title="agent-pays.ts" import { createRemesso } from "remesso-sdk"; import { fileStore } from "remesso-sdk/node"; import { privateKeyToAccount } from "viem/accounts"; const agent = createRemesso({ account: privateKeyToAccount(process.env.AGENT_KEY as `0x${string}`), store: fileStore(), // the record that makes a retry pay once, kept on disk }); const r = await agent.pay({ to: "0xShop", amount: "2.50", token: "USDT", memo: "order 1042", idempotencyKey: "order-1042", }); console.log(r.status, r.sent, r.explorer); // confirmed 2.5 USDT https://celoscan.io/tx/0x… ``` | Option | Required | What it is | |---|---|---| | `to` | Yes | The recipient's address. | | `amount` | Yes | A decimal string in the policy token's units: `"0.10"`. | | `token` | No | A symbol or an address. Default: the token of the policy used. | | `policyId` | No | Which policy to pay under. Default: an active policy naming this agent, for the token, that can cover the amount now. | | `memo` | No | Up to 32 bytes, recorded on-chain in the `Paid` event. | | `idempotencyKey` | Strongly advised | One per payment you intend, such as an invoice or order id. A retry with the same key never pays twice. | | `allowRepeat` | No | Pay even though an identical payment went out in the last ten minutes. | | `waitMs` | No | How long to wait for the receipt before answering `"pending"`. Default 45,000. | Before signing, the SDK checks the policy is active, names this agent and spends this token, and that the amount fits what it can spend now. Then it simulates the payment, so a refusal from the contract arrives as [`refused`](https://docs.remesso.xyz/docs/reference/errors#refused) with its reason in words, before anything is signed. ### What `pay` returns `pay` resolves to a `PaymentResult`: | Field | What it is | |---|---| | `status` | `"confirmed"`: mined and successful. `"pending"`: sent, but no receipt within `waitMs`. | | `hash`, `explorer` | The transaction, and its Celoscan link. | | `policyId`, `to`, `amount`, `token` | What was paid, under which policy. `amount` is in base units. | | `sent` | The amount and symbol, for people: `"2.5 USDT"`. | | `repeat` | `true` when this key had already paid and nothing new was sent. | A `"pending"` result means the payment is out and its outcome isn't known yet. Don't pay again with a new key. Call `pay` again later with the same key, and it reports what became of the first attempt. If the payment was mined and reverted, `pay` throws [`reverted`](https://docs.remesso.xyz/docs/reference/errors#reverted): nothing was paid, and a new key is needed to try again. ## Retries and idempotency keys An agent's tool call can time out after its transaction went out, and the agent tries again. The contract checks nothing about repeats, so without care that is a second payment inside every cap. The SDK prevents it. Every payment is signed first and recorded in the client's store, with its hash and its signed bytes, before it is broadcast. A retry with the same `idempotencyKey` finds that record and reconciles it with the chain: | What became of the first attempt | What the retry does | |---|---| | Confirmed | Returns it with `repeat: true`. Nothing new is sent. | | Sent, still waiting to be mined | Returns it as `"pending"`. | | Signed and recorded, never sent | Sends the recorded bytes, with the same nonce, so it still pays at most once. | | Dropped: its nonce went to another transaction | Pays now, under the same key. | | Reverted | Throws `reverted`. Use a new key to try again. | Two more rules guard against mistakes: - **A key belongs to one payment.** Reusing a key for a different recipient, amount or memo throws [`key_reused`](https://docs.remesso.xyz/docs/reference/errors#key_reused). - **No key, no quick repeats.** Without a key, a payment identical to one in the last ten minutes, with the same policy, recipient, amount and memo, throws [`repeat`](https://docs.remesso.xyz/docs/reference/errors#repeat). The SDK looks in its own store and on-chain. Pass `allowRepeat: true` if you mean to pay twice. A client sends its agent payments one at a time, so two calls in parallel can't sign with the same nonce. ## Where payment records live The store is what makes a retry safe, so it must outlive the failure you are guarding against: - **`memoryStore()`**, the default, covers retries inside one process. A restart forgets it. - **`fileStore(path?)`** from `remesso-sdk/node` keeps records in a JSON file with mode 0600, by default `~/.remesso-sdk/payments.json`. It holds across restarts. Use one process per file: it isn't locked. - **Your own store**, such as a database table, implements `PaymentStore`: ```ts import type { PaymentRecord, PaymentStore } from "remesso-sdk"; // A stand-in for your database: one record per transaction hash. const records = new Map(); const store: PaymentStore = { // The latest record for a key, in one deployment. async byKey(key, deployment) { return [...records.values()].reverse().find((r) => r.key === key && r.deployment === deployment); }, // Records newer than a time, in milliseconds since the epoch. async since(since) { return [...records.values()].filter((r) => Date.parse(r.at) > since); }, // A key has one record, its latest attempt: replace by hash, or by key in its deployment. async save(rec) { for (const [hash, r] of records) { if (hash === rec.hash || (rec.key && r.key === rec.key && r.deployment === rec.deployment)) records.delete(hash); } records.set(rec.hash, rec); }, }; ``` Pass it as `createRemesso({ account, store })`. The built-in stores keep records for 30 days, up to 1,000 of them. ## Memos A memo is 32 bytes on-chain, measured in bytes, not characters: a plain letter is 1 byte, an accented letter 2, an emoji 4. A longer memo is refused with [`invalid`](https://docs.remesso.xyz/docs/reference/errors#invalid), not cut, because a cut reference no longer matches what it refers to. The memo is recorded in the `Paid` event, and [payment events](https://docs.remesso.xyz/docs/guides/payment-events) show it as text. To pay a [payment request](https://docs.remesso.xyz/docs/guides/payment-requests), use `agent.requests.pay(requestId)`. It puts the request's 32-character reference in the memo for you. ## What the agent's key needs **A local account.** Agent payments are signed before they are sent, so the client needs an account made with `privateKeyToAccount`. A browser wallet's `walletClient` can create policies and schedules, but `pay` throws [`no_account`](https://docs.remesso.xyz/docs/reference/errors#no_account). **A little CELO for gas.** The tokens move from the person's wallet, but the agent's key sends the transaction and pays its gas, about 85,000 gas per payment. With too little CELO, an agent that holds some of the policy's token pays gas in that token instead, if it is one that can (USDT, USDC, USDm, USA₮). Otherwise `pay` throws [`no_gas`](https://docs.remesso.xyz/docs/reference/errors#no_gas). See [Fees, limits and gas](https://docs.remesso.xyz/docs/guides/fees-and-gas). ## What the policy refuses The SDK simulates every payment, and the contract's refusal comes back as `refused` with its reason: | The contract says | Because | |---|---| | the policy names a different agent | `policyId` belongs to another agent's key. | | the policy was revoked | Its owner revoked it. | | the policy has expired | Its `days` have run out. | | the amount is over the policy's per-payment cap | Lower the amount, or ask the person for a new policy. | | today's cap is used up | The current 24-hour window is spent. It reopens 24 hours after the window's first payment. | | the recipient isn't on the policy's allowlist | The policy was created with `recipients`, and this one isn't among them. | | a policy can't pay its own owner | Paying the person from their own wallet does nothing. | | the token refused the transfer | The person's balance or approval is too low. | The SDK checks `spendable` before simulating, so an amount over what the policy allows right now is refused with the exact limit, for example `3 USDT is over what policy 12 allows right now (2 USDT)`. ## Next steps - [AI agent budget](https://docs.remesso.xyz/docs/use-cases/ai-agent-budget): a complete agent that pays under a policy. - [Payment requests](https://docs.remesso.xyz/docs/guides/payment-requests): pay a request as an agent, or decline it for the person. - [Payment events](https://docs.remesso.xyz/docs/guides/payment-events): see each `Paid` event, with its memo. --- # Payment requests > Ask a wallet to pay you, follow the request until it is paid, and answer or decline requests sent to you. Source: https://docs.remesso.xyz/docs/guides/payment-requests A payment request asks a wallet to pay you: an amount, a token and, if you like, a cadence and a number of payments. This page covers sending one, following it until it is paid, and the payer's side, answering or declining. A request is free to send and needs no account with Remesso. It lands in the payer's Remesso inbox, and you get a link that opens their payment form filled in. A request can't move money. Nothing is authorised until the payer signs in their own wallet. ## How a request is answered A request can be answered in two ways: - **By a schedule.** The payer signs a schedule for the terms, in the Remesso app or with the SDK, and it is reported against the request. - **By an agent payment.** The payer's agent pays through a [policy](https://docs.remesso.xyz/docs/guides/agent-payments), with the request's reference in the memo. A request has a status: | Status | What it means | |---|---| | `pending` | Waiting for the payer. | | `accepted` | Answered with the terms you asked for. | | `accepted_with_changes` | Answered, with some terms different. The answer lists which. | | `declined` | The payer, or their agent, turned it down with a signature. | | `expired` | Still pending after 30 days. | **Accepted is not paid.** A schedule can be accepted days before its first payment, and that payment can still fail. A request counts as paid only once a payment with a transaction answers it. That shows in `payment.state`, described in [Check where a request stands](https://docs.remesso.xyz/docs/guides/payment-requests#check-where-a-request-stands). ## Ask to be paid ```ts const req = await receiver.requests.create({ payer: "0xTheirWallet", amount: "50", token: "USDT", every: "month", runs: 12, from: "Ada's studio", note: "Monthly retainer", }); console.log(req.id, req.reference); console.log(req.link); // send it however you already reach them ``` | Field | Required | What it is | |---|---|---| | `payer` | Yes | The wallet you are asking to pay. | | `to` | No | Where you want to be paid. Default: this account. | | `amount` | No | What you want to receive per payment, as a decimal string. The payer covers the fee on top. | | `token` | No | The token, one of those schedules accept. | | `every` | No | `"week"`, `"fortnight"`, `"month"` or `"quarter"`. | | `runs` | No | How many payments, 1 to 9,999. | | `from` | No | Who is asking, shown to the payer. Up to 40 characters. | | `note` | No | Why, up to 120 characters. | Anything you leave out, the payer chooses. `create` returns: - `id`: the request's id, which every other call takes; - `reference`: the id without its dashes, 32 characters, which an agent payment carries in its memo to answer the request; - `link`: the payer's form, prefilled; - `status`: `"pending"`. **A wallet can have at most 10 live requests**, meaning pending and under 30 days old. Beyond that, `create` throws [`refused`](https://docs.remesso.xyz/docs/reference/errors#refused) until the payer answers or declines some, or they expire. The cap protects the payer's inbox. > **Note** > > Requests are public to anyone who names the payer's address, the way an invoice is. Don't put anything private in `from` or `note`. ## Share the link The link opens `remesso.xyz/schedules/new` with the request's terms filled in. It also carries `req=`, the request's id, so the schedule the payer signs from it is reported against your request. The form tells the payer a link filled it, and they review every field before signing. Send the link however you already reach the payer: by email, in a chat, or as a QR code. The request also shows in their Remesso inbox, so a payer who opens the app sees it without the link. ## See the requests sent to you The payer's inbox is a list of live requests addressed to a wallet: ```ts for (const r of await payer.requests.list()) { const amount = r.amount ? `${r.amount} ${r.token ?? ""}` : "an amount you choose"; console.log(`${r.id}: ${r.from ?? "someone"} asks for ${amount}`, r.note ?? ""); } ``` `list(payer?)` defaults to this account. A read-only client can pass any address. Each entry has the request's `id`, `reference`, `to`, `amount`, `token`, `intervalSeconds`, `runs`, `from`, `note`, `status` and `createdAt`. It lists live requests only: pending and under 30 days old. ## Answer with a schedule As the payer, create a schedule from the request. Its terms fill whatever you leave out, and the SDK reports the schedule against the request: ```ts const s = await payer.schedules.create({ request: requestId }); console.log(`schedule ${s.id}`, s.request); // { status: "accepted", signed: { … } } ``` The request's amount is what the recipient receives, so the SDK sends that amount plus the fee. The answer is `accepted` when every term matches, and `accepted_with_changes` when the schedule differs in token, amount, cadence or number of payments. `signed.changed` names which. If you created the schedule another way, report it yourself: ```ts const answer = await payer.requests.answer(requestId, scheduleId); console.log(answer.status, answer.signed); ``` The backend may not see a new schedule for a few seconds, because a node behind Celo's RPC can answer from an earlier block. `answer` retries for up to 90 seconds while the backend says the schedule hasn't been seen yet, and after a server error. Answering again with the same schedule is safe: it returns the first answer. A schedule answers a request only if all of these hold. Otherwise `answer` throws [`conflict`](https://docs.remesso.xyz/docs/reference/errors#conflict) with the reason: - it is from the wallet the request was sent to; - it pays the address that asked; - it was created after the request; - it doesn't already answer another request. ## Pay a request as an agent An agent with a policy from the payer can pay the request directly: ```ts const r = await agent.requests.pay(requestId); console.log(r.status, r.sent, r.explorer); ``` `requests.pay` reads the request and finds an active policy from the wallet the request was sent to, for the request's token (USDT if it names none). Then it pays the requested amount to the requested address, with: - the request's 32-character reference as the memo, which is how the payment answers the request; - the idempotency key `request:` unless you pass your own, so the request is paid at most once. Pass `amount` if the request names none. You can also pass `policyId`, `idempotencyKey`, `allowRepeat` and `waitMs`, as for [`pay`](https://docs.remesso.xyz/docs/guides/agent-payments#pay-as-the-agent). A request that is declined or expired is refused. Remesso's indexer binds the payment to the request within about a minute. ## Decline a request Declining turns a request down for everyone: the requester sees it as `declined`. It needs a signature from the wallet the request was sent to: ```ts await payer.requests.decline(requestId); ``` An agent can decline for its person, under a live policy from that wallet: ```ts await agent.requests.decline(requestId, { policyId }); ``` The signature is EIP-712 typed data that expires after 10 minutes. `ttlSeconds` sets it from 60 seconds to a day. Declining needs an account that can sign: a local account or a wallet client. Hiding a request in the Remesso app is different: it is private to that session and declines nothing. ## Wait until it is paid ```ts const paid = await receiver.requests.waitUntilPaid(requestId, { timeoutMs: 30 * 60_000 }); for (const tx of paid.payment?.txs ?? []) { console.log(tx.txHash, tx.amount, tx.final ? "final" : "not final yet"); } ``` `waitUntilPaid` checks the request every 15 seconds until a payment with a transaction answers it: - it throws [`refused`](https://docs.remesso.xyz/docs/reference/errors#refused) if the request is declined or expires; - it throws [`timeout`](https://docs.remesso.xyz/docs/reference/errors#timeout) if it isn't paid within `timeoutMs`, 20 minutes by default; - it takes an `AbortSignal` as `signal`, and `intervalMs` to change how often it checks. Allow for the first payment of a schedule. Remesso's sync finds a schedule created outside its app within about five minutes, and the payment follows within a minute. In a server, [webhooks](https://docs.remesso.xyz/docs/guides/webhooks) tell you the moment a payment lands, without polling. ## Check where a request stands ```ts const r = await receiver.requests.get(requestId); console.log(r.status, r.payment?.state ?? "not answered yet"); if (r.payment?.state === "failed") console.log("the last attempt failed:", r.payment.reason); ``` `get(id)` returns the request's `status`, the `payer` asked, the `requested` terms, the `signed` terms once answered, and `payment`: | `payment.state` | What it means | |---|---| | `null` | Not answered yet. | | `"scheduled"` | Answered by a schedule, and nothing paid yet: its first payment hasn't come due. | | `"paid"` | At least one payment landed. `payment.txs` lists each, with `txHash`, `amount`, `final` and `blockTime`. | | `"failed"` | Answered by a schedule whose last attempt failed. `payment.reason` says why, for example a balance below one payment. | For a schedule, `payment.schedule` gives its contract and id. ## Pay links A pay link fills in the payer's form, like a request's link, but it creates no request: nothing reaches their inbox, and nothing is tracked unless the link carries a request id. Build one with `payLink`, and read one with `parsePayLink`: ```ts import { parsePayLink, payLink } from "remesso-sdk"; // To this account, at the client's app origin. const mine = receiver.payLink({ amount: "25", token: "USDT", every: "month", runs: 6, from: "Ada's studio" }); // To any address. const theirs = payLink({ to: process.env.RECIPIENT!, amount: "10", token: "USDC" }); const fields = parsePayLink(mine); console.log(fields.to, fields.amount, fields.token, fields.intervalSeconds, fields.runs); ``` A link is input from a stranger, so `parsePayLink` keeps only what validates, the same rules as the app's form: - an address; - `from` cut to 40 characters, and a note cut to 120; - a positive decimal amount; - a token that schedules accept; - a cadence the form offers; - `runs` from 1 to 9,999; - a request id. It drops anything else. A link can't create a schedule, approve an allowance or sign anything. ## Next steps - [Pay on a schedule](https://docs.remesso.xyz/docs/guides/schedules): everything `schedules.create` takes. - [Agent payments](https://docs.remesso.xyz/docs/guides/agent-payments): policies, and paying exactly once. - [Webhooks](https://docs.remesso.xyz/docs/guides/webhooks): be told when a request's payment lands. - [Bill a client monthly](https://docs.remesso.xyz/docs/use-cases/freelancer-retainer): a complete requester. --- # Payment events > Read every payment to or from an address, as Remesso's indexer records it from the chain, and follow new ones as they arrive. Source: https://docs.remesso.xyz/docs/guides/payment-events This guide shows how to read payment events: every schedule payment and agent payment to or from an address, as Remesso records it from Celo, with no node of your own. ## What is a payment event? Remesso's indexer reads the executor and allowance contracts every minute and records each log as a payment event. That covers every way money moves through Remesso: - a schedule's payment, whether Remesso's executor sent it on time or a trigger brought it forward; - an agent's payment under a policy; - a schedule or policy being created, and a policy being revoked. Payment events are public and free, like the chain they come from. You read them by address: as the recipient, the payer, or the agent that paid. ## List an address's events `events.list` returns one page of events and the `next` value to pass back for the following page. ```ts import { formatAmount } from "remesso-sdk"; const { events, next } = await receiver.events.list({ role: "to", kinds: ["run", "policy_payment"], }); for (const e of events) { console.log(e.kind, formatAmount(e.amount!, e.token!), e.final ? "final" : "not final yet", e.txHash); } console.log("next page after", next); ``` The query takes these options: | Option | Default | Meaning | |---|---|---| | `address` | the client's account | The address to read. Required on a read-only client. | | `role` | `"any"` | Which side of the event the address is on: `"to"`, `"from"`, `"agent"` or `"any"`. | | `kinds` | every kind | Any of `run`, `schedule_created`, `policy_payment`, `policy_created`, `policy_revoked`. | | `after` | `0` | The `next` of a previous page. `0` starts from the beginning. | | `limit` | `100` | Events per page, from 1 to 500. | A read-only client can read any address: ```ts const { events } = await reader.events.list({ address: "0xTheirWallet", role: "from", kinds: ["schedule_created"], }); console.log(events.map((e) => e.refId)); // the ids of the schedules this wallet created ``` ## Page through everything Events come in the order of their `seq`, a number that only grows. Pass each page's `next` back as `after` until a page comes back short: ```ts import type { PaymentEvent } from "remesso-sdk"; const all: PaymentEvent[] = []; let after = 0; for (;;) { const page = await receiver.events.list({ role: "to", after, limit: 500 }); all.push(...page.events); after = page.next; if (page.events.length < 500) break; } console.log(`${all.length} events; resume from ${after} next time`); ``` Keep the last `next` you processed. Starting from it later returns only what is new or changed since. ## Watch for new events `events.watch` is an async iterator. It yields every event from `after` on, then waits and yields each new one or change as it comes. When it has caught up it polls every `intervalMs`, 15 seconds by default. Stop it with an `AbortSignal`. ```ts import { formatAmount } from "remesso-sdk"; const stop = new AbortController(); process.on("SIGINT", () => stop.abort()); let cursor = 0; // load the last seq you handled from your own storage for await (const e of receiver.events.watch({ role: "to", kinds: ["run", "policy_payment"], after: cursor, signal: stop.signal })) { console.log(e.kind, formatAmount(e.amount!, e.token!), e.final ? "final" : "not final yet"); cursor = e.seq; // save it, so a restart resumes here } ``` Without `after`, a watch starts from the address's first event. To act only on what arrives from now on, store the `seq` of the last event you handled and pass it back when you start again. ## Wait for one payment `events.waitFor` watches until an event matches, then returns it. It throws a `RemessoError` with code [`timeout`](https://docs.remesso.xyz/docs/reference/errors#timeout) after `timeoutMs`, 20 minutes by default. ```ts const paid = await receiver.events.waitFor( (e) => e.kind === "run" && e.refId === scheduleId.toString(), { role: "to", timeoutMs: 15 * 60_000 }, ); console.log("paid in", paid.txHash, "final:", paid.final); ``` ## The five kinds | Kind | Contract | `refId` | `from` | `to` | `amount` | |---|---|---|---|---|---| | `run` | executor | schedule id | the payer | the recipient | what arrived, after the fee | | `schedule_created` | executor | schedule id | the payer | the recipient | what each payment will deliver, after the fee | | `policy_payment` | allowance | policy id | the policy's owner | the recipient | what was paid; agent payments carry no fee | | `policy_created` | allowance | policy id | the owner | none | none | | `policy_revoked` | allowance | policy id | the owner | none | none | `agent` is set on the three policy kinds: the agent the policy names. ## The fields of an event Amounts and ids are strings, because an 18-decimal amount doesn't fit in a JavaScript number. Addresses come back in lowercase, so compare them without regard to case. | Field | Type | Meaning | |---|---|---| | `seq` | number | Position in the feed. It moves whenever the event changes. | | `chainId` | number | `42220`, Celo mainnet. | | `contract` | address | The contract that emitted the log. | | `kind` | string | One of the five kinds above. | | `refId` | string | The schedule id or the policy id. | | `from`, `to`, `agent` | address or null | The payer, the recipient and the paying agent, as the table above sets them. | | `token` | address or null | The token that arrived. | | `amount` | string or null | Base units after the fee: what the recipient received. | | `amountIn` | string or null | For a run, base units before the fee: what left the payer. | | `memo`, `memoText` | string or null | A policy payment's 32-byte memo, and the same as text when it is plain text. A request's reference travels here. | | `runNumber` | number or null | Which payment of the schedule this run was, from 1. | | `early` | boolean or null | For a run, whether a trigger brought it forward. | | `details` | object | Facts specific to the kind (below). | | `txHash`, `logIndex` | string, number | The transaction and the log inside it. Together they identify the event. | | `blockNumber`, `blockHash`, `blockTime` | number, string, string | Where and when it was mined. | | `final` | boolean | At or below Celo's finalized block. | | `removed` | boolean | Undone by a reorg. | What `details` holds depends on the kind: - `run`: `nextRunAt`, the Unix time the next payment is due. An early payment adds `triggeredBy` and `triggersLeft`. - `schedule_created`: `interval` (seconds), `maxRuns`, `expiresAt`, `minRateE6`, `payoutType`, `feeBps`, `fundingToken`, and `builderCodes`: the integrator codes read from the transaction that created it (see [builder codes](https://docs.remesso.xyz/docs/guides/builder-codes)). Events recorded before 25 October 2026 have no `builderCodes`. - `policy_created`: `perPayment`, `perDay`, `expiresAt` and `allowlistOnly`. ## Final and removed A payment appears as soon as the indexer sees it, usually within a minute or two of its transaction, with `final: false`. Celo's finalized block passes it usually 25 to 30 minutes later, and the same event comes round again with `final: true` and a new `seq`. A block above the finalized one can still be replaced by a reorg. If that undoes an event you were shown, it comes round once more with `removed: true`. Removed events are never deleted, so you can always see what changed. So decide what each step of your own process needs: - show "payment received" as soon as an event arrives with `final: false`; - release anything you can't take back, such as goods or a withdrawal, only once the same event reads `final: true`; - treat `removed: true` as "that payment didn't happen", and key your records on `txHash` and `logIndex` so the later versions of an event replace the earlier one. > **Tip** > > For anything valuable, also read the transaction yourself. The event names it in `txHash`, and the chain is the authority. ## Poll, or use webhooks? Both read the same payment events. - **Polling** with `events.list` or `events.watch` needs no public endpoint. It suits scripts, agents and anything behind a firewall. You keep the cursor. - **[Webhooks](https://docs.remesso.xyz/docs/guides/webhooks)** push each payment to your server as it happens, signed, and retry for 24 hours if your server is down. They suit a web service that should react at once. Many integrations use both: webhooks for speed, and a periodic `events.list` from the last `seq` to catch anything missed. The same feed is available over plain HTTP, without the SDK: see [`payment-events` in the HTTP API](https://docs.remesso.xyz/docs/reference/http-api#payment-events). --- # Webhooks > Have Remesso announce every payment to your address at your own server, signed, retried, and verified by the SDK. Source: https://docs.remesso.xyz/docs/guides/webhooks This guide shows how to receive a signed message at your server each time an address is paid, and how to run, verify and manage that endpoint. ## How it works A webhook subscription ties a receiving address to an HTTPS endpoint. From then on, every schedule payment and every agent payment to that address is posted to the endpoint, signed the [Standard Webhooks](https://www.standardwebhooks.com/) way. Only the address can manage its subscriptions. The SDK signs each request with the client's account (an EIP-712 signature), so nobody else can point your payments at their server or fill your subscription slots. Before anything is delivered, Remesso posts a challenge to the endpoint, and the endpoint must echo it. ```mermaid sequenceDiagram participant Code as Your code participant API as Remesso webhooks API participant Server as Your endpoint Code->>API: subscribe, signed by the receiving address API->>Server: POST webhook.verification with a challenge Server-->>API: 200 with the challenge API-->>Code: subscription id and signing secret, shown once Note over API,Server: then, for each payment to the address API->>Server: payment.received, signed Server-->>API: 2xx API->>Server: payment.finalized, signed Server-->>API: 2xx ``` Each payment produces up to three messages: - `payment.received` as soon as the indexer sees the payment, with `final: false`; - `payment.finalized` once Celo's finalized block passes it, usually 25 to 30 minutes later; - `payment.removed` if a reorg undoes a payment you were already told about. The exact format is in [webhook payloads](https://docs.remesso.xyz/docs/reference/webhook-payloads). ## Run a handler `webhookHandler` returns a fetch-style function, `(Request) => Promise`. It answers the challenge, verifies every delivery's signature and calls `onPayment` with the parsed message. ```ts title="receiver.ts" import { createServer } from "node:http"; import { createRemesso, formatAmount, webhookHandler } from "remesso-sdk"; import { toNodeListener } from "remesso-sdk/node"; import { privateKeyToAccount } from "viem/accounts"; const receiver = createRemesso({ account: privateKeyToAccount(process.env.RECEIVER_KEY as `0x${string}`), }); // The secret exists only once the endpoint has answered the challenge, so the // handler reads it when each delivery arrives. let secret: string | undefined = process.env.WEBHOOK_SECRET; const handler = webhookHandler({ recipient: receiver.address, secret: () => secret, onPayment: async (m) => { console.log(m.type, formatAmount(m.data.amount, m.data.token), m.data.final ? "final" : "not final yet", m.data.txHash); }, }); const server = createServer(toNodeListener(handler)); await new Promise((done) => server.listen(8787, done)); ``` The handler answers: | Request | Answer | |---|---| | A challenge for an address in `recipient` | `200` with the challenge | | A challenge for any other address | `403`: a stranger can't point their deliveries at you | | A delivery while there is no secret yet | `503`: Remesso retries | | A delivery whose signature doesn't verify | `401` | | A body that isn't a payment message | `400` | | A verified delivery, and `onPayment` returns | `200` | | A verified delivery, and `onPayment` throws | `500`: Remesso retries with the same `webhook-id` | `recipient` takes one address or a list, if one endpoint receives for several. `secret` takes a string, a list (during a [rotation](https://docs.remesso.xyz/docs/guides/webhooks#rotate-the-secret)), or a function that returns either, read on every delivery. Throw from `onPayment` when you couldn't record the payment, for example because your database is down. Remesso then retries, so nothing is lost. ## Subscribe Start the handler first: subscribing makes Remesso post the challenge to it straight away. Then subscribe from the receiving address: ```ts let secret: string | undefined; const sub = await receiver.webhooks.subscribe({ url: "https://example.com/remesso" }); secret = sub.secret; // shown once console.log(sub.subscription, sub.state, "for", sub.recipient); ``` - The URL must be HTTPS, with no credentials and no fragment, at most 512 characters. - `subscribe` returns `{ ok, subscription, recipient, url, state, secret }`. The secret is returned only this once: store it where your server can read it after a restart, such as a secret manager or an environment variable. - A new subscription starts from now. It isn't sent the address's earlier payments; read those with [payment events](https://docs.remesso.xyz/docs/guides/payment-events). - An address can hold 5 subscriptions. A sixth is refused until you revoke one. - Subscribing a URL that is already subscribed for the address fails with [`conflict`](https://docs.remesso.xyz/docs/reference/errors#conflict): rotate its secret, or revoke it and subscribe again. If the endpoint doesn't echo the challenge, `subscribe` fails with `invalid` and says why, for example `the endpoint answered 404`. Nothing is kept, so fix the endpoint and subscribe again. > **Tip** > > For a quick test without a server on the internet, a Cloudflare quick tunnel gives your local port a public HTTPS address: `cloudflared tunnel --url http://localhost:8787`. It gets a new URL each time it starts, so revoke the old subscription when you're done. ## Run it on other runtimes The handler is a standard fetch handler, so it runs anywhere `Request` and `Response` exist. **Deno:** ```ts import { webhookHandler } from "remesso-sdk"; const handler = webhookHandler({ recipient: Deno.env.get("RECEIVER_ADDRESS")!, secret: Deno.env.get("WEBHOOK_SECRET"), onPayment: (m) => console.log(m.type, m.data.txHash), }); Deno.serve({ port: 8787 }, handler); ``` **Cloudflare Workers:** ```ts import { webhookHandler } from "remesso-sdk"; export default { async fetch(req: Request, env: { RECEIVER_ADDRESS: string; WEBHOOK_SECRET: string }): Promise { const handler = webhookHandler({ recipient: env.RECEIVER_ADDRESS, secret: env.WEBHOOK_SECRET, onPayment: (m) => console.log(m.type, m.data.txHash), }); return await handler(req); }, }; ``` **A Next.js route handler**, such as `app/api/remesso/route.ts`: ```ts import { webhookHandler } from "remesso-sdk"; const handler = webhookHandler({ recipient: process.env.RECEIVER_ADDRESS!, secret: process.env.WEBHOOK_SECRET, onPayment: async (m) => { // record the payment console.log(m.type, m.data.txHash); }, }); export async function POST(req: Request): Promise { return await handler(req); } ``` **Bun** serves it as it is: `Bun.serve({ port: 8787, fetch: handler })`. **Express** takes it through `toNodeListener`: `app.post("/remesso", toNodeListener(handler))`. Register that route before any JSON body parser such as `express.json()`. The signature covers the raw body, and a parser that has already consumed it leaves nothing to verify. ## Deduplicate deliveries A delivery can arrive more than once, for example when your server handled it but its answer was lost. Every attempt at one delivery carries the same `webhook-id`, which the handler passes on as `m.id`. Record the ids you have processed and skip repeats: ```ts import { webhookHandler } from "remesso-sdk"; const seen = new Set(); // use your database in production const handler = webhookHandler({ recipient: receiver.address, secret: process.env.WEBHOOK_SECRET, onPayment: async (m) => { if (seen.has(m.id)) return; // ... record the payment ... seen.add(m.id); }, }); ``` `payment.received` and `payment.finalized` for the same payment are two deliveries with two ids. To track the payment itself, key your records on `txHash` and `logIndex`, and update them as `final` and `removed` change. ## Retries A delivery succeeds when your endpoint answers with a 2xx status within 10 seconds. Anything else is retried, with the same `webhook-id`, after 1 minute, 5 minutes, 30 minutes, 2 hours and 6 hours, then every 6 hours, for 24 hours from the first attempt. After that the delivery is held as failed until you [replay](https://docs.remesso.xyz/docs/guides/webhooks#replay-failed-deliveries) it. Each attempt is signed again with a fresh timestamp, and the signature is valid for 5 minutes, so keep your server's clock accurate. ## Verify a delivery yourself If you don't use `webhookHandler`, verify each delivery against the raw body before trusting it. `parseWebhook` verifies and parses in one step, and throws a `RemessoError` when the signature doesn't verify (`refused`) or the body isn't a payment message (`invalid`): ```ts import { parseWebhook, RemessoError } from "remesso-sdk"; export async function handle(req: Request): Promise { const raw = await req.text(); // the exact bytes that were signed try { const m = await parseWebhook(raw, req.headers, process.env.WEBHOOK_SECRET!); console.log(m.id, m.type, m.data.amount); return new Response("ok"); } catch (e) { const status = e instanceof RemessoError && e.code === "invalid" ? 400 : 401; return new Response("refused", { status }); } } ``` `verifyWebhook(raw, headers, secrets)` answers only `true` or `false`. Both take one secret or a list. Your endpoint must still answer the challenge when you subscribe: reply `200` with the challenge, as the whole body or as `{"challenge": "..."}`. ## Rotate the secret `rotate` issues a new signing secret. The old one keeps signing alongside it for a day, so you can deploy the new secret without missing a delivery. ```ts const r = await receiver.webhooks.rotate({ url: "https://example.com/remesso" }); console.log("new secret:", r.secret, "old one valid until", r.previousSecretValidUntil); ``` Until `previousSecretValidUntil`, every delivery carries a signature from each secret, so a server holding either one verifies. Rotate when a secret may have leaked, or when you have lost it. ## Revoke a subscription ```ts await receiver.webhooks.revoke({ url: "https://example.com/remesso" }); ``` Deliveries to that URL stop, and the slot frees up for another subscription. ## Replay failed deliveries If your endpoint was down for longer than the retry window, `replay` queues its failed deliveries again. `days` reaches back 1 to 7 days, 1 by default. ```ts const { requeued } = await receiver.webhooks.replay({ url: "https://example.com/remesso", days: 3 }); console.log(requeued, "deliveries queued again"); ``` Replayed deliveries keep their original `webhook-id`, so your deduplication still applies. ## Manage webhooks for another address `subscribe`, `rotate`, `revoke` and `replay` act for the client's own address by default. Each also takes `recipient`, for a smart account you control: the client's account signs, and Remesso checks that signature against `recipient`, which a smart account accepts through ERC-1271. For any other address the signature doesn't match, and the request is refused. Managing webhooks needs an account that can sign. On a read-only client these calls fail with [`no_account`](https://docs.remesso.xyz/docs/reference/errors#no_account). The same API is available over HTTP: see [`webhooks` in the HTTP API](https://docs.remesso.xyz/docs/reference/http-api#webhooks). --- # Builder codes > Put your own ERC-8021 code on the schedules you create, so Celo credits their volume to you on every payment. Source: https://docs.remesso.xyz/docs/guides/builder-codes This guide shows how to get credit for the payments your app brings to Celo: your builder code goes on the transaction that creates a schedule, and Remesso's executor carries it on every payment of that schedule. ## What is a builder code? A builder code is a short name for your app, written after a transaction's calldata in the format [ERC-8021](https://github.com/celo-org/attribution-tags) defines. The EVM ignores those trailing bytes, so the code changes nothing about what the transaction does. It only records who caused it. Celo reads these codes to credit activity to the apps that produced it, and a transaction that went out untagged can't be credited later. Remesso tags every transaction it causes with its own codes. Builder codes add yours. ## Why the executor carries your code You sign one transaction to create a schedule, but its payments are sent later, one by one, by Remesso's executor. Without builder codes, those payments would carry only Remesso's codes, and the volume your schedule moves would never be credited to you. So the executor reads your codes from the transaction that created the schedule and puts them, after its own, on every payment of it, for as long as the schedule runs. ## Add your code Pass `builderCodes` when you create the client: ```ts title="pay.ts" import { createRemesso } from "remesso-sdk"; import { privateKeyToAccount } from "viem/accounts"; const payer = createRemesso({ account: privateKeyToAccount(process.env.PAYER_KEY as `0x${string}`), builderCodes: ["your_code"], }); const s = await payer.schedules.create({ to: "0xRecipient", amount: "5", token: "USDT", every: "month", runs: 12 }); console.log("schedule", s.id, "created in", s.hash); ``` Every transaction this client sends carries Remesso's codes and then yours: the approval, the schedule, policies and agent payments. Each payment of the schedule then carries Remesso's codes and yours. `attribution: false` leaves Remesso's codes out and keeps yours. ## The rules The SDK checks your codes when you create the client, by the same rules the executor applies, and throws a `RemessoError` with code [`invalid`](https://docs.remesso.xyz/docs/reference/errors#invalid) for any it wouldn't carry: - up to three codes; - each 1 to 32 characters of lowercase `a` to `z`, `0` to `9` and `_`, as Celo's attribution codes are; - no repeats; - none of Remesso's own codes, `remesso` and `celo_5cd35ca55baf`: those are carried anyway. Register your code with Celo so its volume is credited to you. Tagging works either way. ## Check that it was read About a minute after the schedule is created, Remesso's indexer has read its transaction. The schedule's `schedule_created` [payment event](https://docs.remesso.xyz/docs/guides/payment-events) lists what it found under `details.builderCodes`: ```ts const { events } = await payer.events.list({ role: "from", kinds: ["schedule_created"] }); const created = events.find((e) => e.refId === scheduleId.toString()); console.log(created?.details?.builderCodes); // ["your_code"] ``` An empty list means the transaction carried none of yours. To check a payment itself, read its transaction with `verifyTx` from Celo's [`@celo/attribution-tags`](https://www.npmjs.com/package/@celo/attribution-tags), which returns every code its suffix carries: ```ts import { verifyTx } from "@celo/attribution-tags"; import { createPublicClient, http } from "viem"; import { celo } from "viem/chains"; const client = createPublicClient({ chain: celo, transport: http("https://forno.celo.org") }); const tag = await verifyTx({ client, hash: "0x…" }); // a run's txHash, from a payment event or a webhook console.log(tag?.codes); // ["remesso", "celo_5cd35ca55baf", "your_code"] ``` ## Tag without the SDK If you create schedules with your own code, tag the `createSchedule` transaction yourself. `withAttribution` from `@celo/attribution-tags` adds the suffix to every transaction a viem wallet client sends, and `toDataSuffix` returns it for you to append: ```ts import { withAttribution } from "@celo/attribution-tags"; import { createWalletClient, http } from "viem"; import { privateKeyToAccount } from "viem/accounts"; import { celo } from "viem/chains"; const wallet = createWalletClient({ account: privateKeyToAccount(process.env.PAYER_KEY as `0x${string}`), chain: celo, transport: http("https://forno.celo.org"), }).extend(withAttribution("your_code")); ``` The executor reads Schema 0, the format both functions write: a list of codes. It doesn't read Schema 2 (roles such as app and wallet). A schedule created through an ERC-4337 bundle isn't read yet either: the executor reads the outer transaction, which is the bundler's. `attributionFor` from remesso-sdk gives the suffix with Remesso's codes first and yours after, checked by the same rules, for a transaction you send yourself: ```ts import { attributionFor } from "remesso-sdk"; const suffix = attributionFor(["your_code"]); // pass it as viem's dataSuffix console.log(suffix); ``` ## What it doesn't change The suffix sits after the calldata, where the contract never reads it. A builder code changes who is credited, never what a payment does: the recipient, the amount and the cadence are the ones the payer signed. If your codes and Remesso's don't fit in one suffix, the executor sends Remesso's alone rather than leave a payment untagged. --- # Find agents > Look up an agent in the ERC-8004 registry on Celo, get the only address it can safely be paid at, and read what it accepts. Source: https://docs.remesso.xyz/docs/guides/find-agents This guide shows how to find another agent by its ERC-8004 id, where to pay it, and what its payee profile says it accepts. ## What `agents.find` returns [ERC-8004](https://eips.ethereum.org/EIPS/eip-8004) is a registry of agents on-chain: each has a numeric id, an owner, a wallet and a registration file. `agents.find` reads an agent from the registry on Celo: ```ts async function lookUp(agentId: bigint) { const found = await reader.agents.find(agentId); if (!found.wallet) { console.log("don't pay it:", found.note); return; } console.log("pay", found.wallet, "accepts", found.profile?.assets ?? "not stated"); } ``` It returns: | Field | Meaning | |---|---| | `agentId` | The id you asked for, as a `bigint`. | | `wallet` | Where to pay the agent, or `null` when it has set no wallet. Then don't pay it. | | `profile` | Its payee profile (below), or `null` when it has none Remesso can read. | | `note` | Why there is no wallet or no profile, when there isn't. | A read-only client is enough: finding an agent signs nothing. ## Where to pay an agent The address comes from the registry's `getAgentWallet`, and only from there. That value can be set only by a signature from the wallet itself, and the registry clears it whenever the agent's token changes hands. The registration file is different: whoever holds the agent's token can rewrite it at any time. So `agents.find` never takes an address from it, even when the file names one. Do the same in your own code: pay `found.wallet`, never an address from a document or a message. ## The payee profile An agent can say what it accepts in a `remesso` entry in the `services` list of its registration file: ```json { "name": "remesso", "endpoint": "https://engaboljiqudghvzmebq.supabase.co/functions/v1/request-payment", "version": "1", "assets": [ { "network": "eip155:42220", "asset": "0x48065fbbe25f71c9282ddf5e1cd6d6a887483d5e" } ], "reference": { "required": true } } ``` `agents.find` reads it into `found.profile`: | Field | Meaning | |---|---| | `version` | `"1"`. An entry of another version reads as no profile. | | `endpoint` | Remesso's request service, where a payer checks and answers the agent's requests, or `null`. | | `assets` | The tokens accepted: the chain as CAIP-2 (`eip155:42220` for Celo) and the token's address. | | `reference` | `{ required: true }` when every payment must carry the agent's request reference, otherwise `null` or `{ required: false }`. | Match tokens by address, never by symbol: on Celo, a fee-currency adapter reports the same symbol as the real token. `findToken` from remesso-sdk takes an address: ```ts import { findToken, type FoundAgent } from "remesso-sdk"; function payableTokens(found: FoundAgent) { return (found.profile?.assets ?? []) .filter((a) => a.network === "eip155:42220") .map((a) => findToken(a.asset)) .filter((t) => t !== undefined); } async function show(agentId: bigint) { const found = await reader.agents.find(agentId); console.log("tokens Remesso can pay it in:", payableTokens(found).map((t) => t.symbol)); } ``` To publish a profile for your own agent, add the entry to your registration file. Its format and the reasoning behind it are in [AGENTS.md](https://bitbucket.org/crackedstudios/remesso/src/main/docs/AGENTS.md). Never add an address entry such as `agentWallet` to the file: some tools take one as the agent's wallet without reading the chain. ## Remote registration files A registration file is a `data:` URI inside the token, or a link to one at `https:` or `ipfs:`. `agents.find` reads a `data:` URI with no network request. For a remote file it needs a fetcher, because the URL is whatever the agent's owner chose. In Node, pass `safeFetchText` from `remesso-sdk/node`: ```ts title="find.ts" import { createRemesso } from "remesso-sdk"; import { safeFetchText } from "remesso-sdk/node"; // npx tsx find.ts const reader = createRemesso({ fetchText: safeFetchText }); const found = await reader.agents.find(BigInt(process.argv[2])); console.log(found.wallet, found.profile, found.note ?? ""); ``` `safeFetchText` fetches only from public addresses. It allows HTTPS on the default port with no credentials and no redirects, and refuses any name that resolves to a private, loopback, link-local or otherwise reserved address. It then connects to the address it checked, so DNS can't be switched in between, and stops at 5 seconds or 256 KB. An `ipfs:` URI is read through a public gateway. Without `fetchText`, a remote file isn't read: `profile` is `null` and `note` says so. Pass your own `fetchText` in other runtimes, with the same care. ## Pay the agent you found Once you have `found.wallet`, pay it as you would any address. **On a schedule**, as the payer: ```ts async function subscribeTo(agentId: bigint) { const found = await payer.agents.find(agentId); if (!found.wallet) throw new Error(found.note); return await payer.schedules.create({ to: found.wallet, amount: "5", token: "USDT", every: "month", runs: 6 }); } ``` **As an agent, under a policy**, see [agent payments](https://docs.remesso.xyz/docs/guides/agent-payments): ```ts async function payInvoice(agentId: bigint, invoice: string) { const found = await agent.agents.find(agentId); if (!found.wallet) throw new Error(found.note); return await agent.pay({ to: found.wallet, amount: "0.50", token: "USDT", idempotencyKey: `agent-${agentId}-${invoice}` }); } ``` When the profile says `reference: { required: true }`, the agent expects each payment to carry the reference of a request it sent you. Ask it for a [payment request](https://docs.remesso.xyz/docs/guides/payment-requests), then answer that request: an agent pays it with `requests.pay(id)`, which puts the reference in the payment's memo, and a schedule answers it through `schedules.create({ request: id })`. --- # Fees, limits and gas > What each payment costs, the amounts Remesso's executor sends, and who pays gas for what. Source: https://docs.remesso.xyz/docs/guides/fees-and-gas This page covers what a payment costs: Remesso's fee on scheduled payments, the range of amounts its executor sends, and the gas each step uses and who pays it. ## The fee on scheduled payments Remesso takes **0.25% of each scheduled payment** (25 basis points), from the payment itself, rounded up to the token's smallest unit. The rest arrives. | Each payment sends | Fee | The recipient receives | |---|---|---| | 1 USDT | 0.0025 USDT | 0.9975 USDT | | 50 USDT | 0.125 USDT | 49.875 USDT | | 1,000 USDT | 2.5 USDT | 997.5 USDT | | 1,500 wARS | 3.75 wARS | 1,496.25 wARS | | 5 wBRL | 0.0125 wBRL | 4.9875 wBRL | A few things follow from how the fee is set: - **It is pinned when the schedule is signed.** Each schedule stores its own `feeBps`, and nothing can raise it afterwards. The contract caps any fee at 0.5% in code. `schedules.create` reads the current fee from the contract when it builds a schedule, and `FEE_BPS` is that value as of the SDK's release. - **Policy payments carry no fee.** What an agent pays is what arrives. - **Receiving costs nothing,** and so do payment requests, payment events and webhooks. ### Work out the numbers The SDK exports the contract's own arithmetic, in base units: ```ts import { FEE_BPS, feeOf, formatAmount, grossFor, netOf, token, toUnits } from "remesso-sdk"; const usdt = token("USDT"); const gross = toUnits("50", usdt); // 50_000_000n formatAmount(feeOf(gross, FEE_BPS), usdt); // "0.125 USDT" formatAmount(netOf(gross, FEE_BPS), usdt); // "49.875 USDT" // What to send so that exactly 50 arrives: formatAmount(grossFor(toUnits("50", usdt), FEE_BPS), usdt); // "50.125314 USDT" ``` - `feeOf(gross, bps)` is what the contract keeps, rounded up as the contract rounds it: `feeOf(1_000_001n, 25)` is `2_501n`, not 2,500. - `netOf(gross, bps)` is what arrives. - `grossFor(net, bps)` is the smallest amount to send for at least `net` to arrive. This is what `schedules.create({ receive })` signs, and what a payer's form signs for a [payment request](https://docs.remesso.xyz/docs/guides/payment-requests). For a schedule that already exists, use its own `feeBps`, from `schedules.get(id)`. `recipientGets` on a schedule is already `netOf` its amount. ### What a whole schedule costs The approval covers every payment, so the payer needs the gross amount times the number of payments: ```ts import { FEE_BPS, formatAmount, grossFor, token, toUnits } from "remesso-sdk"; const usdt = token("USDT"); const each = grossFor(toUnits("50", usdt), FEE_BPS); // 50 arrives each month const total = each * 12n; console.log(`approve ${formatAmount(total, usdt)} for 12 payments`); // approve 601.503768 USDT for 12 payments ``` ## Per-payment limits Remesso's executor sends the equivalent of **1 to 1,000 US dollars per payment**, counted on what leaves the payer's wallet, before the fee. A schedule outside that range is valid on-chain and never runs, because the executor won't send it. So the SDK refuses it before signing, with [`refused`](https://docs.remesso.xyz/docs/reference/errors#refused). | Token | Currency | Decimals | Per payment, before the fee | Pays gas | Address | |---|---|---|---|---|---| | **USDT** | USD | 6 | 1 to 1,000 | Yes | [`0x48065fbBE25f71C9282ddf5e1cD6D6A887483D5e`](https://celoscan.io/address/0x48065fbBE25f71C9282ddf5e1cD6D6A887483D5e) | | **USDC** | USD | 6 | 1 to 1,000 | Yes | [`0xcebA9300f2b948710d2653dD7B07f33A8B32118C`](https://celoscan.io/address/0xcebA9300f2b948710d2653dD7B07f33A8B32118C) | | **USDm** | USD | 18 | 1 to 1,000 | Yes | [`0x765DE816845861e75A25fCA122bb6898B8B1282a`](https://celoscan.io/address/0x765DE816845861e75A25fCA122bb6898B8B1282a) | | **USA₮** | USD | 6 | 1 to 1,000 | Yes | [`0xD2ab3C9A02DBBAB236BfEC45D1d755DF4267F771`](https://celoscan.io/address/0xD2ab3C9A02DBBAB236BfEC45D1d755DF4267F771) | | **wARS** | ARS | 18 | 1,500 to 1,500,000 | No | [`0x0DC4F92879B7670e5f4e4e6e3c801D229129D90D`](https://celoscan.io/address/0x0DC4F92879B7670e5f4e4e6e3c801D229129D90D) | | **wBRL** | BRL | 18 | 5 to 5,000 | No | [`0xD76f5Faf6888e24D9F04Bf92a0c8B921FE4390e0`](https://celoscan.io/address/0xD76f5Faf6888e24D9F04Bf92a0c8B921FE4390e0) | | **wCOP** | COP | 18 | 3,100 to 3,100,000 | No | [`0x8a1D45e102e886510e891d2Ec656a708991e2D76`](https://celoscan.io/address/0x8a1D45e102e886510e891d2Ec656a708991e2D76) | | **wMXN** | MXN | 18 | 18 to 18,000 | No | [`0x337E7456B420bD3481e7FA61fA9850343d610d34`](https://celoscan.io/address/0x337E7456B420bD3481e7FA61fA9850343d610d34) | | **wCLP** | CLP | 18 | 950 to 950,000 | No | [`0x61D450a098b6a7f69fC4b98CE68198fe59768651`](https://celoscan.io/address/0x61D450a098b6a7f69fC4b98CE68198fe59768651) | | **wPEN** | PEN | 18 | 3 to 3,000 | No | [`0x4F34c8b3b5FB6D98Da888F0feA543d4d9C9F2eBE`](https://celoscan.io/address/0x4F34c8b3b5FB6D98Da888F0feA543d4d9C9F2eBE) | | **cNGN** | NGN | 6 | 1,300 to 1,300,000 | No | [`0xF6829D7393dAe24509eb1E52eE8e572e2E271a4f`](https://celoscan.io/address/0xF6829D7393dAe24509eb1E52eE8e572e2E271a4f) | These come from the deployment the SDK is generated from, and Remesso's capability document publishes the live values under `limits.perRun`. In code, `runLimits` gives the bounds in base units: ```ts import { formatAmount, runLimits, token } from "remesso-sdk"; const wars = token("wARS"); const { min, max } = runLimits(wars); console.log(`${formatAmount(min, wars)} to ${formatAmount(max, wars)} per payment`); // 1500 wARS to 1500000 wARS per payment ``` With `receive`, the limit applies to the gross amount the SDK works out. Receiving exactly 1 USDT sends 1.002507 USDT, inside the range. Asking to receive 999 USDT sends about 1,001.5 USDT, which is refused. Agent payments have no executor limits. The policy's caps per payment and per day are their limits. See [Agent payments](https://docs.remesso.xyz/docs/guides/agent-payments). ## Who pays gas Every transaction on Celo pays gas. Remesso pays for the payments its executor sends, and you pay for the transactions your own key sends: | Step | Who pays the gas | Measured on our own transactions | |---|---|---| | Creating a schedule: an approval, then `createSchedule` | The payer | About 224,000 gas for `createSchedule`, plus the approval | | Each scheduled payment | Remesso's executor, covered by the fee | About 91,875 gas | | Pausing, resuming, cancelling, or changing a trigger | The payer | One transaction each | | Creating a policy: an approval, then `createPolicy` | The person | Two transactions | | Each agent payment | The agent's key | About 85,000 gas | | Receiving a payment | Nobody | None | | Requests, payment events, webhooks | Nobody: they are HTTP calls | None | These figures were measured, not promised: at 202 gwei, with CELO near $0.09, creating a schedule cost about $0.004. They move with the gas price and the price of CELO. ## Paying gas without CELO Celo lets some tokens pay for gas themselves, which Celo calls fee abstraction. The SDK uses it automatically. Before each transaction it looks at the account's CELO: 1. If the account holds at least twice the estimated cost in CELO, it pays in CELO. 2. If not, and the transaction's token can pay gas and the account holds some of it, it pays the gas in that token. 3. Otherwise it throws [`no_gas`](https://docs.remesso.xyz/docs/reference/errors#no_gas) before signing, saying how much CELO the transaction needs. The token it uses is the one the transaction is about: - **creating or managing a schedule:** the schedule's token; - **creating or managing a policy:** the policy's token; - **an agent payment:** the policy's token, but held by the agent's own key, since the agent pays its gas. Most agents hold none, so they need a little CELO. USDT, USDC, USDm and USA₮ can pay gas: the "Pays gas" column in the table above. Ripio's tokens and cNGN can't, so a payer funding a schedule in them needs some CELO. Gas paid in a token costs a little more gas than gas paid in CELO. In our mainnet test on 2026-10-25, a wallet holding no CELO sent an approval, a `createPolicy` and a `revoke`, which used 115,469, 211,253 and 115,498 gas. The three cost 0.007028 USDT in all. ```ts import { RemessoError } from "remesso-sdk"; try { await payer.schedules.create({ to: "0xRecipient", amount: "1500", token: "wARS", every: "month", runs: 3 }); } catch (e) { // wARS can't pay gas: the message says how much CELO to send the payer. if (e instanceof RemessoError && e.code === "no_gas") console.error(e.message); else throw e; } ``` ## Early sends An early send brings one payment of a schedule forward, when its payer granted a trigger (see [Let a payment be sent early](https://docs.remesso.xyz/docs/guides/schedules#let-a-payment-be-sent-early)): - **Through Remesso,** for a schedule whose trigger is `"remesso"`: `trigger-run` costs 0.01 USDC, USDT or USA₮, or the same in a Ripio token, paid over x402. The x402 facilitator pays the settlement's gas, and Remesso's executor pays for the run. See the [HTTP API](https://docs.remesso.xyz/docs/reference/http-api). - **Yourself,** when your own address is the trigger: call `runNow` on the contract, and pay its gas. An early send costs no extra fee beyond the 0.25% the payment carries anyway, and it never adds a payment. ## Next steps - [Tokens and limits](https://docs.remesso.xyz/docs/reference/tokens-and-limits): every token, with its address and decimals. - [Pay on a schedule](https://docs.remesso.xyz/docs/guides/schedules): `amount` or `receive`, and everything else `create` takes. - [Errors](https://docs.remesso.xyz/docs/reference/errors): what `refused` and `no_gas` carry. --- # Use cases > Six worked examples of building on remesso-sdk, from a freelancer's monthly invoice to an AI agent with a spending budget. Source: https://docs.remesso.xyz/docs/use-cases Each use case is a small, complete program you can run on Celo mainnet with a few dollars. They all use the same building blocks: schedules, payment requests, agent policies, payment events and webhooks. Each one puts them together for a single job. Pick the one closest to what you're building, then read the guides it links to for the details. | Use case | Who it's for | What it does | SDK features | |---|---|---|---| | [Bill a client every month](https://docs.remesso.xyz/docs/use-cases/freelancer-retainer) | Freelancers and agencies | Asks a client for a monthly retainer in pesos or dollars, then writes a receipt for each payment as it lands | Payment requests, webhooks | | [Subscriptions for your app](https://docs.remesso.xyz/docs/use-cases/subscriptions) | SaaS and API products | Bills each customer monthly, grants access when a payment arrives, and notices a cancelled or paused subscription | Payment requests, webhooks, schedule status | | [Pay a team on a schedule](https://docs.remesso.xyz/docs/use-cases/payroll) | Small companies, an employer's agent | Signs one schedule per contractor so each receives an exact amount, budgets the total, and checks funding before payday | Schedules, fees, schedule status | | [Send money home every month](https://docs.remesso.xyz/docs/use-cases/remittances) | People supporting family abroad | Pays a family member in USDT every month and tells them each time, with a link to cash out | Schedules, payment events | | [A weekly allowance, approved by an agent](https://docs.remesso.xyz/docs/use-cases/family-allowance) | Families, two agents | The child's agent asks for cNGN every week, and the parent's agent signs a schedule after the parent says yes | Payment requests, schedules, webhooks | | [Give an AI agent a budget](https://docs.remesso.xyz/docs/use-cases/ai-agent-budget) | People whose agent pays for things | Caps what an agent's key can spend, pays with idempotency keys so a retry never pays twice, and lets the person review and revoke | Agent policies, agent payments, payment requests | ## Before you start Every use case assumes the setup from [Installation](https://docs.remesso.xyz/docs/installation): Node 20 or later, `remesso-sdk` and `viem` installed, and `tsx` to run TypeScript files directly. If you haven't sent a payment yet, the [Quickstart](https://docs.remesso.xyz/docs/quickstart) does it end to end in about 15 minutes. A few facts hold across all of them: - **The payer always signs.** Remesso can't pull money from anyone. A request or a link only fills in a form. Money moves when the payer signs a schedule in their own wallet, or when an agent pays within a policy its person signed. - **Receiving costs nothing.** A recipient needs an address, and no gas, balance or account. - **Scheduled payments carry a 0.25% fee**, taken from each payment. Agent policy payments carry none. See [Fees and gas](https://docs.remesso.xyz/docs/guides/fees-and-gas). - **Each scheduled payment is the equivalent of 1 to 1,000 dollars**, in whichever token it pays. Outside that range a schedule is valid on-chain but never runs, so the SDK refuses it before signing. [Tokens and limits](https://docs.remesso.xyz/docs/reference/tokens-and-limits) lists the bounds per token. --- # Bill a client every month > A freelancer's agent asks a client for a monthly retainer, then writes a receipt for every payment as it arrives and becomes final. Source: https://docs.remesso.xyz/docs/use-cases/freelancer-retainer A freelancer in Buenos Aires bills a client every month, in Argentine pesos. Chasing each transfer takes time, and converting through dollars costs money at every step. With Remesso the freelancer asks once and the client signs once. After that, each month's payment arrives on its own, and the freelancer's agent writes a receipt for it. ## What you'll build Three small programs, run by the freelancer: - `request.ts` asks the client's wallet for a monthly amount and prints a link for the client. - `receiver.ts` serves a webhook. It writes a receipt for each payment, then marks it final once Celo finalizes the block. - `status.ts` shows where the request stands and lists its payments. ```mermaid sequenceDiagram participant F as Freelancer's agent participant R as Remesso participant C as Client participant E as Executor F->>R: requests.create (1,500 wARS a month) R-->>C: request in their inbox, and a link C->>C: signs the schedule in their own wallet E->>F: each month, the payment R->>F: payment.received, then payment.finalized F->>F: writes and updates the receipt ``` ## Prerequisites - The setup from [Installation](https://docs.remesso.xyz/docs/installation), and a key for the freelancer. Receiving needs no gas and no balance. - The client's wallet address. The client needs wARS for each payment plus Remesso's 0.25% fee, and about 0.05 CELO of gas to sign the schedule once. - A public HTTPS address that reaches port 8787 on your machine. For a test, `cloudflared tunnel --url http://localhost:8787` prints one with no account. wARS is Ripio's Argentine peso stablecoin. MiniPay doesn't show it, so the freelancer needs a wallet that does. Ripio's ramp turns it into pesos in a bank account, from the freelancer's own wallet: `https://ramp.ripio.com/off-ramp?country=AR&chain=42220&token=WARS`. ```bash export FREELANCER_KEY=0x… CLIENT=0x… PUBLIC_URL=https://….trycloudflare.com ``` ## Step 1: Ask the client The amount you ask for is what you receive. The client's form adds Remesso's fee on top. The executor sends 1,500 to 1,500,000 wARS per payment, so ask for at least the minimum. ```ts title="request.ts" import { writeFile } from "node:fs/promises"; import { createRemesso, formatAmount, runLimits, token, toUnits } from "remesso-sdk"; import { privateKeyToAccount } from "viem/accounts"; const freelancer = createRemesso({ account: privateKeyToAccount(process.env.FREELANCER_KEY as `0x${string}`), }); const amount = process.env.AMOUNT ?? "1500"; // wARS a month: what you receive const wars = token("wARS"); const { min } = runLimits(wars); if (toUnits(amount, wars) < min) throw new Error(`ask for at least ${formatAmount(min, wars)} a month`); const req = await freelancer.requests.create({ payer: process.env.CLIENT!, amount, token: "wARS", every: "month", runs: 12, from: "Your name", note: "Monthly retainer", }); await writeFile("request.json", JSON.stringify({ id: req.id, reference: req.reference })); console.log("Send your client this link:", req.link); ``` The request appears in the client's Remesso inbox, and the link opens their form with every field filled in. The link carries the request id, so the schedule they sign is reported against your request. Nothing is authorised yet. A request can't move money: the client reviews the form and signs in their own wallet. ## Step 2: The client signs The client answers in one of two ways: - They open the link in their wallet's browser and sign in the Remesso app. - Their own code answers with the request's terms, adding the fee so you receive the full amount: ```ts await payer.schedules.create({ request: requestId }); // the request's terms, the fee on top ``` Either way, the result is a schedule on-chain. From then on nobody can change the amount, the recipient or the cadence, not even the client. The client can pause it or cancel it, and nothing else. ## Step 3: Hear each payment and write a receipt `receiver.ts` checks that each payment comes from the schedule answering your request, then writes one receipt per payment. When the payment becomes final, the same receipt is rewritten as final. If a reorg undoes a payment, the receipt says it was void. ```ts title="receiver.ts" import { mkdir, readFile, writeFile } from "node:fs/promises"; import { createServer } from "node:http"; import { createRemesso, formatAmount, RemessoError, webhookHandler, type WebhookMessage } from "remesso-sdk"; import { toNodeListener } from "remesso-sdk/node"; import { privateKeyToAccount } from "viem/accounts"; const freelancer = createRemesso({ account: privateKeyToAccount(process.env.FREELANCER_KEY as `0x${string}`), }); const url = process.env.PUBLIC_URL!; const saved = JSON.parse(await readFile("request.json", "utf8")) as { id: string; reference: string }; let secret: string | undefined; const seen = new Set(); // webhook-ids already handled: keep these on disk in production async function onPayment(m: WebhookMessage) { if (seen.has(m.id) || !m.data.schedule) return; const r = await freelancer.requests.get(saved.id); // The answer may not be recorded yet. Throwing makes Remesso retry the delivery. if (!r.payment?.schedule) throw new Error(`request ${saved.id} isn't answered yet`); if (r.payment.schedule.id !== m.data.schedule) return; // another payment to this address await writeReceipt(m); seen.add(m.id); } /// One receipt per payment, rewritten when it becomes final or is undone. async function writeReceipt(m: WebhookMessage) { const d = m.data; const state = m.type === "payment.removed" ? "Void: the block holding this payment was replaced. Nothing was received." : d.final ? "Final." : "Received. Not final yet: Celo finalizes in about half an hour."; await mkdir("receipts", { recursive: true }); await writeFile(`receipts/${saved.reference}-${d.runNumber}.md`, [ `# Receipt ${saved.reference.slice(0, 8)}-${d.runNumber}`, `Received: ${formatAmount(d.amount, d.token)}`, `From: ${d.from}`, `Date: ${d.blockTime}`, `Transaction: https://celoscan.io/tx/${d.txHash}`, "", state, ].join("\n")); console.log(`${m.type}: payment ${d.runNumber}, ${formatAmount(d.amount, d.token)}`); } const handler = webhookHandler({ recipient: freelancer.address, secret: () => secret, onPayment }); const server = createServer(toNodeListener(handler)); await new Promise((done) => server.listen(8787, done)); try { secret = (await freelancer.webhooks.subscribe({ url })).secret; } catch (e) { // Subscribed before, and the secret is lost: rotate to a new one. if (!(e instanceof RemessoError && e.code === "conflict")) throw e; secret = (await freelancer.webhooks.rotate({ url })).secret; } console.log(`listening for payments on request ${saved.id} at ${url}`); ``` A few details matter here: - **The secret arrives after the server starts.** Remesso first posts a challenge to your URL, and the handler answers it only for your address. `subscribe` returns the signing secret once that has worked, so the handler reads `secret` on each delivery. Store it with mode 0600 if the receiver must survive a restart. - **Retries reuse the `webhook-id`.** A failed delivery is retried with the same id for 24 hours, so dedupe on `m.id`. - **Throwing asks for a retry.** If `onPayment` throws, the handler answers 500 and Remesso delivers the message again later. ## Step 4: Check where it stands Reading a request needs no key: ```ts title="status.ts" import { readFile } from "node:fs/promises"; import { createRemesso, formatAmount } from "remesso-sdk"; const { id } = JSON.parse(await readFile("request.json", "utf8")) as { id: string }; const r = await createRemesso().requests.get(id); console.log(`request ${r.id}: ${r.status}`); if (r.payment) console.log(`payment: ${r.payment.state}${r.payment.reason ? ` (${r.payment.reason})` : ""}`); for (const tx of r.payment?.txs ?? []) { console.log(`${tx.blockTime} ${formatAmount(tx.amount, "wARS")} ${tx.final ? "final" : "not final yet"} https://celoscan.io/tx/${tx.txHash}`); } ``` Accepted isn't the same as paid. A request reads `accepted` once a schedule answers it, and its payment reads `paid` only once a payment has a transaction. ## Test it safely 1. Use a second wallet you control as the client. Give it about 1,510 wARS and a little CELO. 2. Ask for `AMOUNT=1500` with `runs: 1`, the smallest payment the executor sends. 3. Start `receiver.ts`, then sign the schedule from the client wallet. 4. If the client signs in the Remesso app, the first payment goes out within a minute or two. A schedule created from code is found by Remesso within about five minutes, and then paid. `payment.received` arrives soon after, and `payment.finalized` follows 25 to 30 minutes later. ## Where to go next - [Payment requests](https://docs.remesso.xyz/docs/guides/payment-requests): request states, declines and pay links. - [Webhooks](https://docs.remesso.xyz/docs/guides/webhooks): subscriptions, secret rotation and replays. - [Payment events](https://docs.remesso.xyz/docs/guides/payment-events): the same payments by polling, if you can't run a public endpoint. - The complete agent, with the request saved and its deliveries remembered across restarts: [examples/freelancer-agent.ts](https://bitbucket.org/crackedstudios/remesso/src/main/examples/freelancer-agent.ts). --- # Subscriptions for your app > Bill each customer monthly in stablecoins, grant access when a payment arrives, and notice when a customer pauses or cancels. Source: https://docs.remesso.xyz/docs/use-cases/subscriptions You run a SaaS or an API product and want customers to pay monthly in stablecoins. Card-style billing, where you charge the customer, doesn't exist here: Remesso can't take money from anyone. Instead, each customer signs a mandate once: a fixed amount, to your address, every month, for a set number of payments. Remesso's executor then sends each payment when it's due. Your app asks for the mandate, then reacts to the payments. ## What you'll build - **Checkout:** a payment request per customer, and the link that opens their Remesso form filled in. - **A webhook** that grants access when a payment arrives, records it once it's final, and takes it back if a reorg undoes it. - **A daily check** that notices a cancelled, paused or underfunded subscription. Webhooks announce payments only, so this is how you learn about everything else. ## What Remesso does and doesn't do Be clear with yourself, and with your customers, about the model: - **The customer is in control.** They can pause or cancel the schedule at any time, from the Remesso app or their own code. You can't charge them, extend the schedule or change its amount. - **Terms are fixed at signing.** A new price means a new schedule: send a new request, and the customer cancels the old schedule (only they can). - **A schedule has an end.** It lasts at most a year, so 12 monthly payments. Renew with a new request before the last one. - **A month is 30 days.** Payment dates drift against calendar months by a day or so each month. - **There are no refunds in the protocol.** A refund is a transfer you send yourself. - **A late payment still goes out.** If a payment is due while the customer's wallet is short, or their approval is too low, it waits. It goes out once it can, as long as the schedule hasn't expired. ## Prerequisites - The setup from [Installation](https://docs.remesso.xyz/docs/installation), and a key for your app's receiving address. Receiving needs no gas and no balance. The key signs your webhook subscription and nothing else. - A public HTTPS URL for the webhook. For a test, `cloudflared tunnel --url http://localhost:8787` prints one. - Each customer needs USDT on Celo for each payment plus the 0.25% fee, and a little CELO to sign the schedule once. ```bash export APP_KEY=0x… PUBLIC_URL=https://….trycloudflare.com ``` ## Step 1: Ask at checkout Create one request per customer, and keep its id next to the customer. The amount is what you receive each month: the customer's form adds the fee on top. ```ts title="checkout.ts" import { createRemesso } from "remesso-sdk"; import { privateKeyToAccount } from "viem/accounts"; const app = createRemesso({ account: privateKeyToAccount(process.env.APP_KEY as `0x${string}`), }); /// At checkout: ask the customer's wallet to pay for the plan every month. /// Returns the link to show them. It opens their Remesso form, filled in. export async function startSubscription(customerId: string, customerWallet: string): Promise { const req = await app.requests.create({ payer: customerWallet, amount: "10", // USDT a month: what you receive token: "USDT", every: "month", runs: 12, from: "Example API", // up to 40 characters note: "Pro plan, billed monthly", // up to 120 characters }); await saveSubscription({ customerId, requestId: req.id }); return req.link; } async function saveSubscription(row: { customerId: string; requestId: string }): Promise { // Your database: remember which request belongs to which customer. } ``` The request also appears in the customer's Remesso inbox. A request stays open for 30 days, and a wallet can have at most 10 open requests at a time. When the customer signs, the request reads `accepted`. Don't grant access yet: accepted isn't paid. Access follows the first payment. ## Step 2: Grant access on each payment Each payment produces up to three deliveries to your webhook: | Delivery | What your app does | |---|---| | `payment.received` | Grant access until the next payment is due, plus a grace period. | | `payment.finalized` | Record the payment as final, for your accounts. It follows 25 to 30 minutes later. | | `payment.removed` | Take that period's access back: a reorg undid the payment. This is rare. | A delivery names the schedule, not the customer. The first time a schedule appears, find the request it answers, and remember the pairing. ```ts title="webhook.ts" import { createServer } from "node:http"; import { createRemesso, webhookHandler, type WebhookMessage } from "remesso-sdk"; import { toNodeListener } from "remesso-sdk/node"; import { privateKeyToAccount } from "viem/accounts"; const app = createRemesso({ account: privateKeyToAccount(process.env.APP_KEY as `0x${string}`), }); const PERIOD_MS = 30 * 86_400_000; // a Remesso month is 30 days const GRACE_MS = 3 * 86_400_000; async function onPayment(m: WebhookMessage) { if (!m.data.schedule || (await alreadyHandled(m.id))) return; // retries reuse the webhook-id const customerId = await customerFor(m.data.schedule); if (!customerId) return; // a payment that isn't a subscription if (m.type === "payment.received") { await grantAccess(customerId, new Date(Date.parse(m.data.blockTime) + PERIOD_MS + GRACE_MS)); } else if (m.type === "payment.finalized") { await recordFinal(customerId, m.data.txHash); } else { await revokePayment(customerId, m.data.txHash); } await markHandled(m.id); } /// Which customer a schedule pays for, found once through the request it answers. async function customerFor(scheduleId: string): Promise { for (const sub of await openSubscriptions()) { if (sub.scheduleId === scheduleId) return sub.customerId; if (sub.scheduleId) continue; const r = await app.requests.get(sub.requestId); if (r.payment?.schedule?.id === scheduleId) { await saveScheduleId(sub.customerId, scheduleId); return sub.customerId; } } return null; } // Your database and your app. These stand in for them. async function openSubscriptions(): Promise<{ customerId: string; requestId: string; scheduleId?: string }[]> { return []; } async function saveScheduleId(customerId: string, scheduleId: string): Promise {} async function grantAccess(customerId: string, until: Date): Promise {} async function recordFinal(customerId: string, txHash: string): Promise {} async function revokePayment(customerId: string, txHash: string): Promise {} async function alreadyHandled(webhookId: string): Promise { return false; } async function markHandled(webhookId: string): Promise {} let secret = process.env.WEBHOOK_SECRET; // saved from the first subscribe const handler = webhookHandler({ recipient: app.address, secret: () => secret, onPayment }); createServer(toNodeListener(handler)).listen(8787); if (!secret) { secret = (await app.webhooks.subscribe({ url: process.env.PUBLIC_URL! })).secret; console.log("Store this signing secret as WEBHOOK_SECRET:", secret); } ``` If `onPayment` throws, the handler answers 500 and Remesso retries with the same `webhook-id` for 24 hours. That makes an outage of your database safe: nothing is lost, it just arrives later. ## Step 3: Check each subscription daily Webhooks tell you about payments, not about a customer pausing or cancelling. Read each schedule from the contract once a day. Reading needs no key. ```ts title="check.ts" import { createRemesso } from "remesso-sdk"; const reader = createRemesso(); // reading schedules needs no key type State = "active" | "payment waiting" | "paused" | "cancelled" | "completed"; /// What a customer's schedule says now, read from the contract. async function stateOf(scheduleId: string): Promise { const [s, run] = await Promise.all([reader.schedules.get(scheduleId), reader.schedules.status(scheduleId)]); if (s.cancelled) return "cancelled"; // The contract turns `active` off on the last payment too, so count first. if (s.runsExecuted >= s.maxRuns || s.expiresAt < new Date()) return "completed"; if (!s.active) return "paused"; if (run.due && !(run.funded && run.approved)) return "payment waiting"; return "active"; } for (const sub of await subscriptionsWithSchedules()) { const state = await stateOf(sub.scheduleId); if (state !== "active") await tellCustomer(sub.customerId, state); } // Your database and your app. These stand in for them. async function subscriptionsWithSchedules(): Promise<{ customerId: string; scheduleId: string }[]> { return []; } async function tellCustomer(customerId: string, state: State): Promise { // "payment waiting": ask them to top up their wallet, or raise their approval. // "completed": send a new request to renew. } ``` `schedules.status` reads the contract's own view of the next payment: whether it's due, and whether the customer's balance (`funded`) and approval (`approved`) cover it. ## Test it safely 1. Use a second wallet you control as the customer, holding about 1.01 USDT and a little CELO. 2. In `checkout.ts`, ask for `amount: "1"` with `runs: 1`. One USDT is the smallest payment the executor sends. 3. Start `webhook.ts`, then sign from the customer wallet using the link. 4. Watch `payment.received` arrive within a few minutes, then `payment.finalized`. Run `check.ts` afterwards: the schedule reads `completed`. ## Where to go next - [Payment requests](https://docs.remesso.xyz/docs/guides/payment-requests): request states, and what a payer sees. - [Webhooks](https://docs.remesso.xyz/docs/guides/webhooks): the challenge, rotating the secret, and replaying failed deliveries. - [Schedules](https://docs.remesso.xyz/docs/guides/schedules): reading, pausing and cancelling schedules. - [Webhook payloads](https://docs.remesso.xyz/docs/reference/webhook-payloads): every field of a delivery. --- # Pay a team on a schedule > Sign one schedule per contractor so each receives an exact monthly amount, budget the total, check funding before payday, and stop a schedule when a contract ends. Source: https://docs.remesso.xyz/docs/use-cases/payroll A small company pays its contractors every month in USDT. The work is repetitive: send each payment, get the amount right after fees, and check the money is there before payday. With Remesso the employer signs one schedule per contractor, once. Each person then receives the exact amount you name every month, and you only watch that the wallet stays funded. The same steps work for an employer's agent, running on the employer's key. ## What you'll build - `payroll.ts` budgets the team's pay, checks the wallet can cover the first payday, and signs one schedule per contractor. - `before-payday.ts` checks every schedule's next payment is funded and approved. - A one-line cancel for when a contract ends. ## Prerequisites - The setup from [Installation](https://docs.remesso.xyz/docs/installation), and the employer's key. It holds the USDT and signs each schedule. - A little CELO for gas: about 0.05 CELO per contractor, for an approval and a schedule. With no CELO, the SDK pays gas in USDT. - Each contractor's wallet address. Receiving needs no gas and no balance. MiniPay shows USDT, so a contractor can use it. ```bash export EMPLOYER_KEY=0x… ``` ## Limits that shape a payroll - **Each payment is 1 to 1,000 USDT before the fee.** After the 0.25% fee, a contractor can receive at most 997.50 USDT per payment. The SDK refuses anything outside that before signing. Other tokens have their own bounds: see [Tokens and limits](https://docs.remesso.xyz/docs/reference/tokens-and-limits). - **A schedule lasts at most a year from signing.** With the first payday up to a month away, six monthly payments fit comfortably. Sign again for the next six. The SDK refuses a schedule whose payments don't fit. - **A month is 30 days.** Paydays drift against calendar dates, by a day or so each month. - **Each schedule is its own approval.** For every contractor the SDK sends two transactions: it adds that schedule's total to your USDT approval for the contract, then creates the schedule. The approval is the most that schedule can ever take. ## Step 1: Budget and sign Use `receive` so each contractor gets exactly the amount you name. The SDK adds the fee on top. `grossFor` does the same sum, so you can budget before signing anything. ```ts title="payroll.ts" import { writeFile } from "node:fs/promises"; import { createRemesso, erc20Abi, FEE_BPS, formatAmount, grossFor, token, toUnits } from "remesso-sdk"; import { privateKeyToAccount } from "viem/accounts"; const employer = createRemesso({ account: privateKeyToAccount(process.env.EMPLOYER_KEY as `0x${string}`), }); const usdt = token("USDT"); /// Who gets paid, and what arrives each month after the fee. const team = [ { name: "Designer", wallet: "0xDesignerWallet", receive: "400" }, { name: "Developer", wallet: "0xDeveloperWallet", receive: "650" }, ]; const runs = 6; // six months: sign again for the next six // What leaves your wallet each payday, and in all. const perPayday = team.reduce((sum, p) => sum + grossFor(toUnits(p.receive, usdt), FEE_BPS), 0n); const balance = await employer.publicClient.readContract({ address: usdt.address, abi: erc20Abi, functionName: "balanceOf", args: [employer.address], }); console.log(`each payday: ${formatAmount(perPayday, usdt)}; over ${runs} months: ${formatAmount(perPayday * BigInt(runs), usdt)}`); if (balance < perPayday) throw new Error(`the wallet holds ${formatAmount(balance, usdt)}: not enough for the first payday`); // The first payday: 09:00 UTC on the 1st of next month. const now = new Date(); const payday = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth() + 1, 1, 9)); const ids: Record = {}; for (const p of team) { const s = await employer.schedules.create({ to: p.wallet, receive: p.receive, token: "USDT", every: "month", runs, firstRunAt: payday, }); ids[p.name] = s.id.toString(); console.log(`${p.name}: schedule #${s.id}, ${formatAmount(s.recipientGets, usdt)} a month from ${s.firstRunAt.toDateString()}`); for (const w of s.warnings) console.log(` note: ${w}`); } await writeFile("payroll.json", JSON.stringify(ids, null, 2)); ``` The wallet doesn't need the whole six months up front. Each payment is taken when it's due, so the balance only has to cover each payday as it comes. ## Step 2: Check before each payday One balance pays every schedule. `schedules.status` tells you whether the next payment of each schedule is covered on its own. The total check makes sure the balance covers everyone paid the same day. ```ts title="before-payday.ts" import { readFile } from "node:fs/promises"; import { createRemesso, erc20Abi, formatAmount, token } from "remesso-sdk"; const reader = createRemesso(); // reading needs no key const usdt = token("USDT"); const employer = process.env.EMPLOYER as `0x${string}`; // the employer's address const ids = JSON.parse(await readFile("payroll.json", "utf8")) as Record; const soon = Date.now() + 2 * 86_400_000; let dueSoon = 0n; for (const [name, id] of Object.entries(ids)) { const [s, run] = await Promise.all([reader.schedules.get(id), reader.schedules.status(id)]); if (s.cancelled || s.runsExecuted >= s.maxRuns) continue; // stopped, or done if (run.nextRunAt.getTime() < soon) dueSoon += s.amountIn; const problems = [ !s.active && "paused", !run.funded && "the balance is below one payment", !run.approved && "the approval is below one payment", ].filter(Boolean); console.log(`${name} #${id}: next payment ${run.nextRunAt.toDateString()}${problems.length ? `, PROBLEM: ${problems.join(", ")}` : ""}`); } const balance = await reader.publicClient.readContract({ address: usdt.address, abi: erc20Abi, functionName: "balanceOf", args: [employer], }); if (balance < dueSoon) console.log(`top up: ${formatAmount(dueSoon, usdt)} is due in the next two days, and the wallet holds ${formatAmount(balance, usdt)}`); ``` A payment that's due when the wallet is short doesn't fail for good. It waits, and goes out once the wallet is topped up, as long as the schedule hasn't expired. ## Step 3: When a contract changes or ends Terms are fixed at signing. To pay someone a different amount, cancel their schedule and sign a new one. To stop paying, cancel: ```ts await payer.schedules.cancel(scheduleId); // stops every future payment ``` Only the wallet that signed a schedule can cancel it. Cancelling leaves your token approval as it is: if nothing else needs it, lower it with an ordinary ERC-20 `approve` from your wallet. For a break, such as unpaid leave, pause instead and resume later: ```ts await payer.schedules.setActive(scheduleId, false); // pause await payer.schedules.setActive(scheduleId, true); // resume ``` ## Test it safely 1. Put one contractor in `team`: a second wallet you control, with `receive: "1"`. 2. Set `runs = 1` and leave out `firstRunAt`, so the payment is due at once. 3. Give the employer wallet about 1.01 USDT and a little CELO, then run `payroll.ts`. 4. A schedule created from code is found by Remesso within about five minutes, and paid within a minute of that. Run `before-payday.ts` before and after. ## Where to go next - [Schedules](https://docs.remesso.xyz/docs/guides/schedules): every option of `create`, and reading, pausing and cancelling. - [Fees and gas](https://docs.remesso.xyz/docs/guides/fees-and-gas): `grossFor`, `netOf` and who pays gas when. - [Payment events](https://docs.remesso.xyz/docs/guides/payment-events): a record of every payment for your accounts. --- # Send money home every month > Pay a family member in USDT every month from one signature, and tell them each time money arrives, with a link to cash it out. Source: https://docs.remesso.xyz/docs/use-cases/remittances Someone working in London supports family in Lagos, and sends money every month. Each transfer means another form, another fee and another wait. With Remesso the sender signs once. After that, the family receives USDT every 30 days, in a wallet they already use, such as MiniPay, and a short program tells them each time money arrives. ## What you'll build - `send-home.ts` signs a monthly USDT schedule to a family member's wallet, so they receive an exact amount every month. - `notify.ts` watches for each payment and sends the family a message with the amount and a link to cash it out. ## Choose the token The family's wallet and their way to cash out decide the token: - **In MiniPay, pay USDT, USDC, USDm or USA₮.** MiniPay shows only those four. It doesn't show cNGN or Ripio's local-currency tokens: a recipient paid in those needs another wallet. - **For naira, pay USDT.** On Celo, USDT is the token Textile pairs with cNGN. The recipient opens Textile with their own wallet, swaps USDT to cNGN, and uses "Withdraw NGN" to a Nigerian bank account in their own name: `https://app.textilecredit.com/s/swap?sell=USDT&buy=cNGN&chainId=42220`. - **For pesos, reais or soles, pay the country's Ripio token:** wARS, wBRL, wCOP, wMXN, wCLP or wPEN. The recipient cashes out on Ripio's ramp, for example `https://ramp.ripio.com/off-ramp?country=AR&chain=42220&token=WARS` for Argentina, or `country=BR` with `token=WBRL` for Brazil. Which countries Ripio ramps on Celo is Ripio's to decide. Remesso never pays into a bank account on the sender's behalf. The family receives tokens in their own wallet, and cashes them out themselves, if and when they choose. ## Prerequisites - The setup from [Installation](https://docs.remesso.xyz/docs/installation), and the sender's key. It holds the USDT: each payment plus Remesso's 0.25% fee. - A little CELO to sign the schedule once, about 0.05 CELO. With no CELO, the SDK pays the gas in USDT. Remesso's executor pays the gas of every monthly payment after that. - The family member's wallet address. Receiving needs no gas, no balance and no software. ```bash export SENDER_KEY=0x… FAMILY_WALLET=0x… ``` ## Step 1: Sign the schedule `receive` is what arrives each month. The SDK adds the fee to what leaves your wallet. ```ts title="send-home.ts" import { createRemesso, formatAmount } from "remesso-sdk"; import { privateKeyToAccount } from "viem/accounts"; const payer = createRemesso({ account: privateKeyToAccount(process.env.SENDER_KEY as `0x${string}`), }); const s = await payer.schedules.create({ to: process.env.FAMILY_WALLET!, receive: "50", // USDT that arrives each month token: "USDT", every: "month", runs: 12, // a year: the longest a schedule lasts }); console.log(`schedule #${s.id}: ${formatAmount(s.amountIn, "USDT")} leaves your wallet each month`); console.log(`${formatAmount(s.recipientGets, "USDT")} arrives, ${s.runs} times, until ${s.expiresAt.toDateString()}`); for (const w of s.warnings) console.log("note:", w); ``` This sends two transactions. The first adds the year's total to your USDT approval for the contract: that approval is the most the schedule can ever take. The second creates the schedule. Remesso finds a schedule created from code within about five minutes, and sends the first payment then. After that, payments go out every 30 days. To start on a particular day, pass `firstRunAt`. A schedule lasts at most a year from signing, so a later start fits fewer payments: the SDK refuses a schedule whose payments don't fit, before signing anything. The wallet doesn't need the whole year up front. Each payment is taken when it's due, so keep one month's payment and the fee in it. If it's short when a payment is due, that payment waits. It goes out once the wallet is topped up, as long as the schedule hasn't expired. ## Step 2: Tell the family each time Payment events are public, so the watcher needs no key, and the family needs no software. This one tells them once each payment is final, about half an hour after it lands, so a message never has to be taken back. ```ts title="notify.ts" import { readFile, writeFile } from "node:fs/promises"; import { createRemesso, formatAmount } from "remesso-sdk"; const reader = createRemesso(); // payment events are public: no key needed const family = process.env.FAMILY_WALLET!; const scheduleId = process.env.SCHEDULE_ID!; // printed by send-home.ts const CASH_OUT = "https://app.textilecredit.com/s/swap?sell=USDT&buy=cNGN&chainId=42220"; // Where the last run stopped, so a restart doesn't announce a payment again. const after = Number(await readFile("notify-seq.txt", "utf8").catch(() => "0")); for await (const e of reader.events.watch({ address: family, role: "to", kinds: ["run"], after })) { if (e.refId === scheduleId && e.final && !e.removed) { await tell(`${formatAmount(e.amount!, e.token!)} has arrived. To cash it out to your bank account: ${CASH_OUT}`); } await writeFile("notify-seq.txt", String(e.seq)); // after telling: a crash repeats a message, never skips one } async function tell(message: string) { // Your messaging: SMS, WhatsApp, email. Printed here. console.log(new Date().toISOString(), message); } ``` Each event comes round again when it changes, so a payment appears once when it lands and again when it becomes final. Announcing only the final one sends exactly one message per payment. To tell the family sooner, announce events that aren't final yet too, and send a correction if one is later marked `removed`. ## Step 3: Change or stop The schedule's recipient, amount and cadence are fixed once it's signed. Nobody can change them, Remesso included. - **To send a different amount**, cancel and sign a new schedule. - **If the family moves to a new wallet**, the schedule keeps paying the old one. Cancel it and sign a new one to the new address. - **To stop**, cancel with the same client that signed. It stops every future payment: ```ts await payer.schedules.cancel(scheduleId); ``` To skip a while without cancelling, pause with `schedules.setActive(scheduleId, false)`, and resume later with `true`. ## Test it safely 1. Use a second wallet you control as the family's wallet. A MiniPay wallet works, and shows the USDT arriving. 2. Sign with `receive: "1"` and `runs: 1`: about 1.0025 USDT leaves the sender's wallet. 3. Start `notify.ts` with the schedule id. The payment goes out within about five minutes, and the message follows once it's final, about half an hour later. ## Where to go next - [Schedules](https://docs.remesso.xyz/docs/guides/schedules): every option of `create`, and pausing and cancelling. - [Payment events](https://docs.remesso.xyz/docs/guides/payment-events): paging, finality and reorgs. - [Tokens and limits](https://docs.remesso.xyz/docs/reference/tokens-and-limits): which tokens a schedule can pay, and the per-payment bounds. --- # A weekly allowance, approved by an agent > A child's agent asks a parent for cNGN every week; the parent's agent shows the request and signs a weekly schedule after a yes. Source: https://docs.remesso.xyz/docs/use-cases/family-allowance A student in Lagos gets a weekly allowance from a parent, in naira. With Remesso, two agents handle it. The child's agent sends one payment request. The parent's agent shows it, asks the parent, and signs a weekly schedule on a yes. From then on, the allowance arrives every week, and the child's agent says so, with a link to cash it out to the child's own bank account. ## What you'll build - `child.ts` asks the parent's wallet for a weekly amount of cNGN once, then listens for each payment by webhook. - `parent.ts` lists the requests waiting for the parent's wallet, explains what signing would cost, and signs or declines. ```mermaid sequenceDiagram participant C as Child's agent participant R as Remesso participant P as Parent's agent participant E as Executor C->>R: requests.create (cNGN, every week) P->>R: requests.list P->>P: shows it, and the parent says yes P->>R: schedules.create, answering the request E->>C: each week, the allowance R->>C: payment.received, then payment.finalized ``` ## Prerequisites - The setup from [Installation](https://docs.remesso.xyz/docs/installation), on both sides. - **The child:** a key, and a wallet that shows cNGN. MiniPay doesn't. Receiving needs no gas and no balance. The child also needs a public HTTPS address that reaches port 8788: `cloudflared tunnel --url http://localhost:8788` prints one for a test. - **The parent:** a key holding cNGN for each payment plus Remesso's 0.25% fee, and about 0.05 CELO to sign once. Textile swaps USDT into cNGN on Celo: `https://app.textilecredit.com/s/swap?sell=USDT&buy=cNGN&chainId=42220`. cNGN is the Nigerian naira stablecoin, with 6 decimals. Remesso's executor sends 1,300 to 1,300,000 cNGN per payment. ## Step 1: The child asks, and listens The child's agent asks once and saves the request, so a restart doesn't ask again. Then it serves the webhook. A payment counts only if it comes from the schedule that answers this request. ```ts title="child.ts" import { readFile, writeFile } from "node:fs/promises"; import { createServer } from "node:http"; import { createRemesso, formatAmount, RemessoError, webhookHandler, type WebhookMessage } from "remesso-sdk"; import { toNodeListener } from "remesso-sdk/node"; import { privateKeyToAccount } from "viem/accounts"; const TEXTILE_WITHDRAW = "https://app.textilecredit.com/s/sell?chainId=42220"; const child = createRemesso({ account: privateKeyToAccount(process.env.CHILD_KEY as `0x${string}`), }); const url = process.env.PUBLIC_URL!; // Ask once. A restart reuses the saved request. let saved = await readFile("allowance.json", "utf8").then((t) => JSON.parse(t) as { id: string }).catch(() => null); if (!saved) { const req = await child.requests.create({ payer: process.env.PARENT!, amount: process.env.AMOUNT ?? "1300", // cNGN a week: what you receive token: "cNGN", every: "week", runs: 12, from: "Your child", note: "Weekly allowance", }); saved = { id: req.id }; await writeFile("allowance.json", JSON.stringify(saved)); console.log(`asked. If your parent prefers the Remesso app, this link opens the request: ${req.link}`); } const requestId = saved.id; let secret: string | undefined; const seen = new Set(); async function onPayment(m: WebhookMessage) { if (seen.has(m.id) || !m.data.schedule) return; const r = await child.requests.get(requestId); if (!r.payment?.schedule) throw new Error("the allowance isn't answered yet"); // Remesso retries if (r.payment.schedule.id !== m.data.schedule) return; // some other payment to this wallet seen.add(m.id); const amount = formatAmount(m.data.amount, m.data.token); if (m.type === "payment.received") { console.log(`Allowance received: ${amount}, week ${m.data.runNumber}.`); console.log(`Cash it out to your own bank account on Textile ("Withdraw NGN"): ${TEXTILE_WITHDRAW}`); } else if (m.type === "payment.finalized") { console.log(`Week ${m.data.runNumber}: final.`); } else { console.log(`Week ${m.data.runNumber}'s payment was undone by a reorg. Nothing arrived: don't withdraw it.`); } } const server = createServer(toNodeListener(webhookHandler({ recipient: child.address, secret: () => secret, onPayment }))); await new Promise((done) => server.listen(8788, done)); try { secret = (await child.webhooks.subscribe({ url })).secret; } catch (e) { if (!(e instanceof RemessoError && e.code === "conflict")) throw e; secret = (await child.webhooks.rotate({ url })).secret; // subscribed before, and the secret is lost } console.log(`listening for the allowance at ${url}`); ``` Cashing out is Textile's "Withdraw NGN": the child withdraws from their own wallet to a bank account in their own name. Remesso pays out no naira itself. ## Step 2: The parent's agent asks, then signs `requests.list()` returns the live requests sent to the parent's wallet: those still pending and under 30 days old. Before signing, the agent shows what the schedule will take. The child receives the amount asked, and the parent sends that plus the fee. ```ts title="parent.ts" import { createInterface } from "node:readline/promises"; import { createRemesso, FEE_BPS, formatAmount, grossFor, token, toUnits } from "remesso-sdk"; import { privateKeyToAccount } from "viem/accounts"; const parent = createRemesso({ account: privateKeyToAccount(process.env.PARENT_KEY as `0x${string}`), }); const cngn = token("cNGN"); const [r] = (await parent.requests.list()).filter((x) => x.token === "cNGN" && x.amount && x.runs); if (!r) { console.log("no cNGN requests are waiting"); process.exit(0); } const each = grossFor(toUnits(r.amount!, cngn), FEE_BPS); console.log(`${r.from ?? "Someone"} asks for ${r.amount} cNGN a week, ${r.runs} times: "${r.note ?? ""}"`); console.log(`You'd send ${formatAmount(each, cngn)} a week, at most ${formatAmount(each * BigInt(r.runs!), cngn)} in all.`); const ask = createInterface({ input: process.stdin, output: process.stdout }); const answer = (await ask.question("Sign it? [y/N] ")).trim().toLowerCase(); ask.close(); if (answer === "y") { const s = await parent.schedules.create({ request: r.id }); // the request's terms, the fee on top console.log(`schedule #${s.id} is live: https://celoscan.io/tx/${s.hash}`); } else { await parent.requests.decline(r.id); // signed by this wallet: the child sees it declined console.log("declined"); } ``` Signing answers the request with its own terms. The SDK refuses a request addressed to a different wallet, or one that's no longer pending. A decline is signed by the parent's wallet, so it holds for everyone: the child's `requests.get` then reads `declined`. The schedule can't change once it's signed. The parent can pause it, resume it, or cancel it from the same client: `schedules.setActive(id, false)` and `schedules.cancel(id)`. ## Test it safely 1. Run `child.ts` with `AMOUNT=1300`, the smallest payment the executor sends. For a quick test, change `runs` to 1. 2. Give the parent wallet about 1,304 cNGN and a little CELO, then run `parent.ts` and answer `y`. 3. Remesso finds the schedule within about five minutes and sends the first payment. The child's terminal prints it, with the cash-out link, and marks it final about half an hour later. ## Where to go next - [Payment requests](https://docs.remesso.xyz/docs/guides/payment-requests): listing, answering and declining requests. - [Webhooks](https://docs.remesso.xyz/docs/guides/webhooks): the handler, the challenge and retries. - [Give an AI agent a budget](https://docs.remesso.xyz/docs/use-cases/ai-agent-budget): an agent that pays from a policy instead of signing schedules. - The complete pair of agents: [examples/family-allowance-agent.ts](https://bitbucket.org/crackedstudios/remesso/src/main/examples/family-allowance-agent.ts). --- # Give an AI agent a budget > Cap what an agent's key can spend from a person's wallet, pay with idempotency keys so a retry never pays twice, and let the person review and revoke. Source: https://docs.remesso.xyz/docs/use-cases/ai-agent-budget An AI agent that buys things for a person needs money it can spend, within limits the person trusts. Handing it a funded key trusts it with everything. With a policy, the person caps what the agent's key can pay: so much per payment, so much per day, for so many days, and optionally only to a fixed list of recipients. The agent pays from the person's wallet, and the contract refuses anything outside the policy. ## What you'll build - `grant.ts`: the person creates the policy, once. - `agent.ts`: the agent pays, with an idempotency key per payment and a store on disk, so a retry after a timeout or a crash never pays twice. - Paying or declining a payment request sent to the person. - `review.ts`: the person sees the policy, the agent's payments, and can revoke it. ## Prerequisites - The setup from [Installation](https://docs.remesso.xyz/docs/installation). - **The person:** a key holding the USDT the agent may spend, and a little CELO to sign the policy. With no CELO, the SDK pays that gas in USDT. The person can also set the policy up in the Remesso app at [remesso.xyz/agent-wallet](https://remesso.xyz/agent-wallet). - **The agent:** its own key, generated for this and holding nothing of value. Agent payments are signed before they're sent, so they need a local account (`privateKeyToAccount`). The agent's key pays its own gas, so keep a little CELO in it. The SDK wants twice a payment's gas in hand before it signs, about 0.05 CELO at 200 gwei. Alternatively, a little USDT of its own then pays the gas. Policy payments carry no fee: what the agent pays is what arrives. There's no per-payment minimum either: the executor's 1-dollar floor applies to schedules only. ## Step 1: The person grants a policy ```ts title="grant.ts" import { createRemesso } from "remesso-sdk"; import { privateKeyToAccount } from "viem/accounts"; const person = createRemesso({ account: privateKeyToAccount(process.env.PERSON_KEY as `0x${string}`), }); const policy = await person.policies.create({ agent: process.env.AGENT_ADDRESS!, // the agent's key, which will call pay() token: "USDT", perPayment: "5", // at most 5 USDT a payment perDay: "20", // and 20 USDT in any rolling day days: 30, // it ends by itself after 30 days budget: "100", // added to your USDT approval for the allowance contract recipients: [process.env.SHOP!], // optional: the only addresses the agent may pay }); console.log(`policy #${policy.id}: https://celoscan.io/tx/${policy.hash}`); ``` This sends two transactions: an approval, then the policy. `budget` is added to the person's USDT approval for the allowance contract. That approval is the most this policy and the person's other USDT policies can ever spend together, whatever the caps say. Leave out `recipients` to let the agent pay anyone. Whether a policy has a list is fixed when it's created. On a policy created with one, the person can add or remove recipients later with `policies.setRecipient(id, address, allowed)`. ## Step 2: The agent pays Give every payment you intend its own idempotency key, such as an order or invoice id. The SDK signs the payment and records it in the store before broadcasting it. A retry with the same key then reports what became of that payment instead of making another. ```ts title="agent.ts" import { createRemesso, RemessoError } from "remesso-sdk"; import { fileStore } from "remesso-sdk/node"; import { privateKeyToAccount } from "viem/accounts"; const agent = createRemesso({ account: privateKeyToAccount(process.env.AGENT_KEY as `0x${string}`), store: fileStore(), // ~/.remesso-sdk/payments.json: a retry pays once, even after a restart }); /// Pay for one order. Safe to call again with the same order id, after a timeout or a crash. export async function payForOrder(orderId: string, shop: string, amount: string) { try { const r = await agent.pay({ to: shop, amount, // decimal, in the policy's token: "2.50" token: "USDT", idempotencyKey: `order:${orderId}`, // one key per payment you intend memo: `order ${orderId}`, // up to 32 bytes, recorded on-chain }); if (r.status === "pending") return { state: "pending", hash: r.hash } as const; // call again later, same order id return { state: r.repeat ? "already paid" : "paid", hash: r.hash, sent: r.sent } as const; } catch (e) { if (e instanceof RemessoError && e.code === "refused") return { state: "refused", reason: e.message } as const; throw e; } } ``` The agent can check its limits before it promises anything: ```ts import { formatAmount } from "remesso-sdk"; for (const id of await agent.policies.find()) { const p = await agent.policies.get(id); if (!p.active) continue; console.log(`policy #${p.id}: up to ${formatAmount(p.spendable, p.token)} now, ${formatAmount(p.remainingToday, p.token)} left today`); } ``` `spendable` counts every limit at once: the per-payment cap, what's left of today, and the person's balance and approval. ### What `pay` answers | What happened | What `pay` does | What the agent does | |---|---|---| | It landed | Returns `status: "confirmed"` | Done. | | No receipt within 45 seconds (`waitMs`) | Returns `status: "pending"` | Call again later with the same key. | | The same key again, after it paid | Returns `confirmed` with `repeat: true`, and sends nothing | Treat it as paid. | | The same key for a different payment | Throws [`key_reused`](https://docs.remesso.xyz/docs/reference/errors#key_reused) | Fix the bug: one key per payment. | | An identical payment in the last ten minutes, without a key | Throws [`repeat`](https://docs.remesso.xyz/docs/reference/errors#repeat) | Pass a key, or `allowRepeat` if you mean it. | | Over a cap, revoked, expired, or a recipient not on the list | Throws `refused`, before anything is signed | Tell the person. | | Mined, and reverted | Throws `reverted`: nothing moved | Use a new key to try again. | A pending payment resolves on the next call with its key. If the transaction is in a block, the call reports the outcome. If it never left the machine, its own signed bytes go out now, with the same nonce, so it still pays at most once. If another transaction took its nonce and it never landed, it's paid afresh under the same key. `fileStore` keeps a record for 30 days, up to 1,000 of them. Use one process per file: the file isn't locked. The default store is memory, which only protects retries inside one process. ## Step 3: Requests sent to the person Other people and agents can ask the person to pay with a payment request. The agent sees the person's inbox and can answer a one-off request, such as an invoice, from the policy: ```ts for (const r of await agent.requests.list(process.env.PERSON_WALLET)) { console.log(`${r.from ?? "someone"} asks for ${r.amount} ${r.token}: "${r.note ?? ""}" (${r.id})`); } // Pay it: its own amount, its reference as the memo, at most once per request. await agent.requests.pay(requestId); // Or turn it down, signed by the agent under the person's policy. await agent.requests.decline(requestId, { policyId }); ``` `requests.pay` pays only a request addressed to the policy's owner, from an active policy of the same token. Its idempotency key defaults to the request id, so paying the same request twice pays once. The memo carries the request's reference, so the requester sees it answered. A request for recurring payments asks for a schedule, which the person signs themselves. ## Step 4: The person reviews and revokes ```ts title="review.ts" import { createRemesso, formatAmount } from "remesso-sdk"; import { privateKeyToAccount } from "viem/accounts"; const person = createRemesso({ account: privateKeyToAccount(process.env.PERSON_KEY as `0x${string}`), }); const policyId = BigInt(process.env.POLICY_ID!); const p = await person.policies.get(policyId); console.log(`policy #${p.id} for ${p.agent}: ${p.active ? "active" : p.revoked ? "revoked" : "expired"}`); console.log(`${formatAmount(p.spendable, p.token)} spendable now, ${formatAmount(p.remainingToday, p.token)} left today`); // Every payment made from this wallet under a policy, as Remesso's indexer reads it from the chain. const { events } = await person.events.list({ role: "from", kinds: ["policy_payment"] }); for (const e of events.filter((x) => x.refId === p.id.toString())) { console.log(`${e.blockTime} ${formatAmount(e.amount!, e.token!)} to ${e.to} ${e.memoText ?? ""}`); } if (process.argv.includes("--revoke")) { console.log("revoked:", await person.policies.revoke(policyId)); // the agent's next payment is refused } ``` Revoking takes effect at once, and no one can undo it, Remesso included. To give the agent a budget again, create a new policy. ## Test it safely 1. Grant a small policy: `perPayment: "0.05"`, `perDay: "0.10"`, `days: 1`, `budget: "0.10"`. 2. Send the agent's key a little CELO, then call `payForOrder("test-1", yourOtherWallet, "0.02")`. 3. Call it again with `"test-1"`: it answers `already paid`, and nothing new is sent. 4. Run `review.ts` to see the payment, then `review.ts --revoke`. A payment after that is refused. ## Where to go next - [Agent payments](https://docs.remesso.xyz/docs/guides/agent-payments): policies, stores and every option of `pay`. - [Payment requests](https://docs.remesso.xyz/docs/guides/payment-requests): requests, references and declines. - [Errors](https://docs.remesso.xyz/docs/reference/errors): every `RemessoError` code, and what to do about it. - [Security](https://docs.remesso.xyz/docs/resources/security): what a policy protects, and what it doesn't. --- # Client API > createRemesso and every method on the client it returns, with parameters, return values and the errors each can raise. Source: https://docs.remesso.xyz/docs/reference/client Everything in the SDK hangs off one client. This page lists its options and every method, with what each takes, returns and refuses. Ids (schedule, policy, agent) are accepted as a `bigint`, a number or a decimal string. ## `createRemesso(options)` ```ts import { createRemesso } from "remesso-sdk"; import { privateKeyToAccount } from "viem/accounts"; const remesso = createRemesso({ account: privateKeyToAccount(process.env.REMESSO_KEY as `0x${string}`), }); ``` | Option | Type | Default | Description | |---|---|---|---| | `account` | viem `Account` | none | The key that pays, signs and receives. Agent payments need a sign-only local account, such as `privateKeyToAccount(...)` or `managedSigner(...)`. Leave it out to read only. | | `walletClient` | viem `WalletClient` | none | A wallet client instead of an account, for example the user's browser wallet on Celo. | | `rpcUrl` | `string` | `https://forno.celo.org` | A Celo mainnet RPC. Checked to answer chain `42220` before anything is signed. | | `publicClient` | viem `PublicClient` | created from `rpcUrl` | Your own public client. Agent payments require `chain: celo` so Celo transactions use the right serializer. | | `store` | `PaymentStore` | `memoryStore()` | Where agent payments are recorded so a retry pays once. Use `fileStore()` from `remesso-sdk/node` to keep records across restarts. | | `remittanceStore` | `RemittanceStore` | `memoryRemittanceStore()` | Where wallet payments are recorded, each transaction before it is sent, so a retry continues the same payment. Use `fileRemittanceStore()` from `remesso-sdk/node` to keep records across restarts. | | `invoiceStore` | `InvoiceStore` | `memoryInvoiceStore()` | Which payment route, signer and key each signed invoice is reserved for, so it is paid only one way. Share it between workers. | | `coordinator` | `SignerCoordinator` | one queue per process | A lock and nonce record shared by every worker that signs with this key. Requires `store`, `remittanceStore` and `invoiceStore` shared the same way; `createPostgresStorage` gives all four. | | `fetchText` | `(url) => Promise` | none | Reads an https or ipfs agent registration file for `agents.find`. Pass `safeFetchText` from `remesso-sdk/node`. | | `attribution` | `boolean` | `true` | Tag transactions with Remesso's ERC-8021 codes. | | `builderCodes` | `string[]` | none | Your own ERC-8021 codes, up to three, on every transaction this client sends and on every payment of the schedules it creates. Throws on a code outside Celo's charset (1 to 32 of `a-z`, `0-9` and `_`), one of Remesso's own, a repeat, or more than three. | With an `account`, or a `walletClient` that has one, the returned client's `address` is typed as an `Address`. Without either, the client reads only, and `address` is `null`. ### Client properties | Property | Type | Description | |---|---|---| | `address` | `Address \| null` | This client's address, or `null` when it only reads. | | `publicClient` | viem `PublicClient` | The client used for reads, for anything the SDK doesn't wrap. | | `deployment` | object | Celo mainnet as Remesso runs it: chain, contracts, tokens, endpoints. See [contracts and endpoints](https://docs.remesso.xyz/docs/reference/contracts). | ## Agent-to-agent payments One-off payments funded from this client's wallet or from a spending policy. These need `remesso-sdk` 0.3.0 or later; the guides are [agent-to-agent](https://docs.remesso.xyz/docs/guides/agent-to-agent) and [production](https://docs.remesso.xyz/docs/guides/production-sdk). ### `remittances.send(input)` | Field | Type | Description | |---|---|---| | `funding` | `"wallet" \| "policy"` | Required. `wallet` spends this account's own tokens; `policy` spends a policy owner's, within the policy. | | `to`, `amount`, `token` | `string` | The recipient, the exact decimal amount it receives, and the token by symbol or address. | | `idempotencyKey` | `string` | Required. One per payment you intend. A retry with the same key and terms continues that payment and never pays twice. | | `requestId` | `string` | Optional one-off request this pays. Its payer, recipient, amount and token must match; a recurring request is refused. | | `policyId`, `payer` | id, `string` | Policy funding only: the policy to use, or the owner whose policy to use. | | `memo` | `string` | Policy funding only: up to 32 bytes recorded with the payment. A request's reference fills it when `requestId` is set. | | `waitMs` | `number` | How long to wait for receipts before answering `pending`: 1 to 120,000 ms, default 45 seconds. | | `maxDebit` | `string` | The most the payer's balance may lose, fee included. Network gas is separate. | | `startBy` | `number` | Unix seconds. Don't start the payment at or after this time; `prepare` sets it to the quote's expiry. A started payment continues regardless. | | `validUntil` | `number` | Unix seconds. The payment must land before this. A wallet payment's schedule expires a second earlier; a policy payment still unsent near it is cancelled at its nonce. A retry can't change it. | Returns `Promise`. Both kinds have `funding`, `status` (`confirmed` or `pending`), `payer` (whose money moved), `to`, `token`, `amount` in base units and `fee`. A policy payment also has the [`pay`](https://docs.remesso.xyz/docs/reference/client#payinput) result's fields, including `cancellation` when its deadline passed before it was sent. A wallet payment also has `stage`, `amountIn`, `feeBps`, `scheduleId`, the hash of each transaction it sent (`createHash`, `approvalHash`, `executionHash`, `cancellationHash`), `repeat`, and `request` for the request's binding. Its `hash` is the transaction that paid, once there is one. `pending` may still be paid: retry the same key, never a new one. A wallet payment is one schedule with a single payment, created, approved and run by the SDK; Remesso's executor may run it first, and either way it pays once. With `requestId`, it is approved and run only once the request names its schedule, and cancelled if the request can no longer be answered. If it can't be completed, its schedule is cancelled, and only after that lands is it reported as not paid: a `refused` error with `details.cancelled`. One that expired unpaid has `details.expired`. ### `remittances.quote(input, options?)` Signs nothing. Takes `funding`, `to`, `amount`, `token`, and optionally `requestId`, `idempotencyKey`, and for a policy `payer` or `policyId`. `options.ttlSeconds` is 1 to 300, default 60. Returns `RemittanceQuote`: the recipient's `amount`, `grossDebit`, `protocolFee`, `feeBps`, `selectedPolicyId`, the approval needed (none when the allowance is already unlimited), the balance, `canSend` and `issues`, a simulation of each step, gas estimates marked `rpc_estimate` or `conservative_hint`, and the block, `checkedAt` and `expiresAt` it describes. Amounts are base units, as `bigint`. A quote reserves nothing. ### `remittances.prepare(input, options?)` Quotes, refuses a payment that can't be sent or that costs more than `maxDebit`, and returns `{ quote, input }`. The input fixes the token, the policy and `maxDebit`, and sets `startBy` to the quote's expiry. Send checks everything again. ### Following a payment | Method | What it does | |---|---| | `remittances.get(key, { funding? }?)` | Reads a saved payment and what the chain says about it. Signs and sends nothing. | | `remittances.listPending({ funding? }?)` | The saved payments not yet final, failed, cancelled or expired, paused ones included. Ones that settled more than two hours ago are left out. | | `remittances.reconcile(key, { funding?, waitMs? }?)` | Continues a payment that has started: sends its saved transactions, or a wallet payment's remaining steps. Never starts a payment. | | `remittances.wait(key, options?)` | Waits for `included` (the default) or `finalized`. Sends nothing. `timeoutMs` 1 to 86,400,000, default 20 minutes; `intervalMs` 1 to 60,000, default 1,500; `signal`; `funding`. | | `remittances.recoverPending(options?)` | One pass of `reconcile` over started payments, oldest first. `limit` 1 to 1,000, default 25, bounds the work; `funding`, `waitMs` and `signal` as above. | Each returns a `PaymentOperation`: `key`, `funding`, `status`, `stage`, `payer`, `to`, `token`, `amount`, `createdAt`, the hash and ids it has, a verified `proof` once included, and an `error` when something needs attention. The statuses are `prepared`, `awaiting_approval`, `signed`, `broadcast`, `included`, `finalized`, `failed`, `cancelled`, `paused`, `expired` and `needs_reconciliation`. A wallet payment whose step reverted while its schedule is live is `needs_reconciliation`: it could still be paid, and reconciling cancels it. So is a transaction whose nonce was used with no receipt to show by what: look at its hash; the SDK never pays it again. `wait()` throws on a timeout, an abort, or a payment that failed, was cancelled, paused or expired. If a key is in both journals, pass `funding`. ### `invoices` | Method | What it does | |---|---| | `invoices.sign(input)` | Signs an invoice's exact terms as its recipient, optionally for an existing one-off request. | | `invoices.create(input)` | Creates a one-off request with the same terms, then signs them. | | `invoices.verify(invoice, { allowExpired? }?)` | Checks the invoice's form, signature and expiry. Returns its terms, an `id` derived from them, and the `idempotencyKey` that pays it. Pays nothing. | | `invoices.pay(invoice, options)` | Verifies it, quotes the payment and refuses one that can't be made now or exceeds `maxDebit`, reserves the invoice for this route, then pays its exact terms. A retry after the invoice expired reports a payment made in time. `funding` is required; `policyId` for a policy, `maxDebit`, `waitMs`. Returns the payment and `invoiceId`. | | `invoices.verifyPayment(invoice, input)` | Checks a transaction is this invoice's payment, on its terms and in its validity window. Finalized by default. Returns `{ invoiceId, orderId, payment }`. | `sign` and `create` take `payer`, a decimal `amount`, `token`, `orderId` and `expiresAt` in Unix seconds. `recipient` defaults to, and must be, the signing account. `nonce` (32 random bytes) and `issuedAt` (now) are defaults: keep them to rebuild the same invoice. An invoice without a request is tied to its payment by the memo `invoiceReference(id)`, so it is paid from a policy; a wallet payment's schedule can't carry a memo. `verifyPayment` takes `txHash`, `policyId` or `scheduleId`, and `finality`, and accepts only the invoice's own payment: the request's, or the one carrying its reference. A payment that landed while the invoice was valid still verifies after it expires. Store the invoice id, the payment's `eventId` and the order uniquely when you fulfil it. ### Server and framework adapters | Import | API | |---|---| | `remesso-sdk/postgres` | `createPostgresStorage(postgresClient, { namespace?, jobLeaseMs?, heartbeatMs? })`: `initialize()`, both journals, the invoice store, the shared lock, jobs (a claim lasts five minutes by default) and a fulfilment inbox. Needs direct or session-pooled connections. | | `remesso-sdk/dev` | `startDev(options?)`: a local Celo fork with funded public test accounts, a stand-in for Remesso's backend, clients, a sample invoice, `smoke()` and `close()`. Needs Foundry's `anvil`. The command line is `npx remesso-sdk dev`. | | `remesso-sdk/signers` | `managedSigner({ address, signTransaction, signTypedData, signMessage? })`: a viem account whose callbacks only sign. The SDK checks every field of what comes back, the signer, and that each signature is low-s. | | `remesso-sdk/tools` | `createAgentTools(client, { authorizeWrite?, enableWrites? }?)`: tool definitions with schemas, `list()` and `call(name, input)`. Tools that pay are refused unless the host allows them. | ### `agents.pay(agentId, input)` Pays an agent registered in ERC-8004 through `remittances.send`, after checking its wallet in the registry and its payee profile. `funding` is required. With `requestId`, the request gives the amount and token, and the key defaults to `request:`; without one, pass `amount`, `token` and `idempotencyKey`. `expectedWallet` makes it refuse any other wallet. `policyId` and `payer` apply to policy funding. Returns the payment and `counterparty: { agentId, wallet }`. It refuses a profile it can't read, a token not on Celo, a missing required reference, a request addressed to someone else, and a wallet that changes before it sends. It never sends a payment anywhere a profile names. ### `receipts.verify(input)` Takes `txHash`, `from` (whose money moved), `to`, `token` and the exact decimal `amount` received. `policyId` or `scheduleId`, not both, says which payment; `memo` must match a policy payment's. `finality` is `"included"` by default, or `"finalized"`. Returns a `PaymentProof`: `eventId`, `chainId`, `contract`, `txHash`, `logIndex`, `blockNumber`, `blockHash`, `from`, `to`, `token`, `amount` in base units, and the `finality` checked. A policy payment's proof has `kind: "policy_payment"`, `policyId`, `agent`, `memo` and `callerVerified`; a schedule's has `kind: "run"`, `scheduleId`, `amountIn` and `runNumber`. It refuses a reverted or replaced transaction, a block no longer on the chain, a payment by another contract, the wrong token, amount, payer or recipient, and more than one match. `finalized` refuses until the block is final, and when the RPC can't say. A receipt some of forno's nodes have lost is read from its block instead. `callerVerified` is `false` when a contract called the policy, since receipts don't show internal calls. Store `eventId` uniquely when you fulfil an order: verifying proves a payment, not that you haven't already delivered for it. ## `schedules` Schedules a payer signs once. See the [schedules guide](https://docs.remesso.xyz/docs/guides/schedules). ### `schedules.create(input)` Approves the contract for the schedule's total, on top of any existing approval, and creates the schedule: two transactions, sent with consecutive nonces. Refuses before signing anything the executor would never run. | Field | Type | Description | |---|---|---| | `to` | `string` | Who gets paid. With `request`, it must match the request's address, and defaults to it. | | `amount` | `string` | Per payment, what leaves your wallet, as a decimal string. Give this or `receive`, not both. | | `receive` | `string` | Per payment, what the recipient receives after the fee. The SDK works out what to send. | | `token` | `string` | A symbol or address. Default `USDT`, or the request's token. | | `every` | `Cadence` or `number` | `"week"`, `"fortnight"`, `"month"` (30 days) or `"quarter"` (90 days), or seconds, at least 3,600. Default `"week"`, or the request's. | | `runs` | `number` | How many payments. Required, unless the request names it. | | `firstRunAt` | `Date` | When the first payment is due. Default now, so it goes out within minutes. | | `trigger` | `"remesso"`, `string` or `null` | Who may bring a payment forward: `"remesso"` for Remesso's executor, or an address. Default nobody. | | `triggers` | `number` | How many early sends the trigger may make. Default: all of them. | | `request` | `string` | A payment request id this schedule answers. Its terms fill whatever you leave out, and the schedule is reported against it. | | `approve` | `boolean` | `true` (default) approves the schedule's total; `false` when you manage approvals yourself. | Returns `Promise`: | Field | Type | Description | |---|---|---| | `id` | `bigint` | The schedule's id on the contract. | | `hash` | `Hex` | The `createSchedule` transaction. | | `approveHash` | `Hex \| null` | The approval transaction, or `null` with `approve: false`. | | `to`, `token` | | The recipient and the token's symbol. | | `amountIn` | `bigint` | What leaves the payer's wallet each payment, in base units. | | `recipientGets` | `bigint` | What the recipient receives each payment, after the fee. | | `feeBps`, `runs`, `intervalSeconds` | `number` | The fee in basis points, the number of payments and the cadence. | | `firstRunAt`, `expiresAt` | `Date` | When the first payment is due, and when the schedule expires. | | `request` | object | With `request`: how the request took the answer, `{ status, signed }`, or `{ error }`. | | `warnings` | `string[]` | For example, a balance below one payment: payments fail until it is funded. | Errors: `invalid` (missing or malformed input, `to` is this wallet, `firstRunAt` in the past), `refused` (a token not taken for new schedules, an amount outside the per-payment limits, more payments than fit in a year, a request that isn't open or isn't addressed to this wallet), `no_account`, `wrong_chain`, `no_gas`, `reverted`. ### `schedules.get(id)` Returns `Promise`, as the contract stores it: `id`, `sender`, `to`, `token` (an address), `amountIn`, `recipientGets`, `feeBps`, `intervalSeconds`, `maxRuns`, `runsExecuted`, `nextRunAt`, `lastRunAt` (or `null`), `expiresAt`, `active`, `cancelled`, `payoutType`, `trigger` and `triggersLeft`. Throws `not_found` for an id nobody has created. ### `schedules.status(id)` Whether a payment can go out: `Promise<{ due: boolean; funded: boolean; approved: boolean; nextRunAt: Date }>`. `funded` checks the payer's balance and `approved` the allowance, against one payment. ### `schedules.list(sender?)` The ids of every schedule a sender created: `Promise`. Default: this account. ### `schedules.cancel(id)` Stops the schedule for good: `Promise`. Only its payer can cancel. The token approval stays; lower it if nothing else needs it. ### `schedules.setActive(id, active)` Pauses (`false`) or resumes (`true`) the schedule: `Promise`. ### `schedules.setTrigger(id, trigger, triggers?)` Changes who may bring a payment forward, and how many times: `Promise`. `"remesso"` grants Remesso's executor; `null` removes the trigger. `triggers` defaults to 0, which allows no early sends, so pass the count you mean. ## `pay(input)` As an agent, pays through a policy a person granted. It signs, records the payment in the store, broadcasts it and waits for the receipt. See the [agent payments guide](https://docs.remesso.xyz/docs/guides/agent-payments). | Field | Type | Description | |---|---|---| | `to` | `string` | The recipient. | | `amount` | `string` | Decimal, in the policy token's units. | | `token` | `string` | Symbol or address. Default: the token of the policy used. | | `policyId` | id | Which policy to pay under. Default: an active one for the token that can cover the amount. | | `payer` | `string` | Optional: only use a policy owned by this wallet. Request payments set it automatically. | | `memo` | `string` | Up to 32 bytes, recorded on-chain in the `Paid` event. A request's reference goes here. | | `idempotencyKey` | `string` | One per payment you intend. A retry with the same key reports the first attempt and never pays twice. | | `allowRepeat` | `boolean` | Pay even though an identical payment went out in the last ten minutes. | | `waitMs` | `number` | How long to wait for the receipt before answering `"pending"`: 1 to 120,000 milliseconds. Default 45 seconds. | | `startBy` | `number` | Optional, Unix seconds: don't start the payment at or after this time. Ignored once the key has a saved payment. | | `validUntil` | `number` | Optional, Unix seconds: the payment must land before this. It isn't started within a minute of it, nor broadcast within 30 seconds of it, and if it's still unsent then, a no-op is signed at its nonce so it can never land late. A retry can't change it. | Returns `Promise`: `status` (`"confirmed"` or `"pending"`), `hash`, `policyId`, `to`, `amount` (base units), `token`, `sent` (for example `"1.5 USDT"`), `explorer` (a Celoscan link), `repeat` (true when this key had already paid and nothing new was sent), and `cancellation`, the no-op's hash, when the deadline passed before it went out. On `"pending"`, call again with the same key: whatever happened, the answer is about that one payment. Errors: `refused` (outside the policy, revoked or expired, or cancelled after its deadline with `details.expired`), `key_reused`, `repeat`, `no_account` (no local account), `no_gas`, `reverted`. ## `policies` Spending limits a person grants an agent. ### `policies.create(input)` As the owner, approves a budget for the allowance contract and creates the policy: `Promise<{ id: bigint; hash: Hex; approveHash: Hex }>`. | Field | Type | Description | |---|---|---| | `agent` | `string` | The agent's address: the key that will call `pay`. | | `token` | `string` | Symbol or address. | | `perPayment` | `string` | The cap per payment, decimal. | | `perDay` | `string` | The cap per day, decimal: a 24-hour window that opens with the first payment after the previous window has closed. | | `days` | `number` | How long it lasts. Default 30. | | `recipients` | `string[]` | If given, the only addresses the agent may pay. | | `budget` | `string` | Added to your approval for the allowance contract: the most this and your other policies for this token can spend together. | ### `policies.get(id)` Returns `Promise`: `id`, `owner`, `agent`, `token`, `symbol`, `perPayment`, `perDay`, `expiresAt`, `allowlistOnly`, `revoked`, `remainingToday`, `ownerBalance`, `ownerAllowance` (shared by all the owner's policies for the token), `active` (not revoked and not expired) and `spendable` (the largest payment the policy allows right now, every limit counted). Throws `not_found` for a policy that doesn't exist. ### `policies.find(agent?)` Every policy naming an agent, newest first: `Promise`. Default: this account. Reads Remesso's index, plus the newest ids from the chain, so a policy created seconds ago shows. ### `policies.revoke(id)` As the owner, ends the policy now: `Promise`. ### `policies.setRecipient(id, recipient, allowed)` As the owner, adds an address to the policy's allowlist or removes it: `Promise`. The allowlist applies only to a policy created with `recipients`: whether a policy is limited to a list is fixed when it is created. ## `requests` Payment requests. See the [payment requests guide](https://docs.remesso.xyz/docs/guides/payment-requests). ### `requests.create(input)` Asks a wallet to pay you. Free. Returns `Promise<{ id: string; reference: string; link: string; status: RequestState }>`. The `reference` is the id without dashes, which an agent payment carries in its memo to answer the request. | Field | Type | Description | |---|---|---| | `payer` | `string` | The wallet you're asking. | | `to` | `string` | Where you want to be paid. Default: this account. | | `amount` | `string` | What you want to receive per payment, decimal. The payer's form adds the fee on top. | | `token` | `string` | Symbol or address. | | `every` | `Cadence` | How often. | | `runs` | `number` | How many payments. | | `from` | `string` | Who is asking, up to 40 characters. | | `note` | `string` | Why, up to 120 characters. | ### `requests.get(id)` Where a request stands: `Promise`, with `id`, `reference`, `status`, `payer`, `requested` (`to`, `amount`, `token`, `intervalSeconds`, `runs`), `signed` (what the payer signed, once answered) and `payment` (`null` until answered, then `state`, `schedule`, `txs` and `reason`). It is paid only when `payment.state` is `"paid"`. ### `requests.list(payer?)` The live requests addressed to a wallet, pending and under 30 days old: `Promise`. Default: this account. ### `requests.answer(id, scheduleId, contract?, opts?)` Reports the schedule that answers a request: `Promise<{ status: RequestState; signed: unknown }>`. It binds only if the schedule is from the wallet asked, pays the address that asked, was created after the request and answers no other request. While the backend can't see a schedule that has just landed, it retries for up to `opts.waitMs` (default 90 seconds). Answering twice with the same schedule returns the first answer. `schedules.create({ request })` calls this for you. ### `requests.decline(id, opts?)` Turns a request down for everyone, with an EIP-712 signature: `Promise<{ ok: boolean; id: string; status: RequestState }>`. Signed by the wallet the request was sent to, or by an agent under a live policy from that wallet (`opts.policyId`). `opts.ttlSeconds` (default 600) is how long the signature is valid. ### `requests.pay(id, opts?)` As an agent, pays a request through a policy from the wallet it was sent to: `Promise`. The payment carries the request's reference as its memo, and the request id is the default idempotency key, so each request is paid once. `opts` takes `pay`'s fields except `to`, `memo` and `token`, and `amount` defaults to the request's. ### `requests.waitUntilPaid(id, opts?)` Polls until the request has a payment with a transaction: `Promise`. `opts.timeoutMs` (default 20 minutes), `opts.intervalMs` (default 15 seconds) and `opts.signal`. Pass `opts.final: true` to wait until at least one payment transaction is finalized. Throws `refused` if the request is declined or expires, and `timeout` when time runs out. ## `events` Payment events from Remesso's indexer. See the [payment events guide](https://docs.remesso.xyz/docs/guides/payment-events). ### `events.list(query?)` One page of events: `Promise<{ events: PaymentEvent[]; next: number }>`. Pass `next` as `after` for the following page. | Field | Type | Description | |---|---|---| | `address` | `string` | Default: this account. | | `role` | `"to"`, `"from"`, `"agent"` or `"any"` | Which side of the event the address is on. Default `"any"`. | | `kinds` | `EventKind[]` | Any of `"run"`, `"schedule_created"`, `"policy_payment"`, `"policy_created"`, `"policy_revoked"`. | | `after` | `number` | Events with a `seq` above this. Default 0, from the start. | | `limit` | `number` | 1 to 500. Default 100. | ### `events.watch(query?)` An async iterator: every event from `after` on, then each new event or change as it comes, until `signal` aborts. Takes `list`'s fields plus `intervalMs` (default 15 seconds) and `signal`. ### `events.waitFor(match, query?)` The first event from `after` on that `match` accepts: `Promise`. Takes `list`'s fields plus `timeoutMs` (default 20 minutes) and `intervalMs`. Throws `timeout`. ## `webhooks` Webhook subscriptions for the receiving address, each request signed by this client's account. See the [webhooks guide](https://docs.remesso.xyz/docs/guides/webhooks). | Method | Returns | Description | |---|---|---| | `subscribe({ url, recipient? })` | `{ ok, subscription, recipient, url, state, secret }` | Starts delivering the address's payments to `url` (https). The endpoint must already answer the challenge. The `secret` is returned once. | | `rotate({ url, recipient? })` | `{ ok, secret, previousSecretValidUntil }` | A new signing secret. The old one keeps signing alongside it for a day. | | `revoke({ url, recipient? })` | `{ ok, state }` | Stops deliveries to `url`. | | `replay({ url, recipient?, days? })` | `{ ok, requeued, days }` | Sends failed deliveries from the last `days` (default 1) again. | `recipient` defaults to this account. ## `agents.find(agentId)` An ERC-8004 agent's wallet and payee profile: `Promise`, with `agentId`, `wallet` (from the registry's `getAgentWallet` only, or `null` when the agent has set none: then don't pay it), `profile` (the `remesso` entry of its registration file, or `null`) and `note` (why either is missing). Reading an https or ipfs registration file needs the `fetchText` option. See [find agents](https://docs.remesso.xyz/docs/guides/find-agents). ## `payLink(input)` The link that opens a payer's Remesso form filled in: `string`. `to` defaults to this account. The other fields (`from`, `amount`, `token`, `every`, `runs`, `note`, `request`) are optional. See [pay links](https://docs.remesso.xyz/docs/guides/payment-requests). --- # Utilities and constants > The functions and constants remesso-sdk exports besides the client - tokens, amounts, fees, limits, memos, pay links, webhook verification, stores, ABIs and the deployment. Source: https://docs.remesso.xyz/docs/reference/utilities Besides `createRemesso`, the SDK exports the helpers it uses itself. They are pure functions with no network access, unless noted. ## Tokens and amounts Amounts go in as decimal strings and come out as base units (`bigint`). Never pass a JavaScript number in between: that is how precision is lost. ```ts import { findToken, formatAmount, token, toUnits } from "remesso-sdk"; const usdt = token("USDT"); // throws for a token Remesso doesn't know const units = toUnits("2.50", usdt); // 2_500_000n: USDT has 6 decimals console.log(formatAmount(997_500n, usdt)); // "0.9975 USDT" console.log(findToken("USAT")?.symbol); // "USA₮": undefined if unknown, never throws ``` | Export | Signature | Description | |---|---|---| | `token` | `(symbolOrAddress: string) => Token` | A token by symbol or address. Throws `invalid` for one Remesso doesn't know. | | `findToken` | `(symbolOrAddress: string) => Token \| undefined` | The same, returning `undefined` instead of throwing. | | `toUnits` | `(amount: string, t: Token) => bigint` | A decimal string in base units. Throws `invalid` for a malformed amount or more decimals than the token has. | | `formatAmount` | `(units: bigint \| string, token: Token \| string) => string` | Base units as a decimal with the symbol, such as `"1500 wARS"`. | Symbols match in any case, and common spellings resolve: `USAT` and `USD₮` to USA₮ and USDT, `cUSD` to USDm. A `Token` has `symbol`, `address`, `decimals`, `currency`, `directAllowed`, `feeCurrency`, `minPerRun` and `maxPerRun`. The full table is on [tokens and limits](https://docs.remesso.xyz/docs/reference/tokens-and-limits). ## Fees and limits The commission is taken from each scheduled payment and rounded up, exactly as the contract rounds it. ```ts import { FEE_BPS, feeOf, grossFor, netOf, runLimits, token, toUnits } from "remesso-sdk"; const usdt = token("USDT"); const gross = toUnits("1", usdt); feeOf(gross, FEE_BPS); // 2_500n: 0.0025 USDT netOf(gross, FEE_BPS); // 997_500n: what arrives grossFor(toUnits("50", usdt), FEE_BPS); // 50_125_314n: send this for exactly 50 to arrive runLimits(token("wARS")); // { min: 1,500 wARS, max: 1,500,000 wARS }, in base units ``` | Export | Signature | Description | |---|---|---| | `FEE_BPS` | `number` | Remesso's commission on new schedules, in basis points: 25, or 0.25%. | | `feeOf` | `(gross: bigint, bps: number) => bigint` | The commission on a gross amount, rounded up. | | `netOf` | `(gross: bigint, bps: number) => bigint` | What the recipient receives from a gross amount. | | `grossFor` | `(net: bigint, bps: number) => bigint` | The smallest gross amount that delivers at least `net`. | | `runLimits` | `(t: Token) => { min: bigint; max: bigint }` | The gross per-payment bounds the executor sends. Throws `refused` for a token not taken for new schedules. | ## Memos ```ts import { memo32 } from "remesso-sdk"; memo32("invoice-42"); // 0x696e766f6963652d3432 followed by zeros: 32 bytes ``` `memo32(memo?)` encodes a memo as the `bytes32` an agent payment records on-chain, padded on the right. It throws `invalid` over 32 bytes rather than cutting it. A plain letter is one byte, an accented letter two and an emoji four. `invoiceReference(invoiceId)` is the memo that settles a signed invoice made without a request: the first 32 hex characters of its id. `invoices.pay` sets it for you; pass it as `memo` to pay such an invoice another way. ## Pay links ```ts import { parsePayLink, payLink } from "remesso-sdk"; const link = payLink({ to: process.env.RECIPIENT!, amount: "50", token: "USDT", every: "month", runs: 12, from: "Ada's studio", }); // https://remesso.xyz/schedules/new?to=0x…&from=Ada%27s+studio&amount=50&token=USDT&every=month&runs=12 parsePayLink(link); // { to, from, amount: "50", token: "USDT", intervalSeconds: 2592000, runs: 12 } ``` | Export | Signature | Description | |---|---|---| | `payLink` | `(input: PayLinkInput, origin?: string) => string` | A link that opens the Remesso payment form filled in. The amount is what the recipient should receive. | | `parsePayLink` | `(urlOrQuery: string) => PayLink` | What a link says, keeping only what validates: a real address, a positive amount, a token taken for new schedules, a cadence the form offers, a request id. | The client's own `payLink` method defaults `to` to the client's address. ## Webhook verification | Export | Signature | Description | |---|---|---| | `webhookHandler` | `(options: WebhookHandlerOptions) => (req: Request) => Promise` | A fetch-style handler: answers the subscription challenge for your address, verifies the signature, payment shape and recipient, and calls `onPayment` per delivery attempt. Deduplicate `message.id` with your business action. | | `verifyWebhook` | `(raw, headers, secrets) => Promise` | Whether a raw body was signed with one of your secrets in the last five minutes. | | `parseWebhook` | `(raw, headers, secrets) => Promise` | A verified delivery, parsed. Throws for a bad signature or a body that isn't a Remesso payment message. | See [webhooks](https://docs.remesso.xyz/docs/guides/webhooks) for running them, and [webhook payloads](https://docs.remesso.xyz/docs/reference/webhook-payloads) for the message format. ## Payment stores Agent payments are recorded before they are broadcast, so a retry with the same idempotency key can find the first attempt. | Export | Description | |---|---| | `memoryStore()` | The default: records in memory, lost when the process exits. | | `fileStore(path?)` | From `remesso-sdk/node`. Records in a JSON file with `0600` permissions, by default `~/.remesso-sdk/payments.json`. Use one process per file. | | `PaymentStore` | The interface, to keep records in your own database: `byKey(key, deployment)`, `since(ms)` and `save(record)`. | | `memoryRemittanceStore()` | Wallet payments in memory, lost when the process exits. | | `fileRemittanceStore(path?)` | From `remesso-sdk/node`. Wallet payments in a JSON file with `0600` permissions, by default `~/.remesso-sdk/remittances.json`. Use one process per file. | | `RemittanceStore` | The interface: `get(key, scope)`, `list(scope)` and `save(record)`. `list` returns every record for the signer; `save` resolves only once the record is durable. | Keys belong to one chain, contract and signing key. A key, and a transaction not yet settled, are kept for good; only settled payments without a key are dropped, after 30 days or past 1,000 of them. Keep the stores across restarts and back them up. An unreadable or corrupt file refuses new payments rather than forgetting the old ones. Before signing anything, the SDK reads every journal for transactions the chain hasn't mined yet. It sends them again, lowest nonce first, or cancels at its nonce one that must no longer land, and signs the new one above them. So a lost broadcast never holds up the transactions after it, and a lagging RPC never lets a new payment replace a saved one. Keep both stores with the key. Clients that share a key in one process share one queue. Workers in several processes need the shared journals and lock of `remesso-sdk/postgres`. A nonce used with no receipt to show by what is a `conflict` to investigate, never a reason to pay again. ## Node helpers From `remesso-sdk/node`: | Export | Description | |---|---| | `fileStore(path?)` | Agent payment records on disk, above. | | `fileRemittanceStore(path?)` | Wallet payment records on disk, above. | | `toNodeListener(handler)` | Runs a fetch-style handler, such as `webhookHandler`'s, under `http.createServer` or Express. Mount it before any JSON body parser: the signature covers the raw body. | | `safeFetchText(url)` | Fetches an agent's registration file only from public addresses, connecting to the address it checked. Pass it as the client's `fetchText`. | ## Deployment constants Generated from what is live, never typed by hand. | Export | Description | |---|---| | `deployment` | Celo mainnet as Remesso runs it: chain, contracts, tokens and endpoints. | | `CHAIN_ID` | `42220`. | | `EXECUTOR` | The `RemessoExecutorV5` contract, which holds schedules. | | `ALLOWANCE` | The `RemessoAgentAllowance` contract, which holds agent policies. | | `REGISTRY` | The ERC-8004 identity registry. | | `CURRENT_EXECUTOR` | `0x…0001`: the trigger value that means Remesso's executor, whichever key it is at the time. | | `CADENCES` | `week`, `fortnight`, `month` (30 days) and `quarter` (90 days), in seconds. | The addresses are on [contracts and endpoints](https://docs.remesso.xyz/docs/reference/contracts). ## ABIs `executorAbi`, `allowanceAbi`, `erc20Abi` and `registryAbi`, for reading or writing the contracts with viem directly. ```ts import { EXECUTOR, executorAbi } from "remesso-sdk"; const [due, funded, approved] = await reader.publicClient.readContract({ address: EXECUTOR, abi: executorAbi, functionName: "runnability", args: [scheduleId], }); console.log({ due, funded, approved }); ``` ## Attribution | Export | Description | |---|---| | `ATTRIBUTION` | The ERC-8021 suffix the SDK appends to every transaction: Remesso's own codes. | | `attributionFor(builderCodes?, ours?)` | That suffix with your builder codes after Remesso's, checked by the rules the executor applies. For a transaction you send yourself. | See [builder codes](https://docs.remesso.xyz/docs/guides/builder-codes). ## Errors `RemessoError` is the class of every refusal, with a `code` (`RemessoErrorCode`) and sometimes `details`. See [errors](https://docs.remesso.xyz/docs/reference/errors). ## Types `Cadence`, `CreateScheduleInput`, `CreatedSchedule`, `Schedule`, `PayInput`, `PaymentResult`, `Policy`, `CreatePolicyInput`, `CreateRequestInput`, `RequestState`, `RequestStatus`, `RequestSummary`, `EventKind`, `EventQuery`, `PaymentEvent`, `WebhookHandlerOptions`, `WebhookMessage`, `WebhookType`, `FoundAgent`, `PayeeProfile`, `PayLink`, `PayLinkInput`, `PaymentRecord`, `PaymentStore`, `Token`, `Remesso`, `RemessoOptions` and `RemessoErrorCode`. --- # Webhook payloads > The exact format of Remesso's webhook deliveries, how each one is signed, and the rules for retries and duplicates. Source: https://docs.remesso.xyz/docs/reference/webhook-payloads This page specifies what Remesso sends to a subscribed endpoint: the headers, the signature, the challenge, each message type and every field. The [webhooks guide](https://docs.remesso.xyz/docs/guides/webhooks) shows how to receive them with the SDK. ## Headers Every delivery is an HTTPS `POST` with a JSON body and these headers, as [Standard Webhooks](https://www.standardwebhooks.com/) defines them: | Header | Example | Meaning | |---|---|---| | `webhook-id` | `msg_775f0089945d724d7d63cae36e805f8c` | The delivery's id. The same on every attempt at this delivery. | | `webhook-timestamp` | `1792937521` | Unix seconds when this attempt was signed. New on every attempt. | | `webhook-signature` | `v1,6+i64kmGDzTxyH+NKSPRQDTpmBehkBJdRsVeNRzdclo=` | One or more signatures, separated by spaces. | | `content-type` | `application/json` | | During a secret rotation, `webhook-signature` carries one `v1,` signature per secret, so a server holding either secret verifies. ## How a delivery is signed The signature is an HMAC-SHA256 over the id, the timestamp and the raw body, joined by dots: ```text signed content = webhook-id + "." + webhook-timestamp + "." + raw body key = base64-decode(the secret, without its "whsec_" prefix) signature = "v1," + base64(HMAC-SHA256(key, signed content)) ``` To verify a delivery: 1. Read the raw body as bytes, before any JSON parsing. A parsed and re-serialised body won't match. 2. Refuse it if `webhook-timestamp` is more than 5 minutes from your clock. That is what stops a captured delivery being replayed later. 3. Compute the signature with your secret and compare it, in constant time, with each `v1,` entry in `webhook-signature`. Accept the delivery if any one matches. `verifyWebhook` and `parseWebhook` in remesso-sdk do exactly this. So does any Standard Webhooks library, such as `standardwebhooks` or `svix`. Without them, in TypeScript with Web Crypto: ```ts const TOLERANCE_SECONDS = 5 * 60; export async function verify(raw: string, headers: Headers, secret: string): Promise { const id = headers.get("webhook-id"); const timestamp = headers.get("webhook-timestamp"); const signatures = headers.get("webhook-signature"); if (!id || !timestamp || !signatures) return false; if (Math.abs(Date.now() / 1000 - Number(timestamp)) > TOLERANCE_SECONDS) return false; const key = await crypto.subtle.importKey( "raw", Uint8Array.from(atob(secret.replace(/^whsec_/, "")), (c) => c.charCodeAt(0)), { name: "HMAC", hash: "SHA-256" }, false, ["sign"], ); const mac = new Uint8Array(await crypto.subtle.sign("HMAC", key, new TextEncoder().encode(`${id}.${timestamp}.${raw}`))); const expected = btoa(String.fromCharCode(...mac)); return signatures.split(" ").some((part) => part.startsWith("v1,") && sameString(part.slice(3), expected)); } function sameString(a: string, b: string): boolean { if (a.length !== b.length) return false; let diff = 0; for (let i = 0; i < a.length; i++) diff |= a.charCodeAt(i) ^ b.charCodeAt(i); return diff === 0; } ``` In Python: ```python import base64, hashlib, hmac, time def verify(raw: bytes, headers, secret: str) -> bool: msg_id = headers.get("webhook-id") timestamp = headers.get("webhook-timestamp") signatures = headers.get("webhook-signature") or "" if not msg_id or not timestamp: return False if abs(time.time() - int(timestamp)) > 5 * 60: return False key = base64.b64decode(secret.removeprefix("whsec_")) signed = f"{msg_id}.{timestamp}.".encode() + raw expected = base64.b64encode(hmac.new(key, signed, hashlib.sha256).digest()).decode() return any( part.startswith("v1,") and hmac.compare_digest(part[3:], expected) for part in signatures.split(" ") ) ``` ### A test vector With the secret `whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw`, the headers in the table above and this exact body, on one line, your signature must come out as `6+i64kmGDzTxyH+NKSPRQDTpmBehkBJdRsVeNRzdclo=`. The timestamp is in the past, so skip the 5-minute check while you test. ```text {"type":"payment.received","timestamp":"2026-10-25T14:12:01.214Z","data":{"chainId":42220,"contract":"0x3c357af3300f383ea49b6d6ed8e084199a92bed8","schedule":"2","from":"0x5732925393fd10baf447d98826ae72febabff483","to":"0xb21051155ab66d9e599af24475359fa8f3d63838","token":"0x48065fbbe25f71c9282ddf5e1cd6d6a887483d5e","amount":"1000000","amountIn":"1002507","runNumber":1,"early":false,"txHash":"0xbbb4e6f9c90474e18f9ce1c6b99885c5d6579ae2d1a0765352af228ccadf6041","logIndex":8,"blockNumber":81036705,"blockHash":"0x6b7f2ce27a4ec5911aa720fcff02fe55d45dd14b8296e4d830bf09f6438bffe7","blockTime":"2026-10-25T14:11:03+00:00","final":false,"removed":false}} ``` The addresses and hashes in it are made up. ## The challenge When you subscribe, Remesso first posts a verification message to the endpoint, before the subscription is active: ```json { "type": "webhook.verification", "timestamp": "2026-10-25T14:00:00.000Z", "data": { "challenge": "9f86d081884c7d659a2feaa0c55ad015", "recipient": "0xb21051155ab66d9e599af24475359fa8f3d63838", "url": "https://example.com/remesso" } } ``` Answer within 10 seconds with a 2xx status and the challenge, either as the whole body or as JSON: ```json { "challenge": "9f86d081884c7d659a2feaa0c55ad015" } ``` The message is signed with the new secret, like every later delivery, but you can't check it yet: the secret is returned only once the endpoint has answered. Answer only for addresses you subscribe, which is what `webhookHandler` does through its `recipient` option. Echoing every challenge would let a stranger point their deliveries at your server. ## Message types | Type | Sent when | |---|---| | `payment.received` | The indexer sees a payment to the address, before it is final. Usually within a minute or two of the transaction. | | `payment.finalized` | Celo's finalized block passes the payment, usually 25 to 30 minutes later. A payment first seen already final gets only this one. | | `payment.removed` | A reorg undid a payment you were already told about. Only a subscription that received a message about the payment gets this, and anything still queued about it is cancelled. | Only payments are delivered: schedule payments (runs) and agent payments under a policy, to the subscribed address. Schedules and policies being created or revoked are not; read those as [payment events](https://docs.remesso.xyz/docs/guides/payment-events). ## The body Every message has the Standard Webhooks shape `{ type, timestamp, data }`: | Field | Type | Meaning | |---|---|---| | `type` | string | One of the message types above. | | `timestamp` | string | ISO 8601 time when the delivery was queued. The body, and so this, is the same on every attempt. | | `data` | object | The payment (below). | ## The `data` fields Amounts are base units as strings, after Remesso's fee: what the recipient received. Addresses are lowercase. | Field | Type | Set for | Meaning | |---|---|---|---| | `chainId` | number | all | `42220`, Celo mainnet. | | `contract` | string | all | The executor for a schedule payment, the allowance contract for a policy payment. | | `schedule` | string | schedule payments | The schedule's id. | | `policy` | string | policy payments | The policy's id. | | `from` | string | all | The payer: the schedule's sender, or the policy's owner. | | `to` | string | all | The recipient: your subscribed address. | | `token` | string | all | The token that arrived. | | `amount` | string | all | Base units that arrived. | | `amountIn` | string or null | schedule payments | Base units that left the payer, before the fee. | | `runNumber` | number or null | schedule payments | Which payment of the schedule this is, from 1. | | `early` | boolean | schedule payments | Whether a trigger brought this payment forward. | | `memo` | string or null | policy payments | The 32-byte memo, as hex. | | `memoText` | string or null | policy payments | The memo as text, when it is plain text. A payment request's reference arrives here. | | `txHash` | string | all | The transaction. | | `logIndex` | number | all | The log inside it. `txHash` and `logIndex` together identify the payment. | | `blockNumber` | number | all | The block it was mined in. | | `blockHash` | string | all | That block's hash. | | `blockTime` | string | all | When that block was mined. | | `final` | boolean | all | At or below Celo's finalized block. | | `removed` | boolean | all | Undone by a reorg. | ## Examples A schedule payment, just seen: ```json { "type": "payment.received", "timestamp": "2026-10-25T14:12:01.214Z", "data": { "chainId": 42220, "contract": "0x3c357af3300f383ea49b6d6ed8e084199a92bed8", "schedule": "2", "from": "0x5732925393fd10baf447d98826ae72febabff483", "to": "0xb21051155ab66d9e599af24475359fa8f3d63838", "token": "0x48065fbbe25f71c9282ddf5e1cd6d6a887483d5e", "amount": "1000000", "amountIn": "1002507", "runNumber": 1, "early": false, "txHash": "0xbbb4e6f9c90474e18f9ce1c6b99885c5d6579ae2d1a0765352af228ccadf6041", "logIndex": 8, "blockNumber": 81036705, "blockHash": "0x6b7f2ce27a4ec5911aa720fcff02fe55d45dd14b8296e4d830bf09f6438bffe7", "blockTime": "2026-10-25T14:11:03+00:00", "final": false, "removed": false } } ``` 1 USDT arrived (`1000000` at 6 decimals); 1.002507 left the payer, the difference being Remesso's 0.25% fee. An agent payment, now final. It carries a payment request's reference in its memo: ```json { "type": "payment.finalized", "timestamp": "2026-10-25T14:44:10.032Z", "data": { "chainId": 42220, "contract": "0xa6351faeeb097698289586c4d6188530300db209", "policy": "8", "from": "0x5732925393fd10baf447d98826ae72febabff483", "to": "0xb21051155ab66d9e599af24475359fa8f3d63838", "token": "0x48065fbbe25f71c9282ddf5e1cd6d6a887483d5e", "amount": "20000", "memo": "0x3533613862613131336466633436653539613432363431353830386438613634", "memoText": "53a8ba113dfc46e59a426415808d8a64", "txHash": "0xa30259d8bc0fb86ae3aed765aff8efe2dbb7e652eec5fc0bb5817c51e800281d", "logIndex": 400, "blockNumber": 81036939, "blockHash": "0x9251f5ca74510ea4d7220f3525960621c42580bcaaca5f372ce8ce60b15b9346", "blockTime": "2026-10-25T14:14:57+00:00", "final": true, "removed": false } } ``` A payment a reorg undid. The fields describe the payment as you were told about it, with `removed: true`: ```json { "type": "payment.removed", "timestamp": "2026-10-25T14:13:30.508Z", "data": { "chainId": 42220, "contract": "0x3c357af3300f383ea49b6d6ed8e084199a92bed8", "schedule": "2", "from": "0x5732925393fd10baf447d98826ae72febabff483", "to": "0xb21051155ab66d9e599af24475359fa8f3d63838", "token": "0x48065fbbe25f71c9282ddf5e1cd6d6a887483d5e", "amount": "1000000", "amountIn": "1002507", "runNumber": 1, "early": false, "txHash": "0xbbb4e6f9c90474e18f9ce1c6b99885c5d6579ae2d1a0765352af228ccadf6041", "logIndex": 8, "blockNumber": 81036705, "blockHash": "0x6b7f2ce27a4ec5911aa720fcff02fe55d45dd14b8296e4d830bf09f6438bffe7", "blockTime": "2026-10-25T14:11:03+00:00", "final": false, "removed": true } } ``` If the same transaction is mined again in another block, it is news again: a new `payment.received` follows, with the new `blockHash`. ## Delivery and retries - **Success** is a 2xx answer within 10 seconds. Only the status is read, and at most 16 KB of the body. - **Retries:** after a failed attempt, Remesso tries again after 1 minute, 5 minutes, 30 minutes, 2 hours and 6 hours, then every 6 hours, for 24 hours from the first attempt. Each attempt has the same `webhook-id` and body, and a new timestamp and signature. - **After 24 hours** a delivery is held as failed until the address asks for a replay, which reaches back 1 to 7 days and restarts the 24 hours. - **Duplicates:** a delivery can arrive more than once, for example when your answer was lost. Record the `webhook-id`s you have processed and skip repeats. - **Order isn't guaranteed.** A retried `payment.received` can arrive after the `payment.finalized` for the same payment. Keep the latest state you have for each `txHash` and `logIndex`, and let `final` and `removed` decide. - **New subscriptions start from now.** Payments made before the subscription are not delivered. > **Tip** > > Before releasing anything valuable, read the transaction in `txHash` from the chain yourself, and wait for `final: true`. --- # Errors > Every RemessoError code the SDK throws, what causes it, and what to do about it. Source: https://docs.remesso.xyz/docs/reference/errors This page lists every error code remesso-sdk throws, with its usual causes and what to do. ## Catch and branch on the code Every refusal is a `RemessoError` with a `code`, so you can tell "this would never run" from "the network is down" without reading the message. The message is written for a person and says what was wrong. ```ts import { RemessoError } from "remesso-sdk"; try { await payer.schedules.create({ to: "0xRecipient", amount: "1", token: "USDT", every: "month", runs: 12 }); } catch (e) { if (!(e instanceof RemessoError)) throw e; // an RPC or network failure, not a refusal switch (e.code) { case "invalid": case "refused": console.error("fix the request:", e.message); break; case "no_gas": console.error("fund the payer:", e.message); break; case "reverted": console.error("mined and reverted:", e.details?.hash); break; default: throw e; } } ``` `e.details` carries more where the SDK has it: | When | `details` | |---|---| | A Remesso endpoint answered with an error | `status`, the HTTP status, and `body`, its JSON answer | | A transaction reverted | `hash` | | An identical payment went out minutes ago (`repeat`) | `hash` of that payment | | The contract's simulation refused a write | `cause`, the underlying error as text | | A transaction succeeded but its id couldn't be read | `hash` | > **Note** > > Errors from the RPC itself, such as a network failure while reading the chain or a receipt that doesn't arrive in time, come from viem unchanged. They are not `RemessoError`s. ## How endpoint answers map to codes When a Remesso endpoint refuses a call, the SDK turns the HTTP status into a code: `400` is `invalid`, `404` is `not_found`, `409` is `conflict`, `401`, `403` and `429` are `refused`, and anything else, or an endpoint that can't be reached, is `http`. The message is the endpoint's own `error`, with its `detail` after it. ## Codes ### invalid **The input is wrong.** Retrying the same call won't help: change it. - An address that isn't one: `not an address: 0x12`. - A token Remesso doesn't take: `unknown token XYZ: Remesso takes USDT, USDC, ...`. - An amount that isn't a decimal string, or has more decimals than the token: `USDT has 6 decimals; 1.0000001 has 7`. - A schedule that can't be what you meant: - `to is this wallet: a schedule paying itself only loses the fee`; - `give amount (what you send each time) or receive (what arrives), not both`; - `runs is required: how many payments`; - `firstRunAt is in the past`; - `every, in seconds, must be a whole number of at least 3600`. - A memo over 32 bytes, a request id that isn't one, or an empty `idempotencyKey`. - A builder code Remesso's runs wouldn't carry. `createRemesso` throws this, for example `builder code "Acme": 1 to 32 of a-z, 0-9 and _, as Celo's attribution codes are`. - An endpoint answered `400`. For example, a webhook endpoint that didn't answer its challenge: `the endpoint didn't confirm the subscription: the endpoint answered 404`. - `parseWebhook` was given a body that isn't a payment message. **What to do:** fix the input the message names. ### wrong_chain **The RPC isn't Celo mainnet, so nothing is signed:** `the RPC answers chain 44787, not Celo mainnet (42220), so nothing is signed`. The SDK asks the RPC for its chain id before the first write and refuses anything but `42220`. Remesso runs on Celo mainnet only, and there is no testnet. **What to do:** point `rpcUrl` or `publicClient` at Celo mainnet. The default, `https://forno.celo.org`, is. ### no_account **A call needs an account, and the client has none, or one that can't do this:** - a read-only client (no `account` or `walletClient`) creating a schedule, paying, declining a request or managing webhooks; - an agent payment from a `walletClient` whose account lives in a browser wallet: `agent payments are signed before they're sent, which needs a local account (privateKeyToAccount)`. **What to do:** create the client with an `account` (`privateKeyToAccount(...)`) or a `walletClient` that has one. Agent payments need a local account, because the SDK records the signed transaction before sending it. ### refused **Remesso's contracts or its backend would refuse this, and nothing was sent.** The SDK simulates every write first, and checks Remesso's own rules before that, so a refusal costs no gas. - **Outside the executor's per-payment limits:** `Remesso's executor sends 1 USDT to 1,000 USDT per payment, and this is 0.5 USDT. A schedule outside that is valid on-chain and never runs.` See [tokens and limits](https://docs.remesso.xyz/docs/reference/tokens-and-limits). - **A token the executor doesn't take for new schedules:** `the contract doesn't take XYZ for new schedules`. - **Too long a schedule:** `60 payments every 7 days don't fit in a schedule's year`. - **The contract's own reason**, from the simulation. For example, `the contract would refuse createSchedule: the token refused the transfer: the balance or the approval is too low`. - **A policy that doesn't allow this payment:** - `no active policy names this agent for USDT: its person sets one up first`; - `policy 12 was revoked by its owner`, or `policy 12 has expired`; - `0.06 USDT is over what policy 12 allows right now (0.05 USDT)`; - `the policy refuses this payment: today's cap is used up; it resets 24 hours after the day's first payment`. - **A request in the wrong state, or for someone else:** `request ... is declined, so it isn't paid`, or `request ... was sent to 0x..., not to 0x...`. - **A webhook signature that doesn't verify:** `the webhook signature doesn't verify`, from `parseWebhook`. - **An endpoint answered `401`, `403` or `429`:** - `this address already has 5 webhook subscriptions: revoke one first`; - `this wallet already has the maximum pending requests: ...`; - `the signature is not from the recipient address`. - **A deadline:** `this payment's quote has expired, so nothing was signed: prepare it again`, from `startBy`; or a `validUntil` too close to start. - **A payment that started and can't complete.** These are final, and safe to retry under a new key: - a policy payment whose deadline passed before it went out, so a no-op took its nonce (`details.expired`); - a wallet payment whose schedule was cancelled (`details.cancelled`, with `cancellationHash`) or expired (`details.expired`) without running. Its schedule's transactions cost gas, but no money moved, and none can. Until the cancellation lands, `send` answers `pending` instead. **What to do:** read the message. It names the limit, the policy or the state that stood in the way. Nothing was paid, so you can fix it and try again. ### conflict **Already done, or taken.** An endpoint answered `409`. - Subscribing a URL that is already subscribed for the address: `this url is already subscribed for this address: rotate its secret or revoke it`. - Answering a request with a schedule that can't answer it: `that schedule can't answer this request: it was made before the request`, or `that schedule or payment already answers another request`. - Declining a request that is no longer pending: `this request is already accepted`. - A schedule the backend hasn't seen on-chain yet: `that schedule hasn't been seen on-chain yet; try again in a minute`. `requests.answer` retries this one by itself for up to 90 seconds before giving up. The SDK also reports `conflict` when it can't tell what happened and won't guess: - a payment's nonce was used and no receipt shows by what: look at `details.hash` before paying again; - a contract write was signed but no node accepted it: before this key's next transaction it is cancelled with a no-op unless a node has it by then; - a journal returned another key's or another signer's record; - the payer paused a wallet payment's schedule (`details.paused`): resume or cancel it. **What to do:** check the current state, with `requests.get`, `schedules.get`, `remittances.get` or `webhooks.rotate`, rather than repeating the call. Never answer one by paying under a new key. ### reverted **The transaction was mined and reverted, so nothing moved.** The gas was spent. `details.hash` is the transaction, and the message links it on Celoscan. The state changed between the simulation and the block: a balance or an approval spent elsewhere, or a policy revoked in between. For an agent payment, a retry with the same key reports it again: `this key's payment reverted on-chain, so nothing was paid: ... Use a new key to try again.` **What to do:** look at the transaction, fix the cause, and try again. For an agent payment, use a new idempotency key: the old one stays tied to the payment that reverted. A wallet payment raises `reverted` only when its schedule couldn't be created. Once the schedule exists, a reverted approval or run doesn't end it: the schedule could still be paid, so the SDK cancels it and answers `pending` until the cancellation lands, then `refused` with `details.cancelled`. ### no_gas **The account can't pay for gas.** It doesn't hold enough CELO, and doesn't hold a token that can pay gas through Celo's fee abstraction: `0x... has 0 CELO, and this costs about 0.06. Send it a little CELO, or hold some USDT, which can pay for gas.` The SDK pays gas in CELO when the account has enough, and otherwise in the token being sent, for the tokens that support it. [Tokens and limits](https://docs.remesso.xyz/docs/reference/tokens-and-limits) shows which do. An agent pays its own gas, so its key needs a little CELO, or a balance of the policy's token. **What to do:** send the account a little CELO, or some of a token that pays gas. ### key_reused **An idempotency key was already used for a different payment:** `idempotency key "invoice-1" was already used for a different payment (0.02 USDT to 0x...)`. A key stands for one intended payment. The same key with a different recipient, amount or token is refused, so a mistake can't pay twice under one key. **What to do:** for a retry, send exactly the first call's details again. For a new payment, use a new key. ### repeat **An identical payment went out in the last ten minutes, and no idempotency key was given:** `an identical payment went out in the last 10 minutes: ... Pass allowRepeat to pay again, or an idempotencyKey to make retries safe.` `details.hash` is the earlier payment. "Identical" means the same policy, recipient, amount and memo. The check stops a retry without a key from paying twice. **What to do:** give every agent payment an `idempotencyKey`, such as an invoice or order id. If you really mean a second identical payment, pass `allowRepeat: true`. ### http **A Remesso endpoint failed:** it answered with a status other than those above, usually a `5xx`, or couldn't be reached at all (`couldn't reach ...`). `details.status` and `details.body` hold its answer when there was one. One case is different: `created, but 0x... has no ScheduleCreated log: read the receipt yourself` means the transaction succeeded and the SDK couldn't read the new id from it. `details.hash` is the transaction. **What to do:** retry a read later: these are usually brief. Don't repeat a write that says `created, but`: the schedule or policy exists, so read its id from the receipt. ### not_found **It doesn't exist:** - `there is no schedule 99 on 0x3C357af3300f383Ea49B6D6ED8e084199a92bEd8`; - `there is no policy 42`; - an endpoint answered `404`: `no such request`, or `no subscription for this address and url`. **What to do:** check the id, and for webhooks the exact URL you subscribed. ### timeout **The SDK waited, and what it was waiting for didn't happen in time.** Nothing failed. - `events.waitFor`, after `timeoutMs` (20 minutes by default): `no matching payment event in time`. - `requests.waitUntilPaid`, after `timeoutMs` (20 minutes by default): `request ... isn't paid yet (accepted)`. - Either one when its `signal` aborts: `stopped`. An agent payment whose receipt hasn't arrived within `waitMs` doesn't throw. It returns `status: "pending"`, and a call with the same idempotency key later reports the outcome. **What to do:** wait longer, or check again later. A schedule created outside the Remesso app is picked up within about 5 minutes. A payment appears as an event within a minute or two, and is final 25 to 30 minutes after that. --- # Tokens and limits > The tokens a schedule can pay in, their decimals and addresses, the executor's per-payment limits, and which tokens can pay gas. Source: https://docs.remesso.xyz/docs/reference/tokens-and-limits These are the tokens Remesso's executor accepts for new schedules, with the bounds it applies to each payment. The table is generated from the live deployment. | Token | Currency | Decimals | Per payment, before the fee | Pays gas | Address | |---|---|---|---|---|---| | **USDT** | USD | 6 | 1 to 1,000 | Yes | [`0x48065fbBE25f71C9282ddf5e1cD6D6A887483D5e`](https://celoscan.io/address/0x48065fbBE25f71C9282ddf5e1cD6D6A887483D5e) | | **USDC** | USD | 6 | 1 to 1,000 | Yes | [`0xcebA9300f2b948710d2653dD7B07f33A8B32118C`](https://celoscan.io/address/0xcebA9300f2b948710d2653dD7B07f33A8B32118C) | | **USDm** | USD | 18 | 1 to 1,000 | Yes | [`0x765DE816845861e75A25fCA122bb6898B8B1282a`](https://celoscan.io/address/0x765DE816845861e75A25fCA122bb6898B8B1282a) | | **USA₮** | USD | 6 | 1 to 1,000 | Yes | [`0xD2ab3C9A02DBBAB236BfEC45D1d755DF4267F771`](https://celoscan.io/address/0xD2ab3C9A02DBBAB236BfEC45D1d755DF4267F771) | | **wARS** | ARS | 18 | 1,500 to 1,500,000 | No | [`0x0DC4F92879B7670e5f4e4e6e3c801D229129D90D`](https://celoscan.io/address/0x0DC4F92879B7670e5f4e4e6e3c801D229129D90D) | | **wBRL** | BRL | 18 | 5 to 5,000 | No | [`0xD76f5Faf6888e24D9F04Bf92a0c8B921FE4390e0`](https://celoscan.io/address/0xD76f5Faf6888e24D9F04Bf92a0c8B921FE4390e0) | | **wCOP** | COP | 18 | 3,100 to 3,100,000 | No | [`0x8a1D45e102e886510e891d2Ec656a708991e2D76`](https://celoscan.io/address/0x8a1D45e102e886510e891d2Ec656a708991e2D76) | | **wMXN** | MXN | 18 | 18 to 18,000 | No | [`0x337E7456B420bD3481e7FA61fA9850343d610d34`](https://celoscan.io/address/0x337E7456B420bD3481e7FA61fA9850343d610d34) | | **wCLP** | CLP | 18 | 950 to 950,000 | No | [`0x61D450a098b6a7f69fC4b98CE68198fe59768651`](https://celoscan.io/address/0x61D450a098b6a7f69fC4b98CE68198fe59768651) | | **wPEN** | PEN | 18 | 3 to 3,000 | No | [`0x4F34c8b3b5FB6D98Da888F0feA543d4d9C9F2eBE`](https://celoscan.io/address/0x4F34c8b3b5FB6D98Da888F0feA543d4d9C9F2eBE) | | **cNGN** | NGN | 6 | 1,300 to 1,300,000 | No | [`0xF6829D7393dAe24509eb1E52eE8e572e2E271a4f`](https://celoscan.io/address/0xF6829D7393dAe24509eb1E52eE8e572e2E271a4f) | ## Reading the table - **Per payment, before the fee.** The executor sends each payment only if its gross amount is between the equivalent of 1 and 1,000 dollars. Local currencies are converted with rough rates rounded down, so the bounds only ever err on the strict side. A schedule outside them is valid on-chain but never runs, so the SDK refuses it before you sign. `runLimits(token)` returns the same bounds in base units. - **Decimals differ.** USDT, USDC, USA₮ and cNGN have 6 decimals; USDm and Ripio's wARS, wBRL, wCOP, wMXN, wCLP and wPEN have 18. Pass amounts as decimal strings and let `toUnits` convert them. - **Pays gas.** A wallet without CELO can pay transaction fees in these tokens, through Celo's fee abstraction. The SDK does this by itself when the account holds too little CELO and some of the token it is sending. See [fees, limits and gas](https://docs.remesso.xyz/docs/guides/fees-and-gas). - **Payments are in the same token.** A schedule funded in a token pays out in that token: there is no conversion. ## Symbols and aliases `token()` and `findToken()` match symbols in any case and accept common spellings: | You write | You get | |---|---| | `USAT`, `usat` | USA₮ | | `USD₮`, `USDT0` | USDT | | `cUSD` | USDm (formerly cUSD) | | an address | the token at that address | Match tokens by address when you can. On Celo, USDT, USDC and USA₮ each have an 18-decimal fee-currency adapter that reports the same `symbol()` as the real token, so a symbol read from a contract can mislead. ## Which tokens a recipient can see What a recipient's wallet displays matters as much as what the contract accepts. - **MiniPay** shows only USDT, USDC, USDm and USA₮. A MiniPay user paid in cNGN or a Ripio token receives the tokens but can't see them in the app. - **Local-currency tokens** suit recipients with a wallet that shows them, or who cash out through a ramp: Ripio's for its six local currencies, and Textile's for naira from USDT. Remesso links to these ramps; the recipient uses them with their own wallet. ## Changes New schedules can use a token only once the contract allows it, and the SDK reads that from the contract before creating a schedule. When the list changes, this page and the SDK's generated deployment change with it. --- # Contracts and endpoints > The chain, contracts and HTTP endpoints remesso-sdk talks to, and how to check each one yourself. Source: https://docs.remesso.xyz/docs/reference/contracts Everything the SDK talks to on Celo mainnet. These tables are generated from the live deployment, the same source the SDK's own `deployment` export is built from. ## Chain | | | |---|---| | **Network** | Celo mainnet | | **Chain id** | `42220` (`eip155:42220`) | | **Default RPC** | `https://forno.celo.org` | The SDK checks that its RPC answers this chain id before it signs anything, and refuses with `wrong_chain` otherwise. ## Contracts | Contract | Address | What it does | |---|---|---| | **RemessoExecutorV5** | [`0x3C357af3300f383Ea49B6D6ED8e084199a92bEd8`](https://celoscan.io/address/0x3C357af3300f383Ea49B6D6ED8e084199a92bEd8) | Holds every schedule. Commission 0.25% (25 bps), pinned per schedule when it is signed. Deployed at block 80,268,721. | | **RemessoAgentAllowance** | [`0xA6351fAeEb097698289586C4D6188530300DB209`](https://celoscan.io/address/0xA6351fAeEb097698289586C4D6188530300DB209) | Agent spending policies: a cap per payment and per day, an expiry, an optional recipient list. No owner, no admin. Deployed at block 80,467,202. | | **ERC-8004 identity registry** | [`0x8004A169FB4a3325136EB29fA0ceB6D2e539a432`](https://celoscan.io/address/0x8004A169FB4a3325136EB29fA0ceB6D2e539a432) | Where agents are registered; the SDK reads an agent's wallet from it. | | **Executor key** | [`0xA351d65c15b8a342B6ECbC7f2B9eFE62B17cdb21`](https://celoscan.io/address/0xA351d65c15b8a342B6ECbC7f2B9eFE62B17cdb21) | Remesso's hot key, which sends each payment when it is due. It pays gas and nothing else. | | **Trigger sentinel** | `0x0000000000000000000000000000000000000001` | Grant this as a schedule's trigger to let Remesso's executor, whichever key it is at the time, bring a payment forward. | Both contracts are immutable and verified on Celoscan. - **`RemessoExecutorV5`** holds schedules, not funds: each payment moves straight from the payer's wallet, the amount to the recipient and the fee to Remesso's treasury. Its owner can pause it, change the executor key, allow tokens for new schedules, set the swap pool's fee tier (for the swap rails, which are switched off) and rescue tokens sent to it by mistake. Nothing it can do changes a signed schedule's recipient, amount or cadence. - **`RemessoAgentAllowance`** holds agent policies. It has no owner and no admin. ## Endpoints | Endpoint | What it does | |---|---| | `https://engaboljiqudghvzmebq.supabase.co/functions/v1/agent` | The capability document: identity, chain, limits, prices and refusals. Free. | | `https://engaboljiqudghvzmebq.supabase.co/functions/v1/request-payment` | Payment requests: create, status, list, answer, decline. Free. | | `https://engaboljiqudghvzmebq.supabase.co/functions/v1/payment-events` | Every payment to or from an address, as Remesso's indexer reads it from the chain. Free. | | `https://engaboljiqudghvzmebq.supabase.co/functions/v1/webhooks` | Subscribe an address's payments to your endpoint, signed. Free. | | `https://engaboljiqudghvzmebq.supabase.co/functions/v1/trigger-run` | Bring one payment of a schedule forward, paid over x402. | | `https://engaboljiqudghvzmebq.supabase.co/functions/v1/rate-service` | USDT/NGN and USDC/NGN rates, paid over x402. | | `https://engaboljiqudghvzmebq.supabase.co/functions/v1/latam-rate` | USD to wARS and wBRL rates, paid over x402. | | `https://engaboljiqudghvzmebq.supabase.co/functions/v1/latam-route` | A route for delivering pesos or reais, paid over x402. | The SDK calls `request-payment`, `payment-events` and `webhooks` for you. They are plain HTTP, documented on [the HTTP API page](https://docs.remesso.xyz/docs/reference/http-api) for other languages. The paid services speak x402, so any x402 client can buy them. ## Check it yourself The contract is the authority, and anyone can read it. With Foundry's `cast`: ```bash cast call 0x3C357af3300f383Ea49B6D6ED8e084199a92bEd8 \ "getSchedule(uint256)" 4 --rpc-url https://forno.celo.org ``` Or with the SDK, which reads the same contract: ```ts const s = await reader.schedules.get(4); console.log(s.to, s.amountIn, s.intervalSeconds, s.runsExecuted, "of", s.maxRuns); ``` If an endpoint and the contract ever disagree, the contract is right. The capability document lists every endpoint, the contracts, the per-payment limits and Remesso's registrations. It is free and needs no key: ```bash curl https://engaboljiqudghvzmebq.supabase.co/functions/v1/agent ``` --- # HTTP API > Remesso's public endpoints over plain HTTP, for any language: payment requests, payment events, webhooks and the capability document. Source: https://docs.remesso.xyz/docs/reference/http-api This page documents the HTTP endpoints remesso-sdk calls, for developers working in another language or without the SDK. ## Conventions All four endpoints live under one base URL, need no API key and allow cross-origin calls: ```text https://engaboljiqudghvzmebq.supabase.co/functions/v1 ``` - Bodies are JSON. Send `content-type: application/json`. - An error answers with a status and `{ "error": "...", "detail": "..." }`, where `detail` is there only sometimes. - Amounts that moved or were signed (payment events, a request's `signed` terms and its payments) are base units as strings: an 18-decimal amount doesn't survive as a JSON number. Amounts you send to `request-payment` are decimals in the token's own units, such as `"2.50"`, and a request's `requested` terms echo them that way. - Addresses in responses are lowercase. - A `GET` on `request-payment` or `webhooks` returns a description of what it accepts. The SDK maps each status to an error code: see [errors](https://docs.remesso.xyz/docs/reference/errors#how-endpoint-answers-map-to-codes). ## request-payment `POST /request-payment` asks a wallet to pay you, and reports where that request stands. The `action` field picks what it does. Without one, it creates a request. A request can't create a schedule or move a token. It shows up in the payer's Remesso app and gives you a link that opens their payment form filled in, and nothing is authorised until they sign in their own wallet. See [payment requests](https://docs.remesso.xyz/docs/guides/payment-requests). ### Create a request ```bash curl -X POST https://engaboljiqudghvzmebq.supabase.co/functions/v1/request-payment \ -H 'content-type: application/json' \ -d '{"payer":"0xTheirWallet","to":"0xYourAddress","amount":"50","token":"USDT","every":"month","runs":12,"from":"Ada studio","note":"monthly retainer"}' ``` | Field | Required | Meaning | |---|---|---| | `payer` | yes | The wallet you're asking to pay. | | `to` | yes | The address you want paid. | | `amount` | no | What you want to receive per payment, as a decimal. The payer's form adds Remesso's fee on top. | | `token` | no | `USDT`, `USDC`, `USDm`, `USA₮`, `wARS`, `wBRL`, `wCOP`, `wMXN`, `wCLP`, `wPEN` or `cNGN`. The `GET` description lists the current set. | | `every` | no | `week`, `fortnight`, `month` or `quarter`. `weekly`, `fortnightly`, `monthly` and `quarterly` also work. | | `runs` | no | How many payments, 1 to 9999. | | `from` | no | Who is asking, up to 40 characters. | | `note` | no | Why, up to 120 characters. | It answers `201`: ```json { "ok": true, "id": "50a5be6a-079f-43fe-9804-0925179fd5f0", "link": "https://…/schedules/new?to=0x…&from=Ada+studio&amount=50&token=USDT&every=month&runs=12¬e=monthly+retainer&req=50a5be6a-079f-43fe-9804-0925179fd5f0", "status": "pending", "detail": "the payer decides. Nothing is authorised until they sign." } ``` Send the `link` to the payer however you already reach them. It carries `req=`, so the schedule they sign from it is reported against your request. A payer can have at most 10 live requests: pending, and under 30 days old. An eleventh is refused with `429`, saying how many are waiting. ### List a payer's requests ```bash curl -X POST https://engaboljiqudghvzmebq.supabase.co/functions/v1/request-payment \ -H 'content-type: application/json' \ -d '{"action":"list","payer":"0xTheirWallet"}' ``` It returns up to 20 live requests addressed to the payer, newest first: ```json { "requests": [ { "id": "50a5be6a-079f-43fe-9804-0925179fd5f0", "reference": "50a5be6a079f43fe98040925179fd5f0", "to": "0x…", "amount": "50", "token": "USDT", "intervalSeconds": 2592000, "runs": 12, "from": "Ada studio", "note": "monthly retainer", "status": "pending", "createdAt": "2026-10-25T14:02:11.513421+00:00" } ] } ``` `reference` is the id without its dashes, 32 characters. An agent paying under a policy puts it in the payment's memo, and the request is then answered by that payment. ### A request's status ```bash curl -X POST https://engaboljiqudghvzmebq.supabase.co/functions/v1/request-payment \ -H 'content-type: application/json' \ -d '{"action":"status","id":"50a5be6a-079f-43fe-9804-0925179fd5f0"}' ``` ```json { "id": "50a5be6a-079f-43fe-9804-0925179fd5f0", "reference": "50a5be6a079f43fe98040925179fd5f0", "status": "accepted", "payer": "0x…", "requested": { "to": "0x…", "amount": "50", "token": "USDT", "intervalSeconds": 2592000, "runs": 12 }, "signed": { "amount": "50000000", "token": "0x48065fbbe25f71c9282ddf5e1cd6d6a887483d5e", "intervalSeconds": 2592000, "runs": 12, "changed": [] }, "payment": { "state": "paid", "schedule": { "contract": "0x3c357af3300f383ea49b6d6ed8e084199a92bed8", "id": "5" }, "txs": [{ "txHash": "0x…", "amount": "50000000", "final": true, "blockTime": "2026-10-25T14:20:44+00:00" }], "reason": null } } ``` - `status` is `pending`, `accepted`, `accepted_with_changes`, `declined` or `expired`. A pending request expires after 30 days. - `signed` is what the payer actually signed, with `amount` in base units after the fee: what you receive per payment. `changed` lists which of `token`, `amount`, `cadence` and `runs` differ from what you asked. It is `null` until the request is answered. - `payment` is `null` until the request is answered. Then its `state` is `scheduled` (answered, nothing paid yet), `paid` (with the transactions in `txs`) or `failed` (with Remesso's reason). > **Warning** > > Accepted is not paid. Release anything only for a payment with a transaction, ideally once it is `final`, and check that transaction on the chain yourself. ### Answer a request with a schedule The payer, or their agent, reports the schedule that answers a request: ```bash curl -X POST https://engaboljiqudghvzmebq.supabase.co/functions/v1/request-payment \ -H 'content-type: application/json' \ -d '{"action":"answer","id":"50a5be6a-079f-43fe-9804-0925179fd5f0","schedule":"5"}' ``` It answers `{ "ok": true, "id": "...", "status": "accepted", "signed": { ... } }`. `contract` is optional and defaults to the executor. The schedule answers the request only if: - it was created by the wallet the request was sent to; - it pays the address that asked; - it was created after the request; - it answers no other request. Otherwise the answer is refused with `409` and says which rule failed. A schedule only seconds old can be refused with `409`, `that schedule hasn't been seen on-chain yet; try again in a minute`. Try again a few seconds later. Answering again with the schedule that already answers the request returns the first answer, so a retry is safe. ### Decline a request Only the wallet the request was sent to can turn it down for everyone, with an EIP-712 signature over this typed data: ```json { "domain": { "name": "Remesso", "version": "1", "chainId": 42220 }, "primaryType": "PaymentRequestAction", "types": { "PaymentRequestAction": [ { "name": "request", "type": "string" }, { "name": "action", "type": "string" }, { "name": "expiry", "type": "uint64" } ] }, "message": { "request": "50a5be6a-079f-43fe-9804-0925179fd5f0", "action": "decline", "expiry": 1792940000 } } ``` Sign it with any EIP-712 library, or `eth_signTypedData_v4`, then send: ```bash curl -X POST https://engaboljiqudghvzmebq.supabase.co/functions/v1/request-payment \ -H 'content-type: application/json' \ -d '{"action":"decline","id":"50a5be6a-079f-43fe-9804-0925179fd5f0","expiry":1792940000,"signature":"0x…"}' ``` - `expiry` is Unix seconds, in the future and at most 24 hours ahead. A signature is worthless after it. - An agent can sign instead of the payer, if it holds a live policy from the payer. Add `"agent": "0x…"` and `"policy_id": "12"`, and sign with the agent's key. - A smart account signs through ERC-1271. - It answers `{ "ok": true, "id": "...", "status": "declined" }`. Declining twice gives the same answer. A request that is already accepted answers `409`. ## payment-events `GET /payment-events` lists every payment to or from an address, as Remesso's indexer reads it from the chain. See [payment events](https://docs.remesso.xyz/docs/guides/payment-events) for what each event means. ```bash curl 'https://engaboljiqudghvzmebq.supabase.co/functions/v1/payment-events?address=0xYourAddress&role=to&kind=run,policy_payment&after=0&limit=100' ``` | Parameter | Default | Meaning | |---|---|---| | `address` | required | The address to read. | | `role` | `any` | `to`, `from`, `agent` or `any`: which side of the event the address is on. | | `kind` | all kinds | A comma-separated list of `run`, `schedule_created`, `policy_payment`, `policy_created`, `policy_revoked`. | | `after` | `0` | The `next` of the previous page. | | `limit` | `100` | 1 to 500. | ```json { "events": [ { "seq": 25, "chainId": 42220, "contract": "0x3c357af3300f383ea49b6d6ed8e084199a92bed8", "kind": "run", "refId": "2", "from": "0x…", "to": "0x…", "agent": null, "token": "0x48065fbbe25f71c9282ddf5e1cd6d6a887483d5e", "amount": "1000000", "amountIn": "1002507", "memo": null, "memoText": null, "runNumber": 1, "early": false, "details": { "nextRunAt": "1793542263" }, "txHash": "0x…", "logIndex": 8, "blockNumber": 81036705, "blockHash": "0x…", "blockTime": "2026-10-25T14:11:03+00:00", "final": true, "removed": false } ], "next": 25 } ``` Pass `next` back as `after` to read the next page. A page shorter than `limit` means you have caught up. `seq` moves whenever an event changes, so an event you saw before it was final comes round again once it is, and again if a reorg removes it. ## webhooks `POST /webhooks` manages the webhook subscriptions of a receiving address: `subscribe`, `rotate`, `revoke` and `replay`. The [webhooks guide](https://docs.remesso.xyz/docs/guides/webhooks) explains the flow, and [webhook payloads](https://docs.remesso.xyz/docs/reference/webhook-payloads) the messages. Every request is signed by the receiving address, with EIP-712, over this typed data: ```json { "domain": { "name": "Remesso", "version": "1", "chainId": 42220 }, "primaryType": "WebhookAction", "types": { "WebhookAction": [ { "name": "recipient", "type": "address" }, { "name": "url", "type": "string" }, { "name": "chainId", "type": "uint256" }, { "name": "action", "type": "string" }, { "name": "nonce", "type": "string" }, { "name": "expiry", "type": "uint64" } ] }, "message": { "recipient": "0x…", "url": "https://example.com/remesso", "chainId": 42220, "action": "subscribe", "nonce": "f3b1c9a07d2e4e18", "expiry": 1792938121 } } ``` Then send the same fields with the signature: ```bash curl -X POST https://engaboljiqudghvzmebq.supabase.co/functions/v1/webhooks \ -H 'content-type: application/json' \ -d '{"action":"subscribe","recipient":"0x…","url":"https://example.com/remesso","chainId":42220,"nonce":"f3b1c9a07d2e4e18","expiry":1792938121,"signature":"0x…"}' ``` | Field | Meaning | |---|---| | `action` | `subscribe`, `rotate`, `revoke` or `replay`. | | `recipient` | The receiving address. Its key, or its ERC-1271 smart account, signs. | | `url` | Your HTTPS endpoint, at most 512 characters, with no credentials and no fragment. | | `chainId` | `42220`. | | `nonce` | 8 to 128 letters, digits, `-` or `_`, used once. | | `expiry` | Unix seconds, in the future and at most an hour ahead. | | `signature` | The EIP-712 signature. | | `days` | `replay` only: how far back, 1 to 7. It isn't signed. | What each action answers: | Action | Success | Refusals | |---|---|---| | `subscribe` | `201`, `{ ok, subscription, recipient, url, state: "active", secret }`. The secret is shown this once. | `400` if the endpoint didn't answer the [challenge](https://docs.remesso.xyz/docs/reference/webhook-payloads#the-challenge), with why. `409` if the URL is already subscribed for the address. `429` at 5 subscriptions. | | `rotate` | `200`, `{ ok, secret, previousSecretValidUntil }`. The old secret keeps signing until then. | `404` if there is no active subscription for the address and URL. | | `revoke` | `200`, `{ ok, state: "revoked" }` | `404` if there is no subscription for the address and URL. | | `replay` | `200`, `{ ok, requeued, days }` | | Any action can also answer `400` for a malformed field, `401` if the signature isn't from `recipient`, or `409` if the nonce was already used: sign again with a new one. ## agent `GET /agent` returns Remesso's capability document: what it is, who runs it, and everything a caller needs to use it. Machines can read it, and it is cached for 5 minutes. ```bash curl https://engaboljiqudghvzmebq.supabase.co/functions/v1/agent ``` Its sections: | Key | What it holds | |---|---| | `identity` | Remesso's ERC-8004 and Self Agent ID registrations, so a caller can check who it deals with. | | `chain` | The network, the executor contract, the trigger sentinel a payer grants, the key that sends payments, and Remesso's attribution code. | | `limits` | `perRun`: the smallest and largest payment the executor sends, per token, in decimals and base units. | | `receiving` | What being paid needs (an address, nothing else), the tokens accepted, and the ways to hand your address to a payer and to know you were paid. | | `developers` | The SDK, its documentation, and how [builder codes](https://docs.remesso.xyz/docs/guides/builder-codes) work. | | `services` | Each endpoint, with its price and what it refuses. | | `guarantees` | What a payer's schedule can never be made to do, whoever asks. | | `verification` | The contract and the read calls that let anyone check a schedule themselves. | ## Paid services Remesso also sells a few services per call over [x402](https://x402.celo.org/) on Celo, with no account. Each has one price in dollars, payable in USDC, USDT or USA₮, or in Ripio's wARS, wBRL, wCOP, wMXN, wCLP or wPEN at the same price in local currency: - `trigger-run` brings one payment of a schedule forward; - `rate-service` gives live USDT/NGN and USDC/NGN rates; - `latam-rate` gives USD to wARS and wBRL rates; - `latam-route` gives a route for delivering pesos, reais or soles. A `POST` without a payment returns the price in a `402` response. The capability document lists each one under `services`, and [AGENTS.md](https://bitbucket.org/crackedstudios/remesso/src/main/docs/AGENTS.md) explains how to pay for them. --- # FAQ and troubleshooting > Answers to the questions developers ask most, and what to check when a payment doesn't arrive. Source: https://docs.remesso.xyz/docs/resources/faq Short answers to common questions, then what to check when something doesn't happen. ## General ### Is there a testnet? No. Remesso runs on Celo mainnet only. Test with small amounts: the smallest scheduled payment is 1 USDT, the fee comes out of it, and gas costs a fraction of a cent. Paying a second address of your own gets 0.9975 USDT back. ### Does the recipient need gas, an account or any software? No. Receiving needs only an address. The payer pays to create a schedule, and Remesso's executor pays the gas for each payment. You need the recipient's key only to sign as that address: to subscribe to webhooks, or to answer or decline a request. ### Does Remesso hold my funds? No. The money stays in the payer's wallet until each payment, and each payment goes straight to the recipient. The payer's token approval and the schedule's terms bound what the contract can move. ### What does it cost? 0.25% of each scheduled payment, taken from the payment. Agent payments and receiving are free. Gas for creating a schedule is a fraction of a cent. See [fees, limits and gas](https://docs.remesso.xyz/docs/guides/fees-and-gas). ### Can a payer pay in one currency and the recipient receive another? No. A schedule pays out in the token it is funded in. For a recipient who wants local currency in a bank, they cash out the tokens they receive through a ramp such as Ripio's or Textile's, with their own wallet. ### Can I use the SDK in a browser? Yes, with the user's wallet: pass a viem wallet client as `walletClient`. The user approves each transaction. Agent payments need a local account, so they belong on a server. ## Schedules ### Can I change a schedule's amount or recipient? No. What the payer signed is fixed. Cancel the schedule and create a new one with the new terms. ### My schedule hasn't paid yet. Why? Check these in order: 1. **Time.** A schedule created with the SDK is picked up within about 5 minutes, and the payment goes out within a minute of that. A trigger of `"remesso"` lets a payment be brought forward instead. 2. **Funding and approval.** `schedules.status(id)` tells you whether a payment is `due`, `funded` and `approved`. If the payer's balance or approval is below one payment, the executor waits until it isn't. 3. **The schedule itself.** `schedules.get(id)` shows whether it is `active`, `cancelled` or expired, and how many payments have run. 4. **The limits.** A payment outside the executor's per-payment limits never runs. The SDK refuses such a schedule, but one created without the SDK can still exist on-chain. ### What happens if the payer runs out of funds? The payment waits. The executor tries again every minute while the payment is due, and sends it once the balance and approval cover it. Missed payments aren't sent in a burst afterwards: the next one is due one interval after the payment actually went out. ### Why did `schedules.create` refuse my schedule? Its error message says which rule. The usual ones: an amount outside the [per-payment limits](https://docs.remesso.xyz/docs/reference/tokens-and-limits), more payments than fit in a year at that cadence, a token not taken for new schedules, a first payment in the past, or a recipient that is your own wallet. See [errors](https://docs.remesso.xyz/docs/reference/errors#refused). ## Payments and events ### When is a payment final? When its block is at or below Celo's finalized block, about 25 to 30 minutes after it lands. Payment events carry a `final` flag, and webhooks send `payment.finalized`. For large amounts, act on finality. ### I got "hasn't been seen on-chain yet". What does it mean? Remesso's backend couldn't see a schedule you had just created. Celo's public RPC is several nodes, and a read straight after a transaction can come from one that is a block behind. The SDK retries for up to 90 seconds. If you call the HTTP API yourself, retry after a few seconds. ### A balance or status read straight after a transaction looks stale. The same cause: a lagging node. Read again a few seconds later, or read at the block of the transaction's receipt. ### An agent payment returned `pending`. What now? Call `pay` again with the same idempotency key. It reports the first attempt's outcome and never pays twice. Never retry with a new key. ## Webhooks ### I receive the same delivery twice. That is expected: delivery is at least once. Deduplicate on the `webhook-id` header, the same on every retry. ### My endpoint returns 503. The handler has no signing secret yet. `subscribe` returns it once; pass it to `webhookHandler`, and store it if the server restarts. ### How do I stop deliveries? `webhooks.revoke({ url })`. An address can hold five subscriptions. ## Still stuck? The [errors reference](https://docs.remesso.xyz/docs/reference/errors) lists every error code with what to do. The [source code](https://bitbucket.org/crackedstudios/remesso) and its example agents show complete programs. --- # Security model > What the contracts guarantee, what Remesso operates and can't do, and how to handle keys, approvals and webhooks safely. Source: https://docs.remesso.xyz/docs/resources/security Remesso is built so that the money path depends on contracts, not on trusting Remesso. This page sets out what is enforced, what is operated, and what is left to you. ## What the contracts enforce **Schedules (`RemessoExecutorV5`)** - A schedule's recipient, amount, cadence, number of payments and expiry are fixed when the payer signs. No one can change them, including Remesso and the contract's owner. - Each payment moves only when it is due, and never more than the amount signed. - A payment can come early only if the payer granted a trigger. A trigger brings the next payment forward; it never adds one. - The payer can pause, resume or cancel at any time, and revoking the token approval stops every future payment. **Agent policies (`RemessoAgentAllowance`)** - An agent can pay only within its policy: one token, a cap per payment and per day, an expiry and, optionally, a list of allowed recipients. - The contract has no owner and no admin. Only the person who created a policy can revoke it or change its recipient list. - The person's token approval to the contract caps what all their policies for that token can spend together. ## What Remesso operates - **The executor**, a backend service with a hot key that sends payments when they are due. The key pays gas and nothing else: the contract decides what each payment is. - **The indexer**, which reads the contracts and records payment events. - **Webhooks**, signed deliveries of those events. - **Payment requests**, which are notes asking to be paid. No request can authorise or move money. If any of these stopped, scheduled payments would wait and events would stop arriving, but no payment could go to the wrong place or for the wrong amount. ## What you are responsible for ### Keys - Keep private keys out of code and repositories. Load them from the environment or a secrets manager. - Use a separate key for each role: the wallet that pays on a schedule, an agent's key, and a receiving address. - An agent's key should hold only CELO for gas. It spends the person's tokens through its policy, not its own balance. ### Approvals - `schedules.create` approves the schedule's total and `policies.create` approves the budget, each on top of what is already approved. Keep approvals close to what you intend to pay. - To stop everything at once, revoke the token approval for the contract. ### Webhooks - Verify every delivery's signature. `webhookHandler` does it for you; if you handle requests yourself, use `verifyWebhook` or `parseWebhook` on the raw body, before any JSON parser. - Deduplicate on `webhook-id`: a delivery can arrive more than once. - For large amounts, act on `payment.finalized`, not `payment.received`, and handle `payment.removed`. - Treat the signing secret like a password. Rotate it with `webhooks.rotate` if it may have leaked. ### Agent payments - Give every payment an idempotency key, so a retry after a timeout can't pay twice. - Keep payment records with `fileStore` or your own store, not in memory, for any agent that restarts. - Set the tightest policy that works: a low cap per payment and per day, a short expiry and a recipient list when you know who will be paid. ## Reporting a vulnerability Report security issues privately to the Remesso maintainers, through the [repository](https://bitbucket.org/crackedstudios/remesso), rather than in a public issue. --- # Changelog > What changed in each release of remesso-sdk. Source: https://docs.remesso.xyz/docs/resources/changelog Releases of [`remesso-sdk`](https://www.npmjs.com/package/remesso-sdk) on npm. ## 0.3.1 Released 28 October 2026. - **Three more currencies.** Ripio's wMXN, wCLP and wPEN (Mexican peso, Chilean peso, Peruvian sol) can be scheduled and paid like the other local-currency tokens: 18 to 18,000 wMXN, 950 to 950,000 wCLP and 3 to 3,000 wPEN per payment. - **wCOP's per-payment limits are 3,100 to 3.1 million**, from 3,200 to 3.2 million. The Colombian peso gained on the dollar, and the limits stay at or under $1,000. ## 0.3.0 Released 26 October 2026, after 0.3.0-rc.1 the same day. - **Agent-to-agent payments.** `remittances.send` pays one exact amount from this agent's own wallet or from a spending policy, optionally answering a one-off request. A retry with the same key continues the same payment. See [Build an agent-to-agent app](https://docs.remesso.xyz/docs/guides/agent-to-agent). - **`agents.pay`** pays an agent registered in ERC-8004, at the wallet the registry names. - **`receipts.verify`** checks a payment on the chain, without a key: exact payer, recipient, token and amount, by Remesso's contracts, optionally finalized. - **Signed invoices.** `invoices.create`, `sign`, `verify`, `pay` and `verifyPayment`: the recipient signs exact terms, the payer checks them, and only that invoice's payment settles it. - **Quotes and deadlines.** `remittances.quote` and `prepare`; `startBy` bounds when a payment may start, and `validUntil` when it must land. - **Recovery.** `remittances.get`, `listPending`, `reconcile`, `wait` and `recoverPending` follow a payment and continue it after a crash. They never start one. - **Workers.** `remesso-sdk/postgres` shares journals, a signing lock, jobs and a fulfilment inbox between processes. - **Local development.** `npx remesso-sdk dev` runs both funding models against a local fork of Celo. - **Managed signers and agent tools.** `remesso-sdk/signers` and `remesso-sdk/tools`. - **Safer journals.** Before signing anything, the SDK sends again any saved transaction the chain hasn't mined, or cancels one past its deadline at its nonce, so one lost broadcast can't hold up the transactions after it. A failed broadcast is retried at once. - **Since 0.3.0-rc.1:** a read pinned to a block is tried again while the RPC's node catches up to that block, instead of failing with "block is out of range". An RPC that spreads calls across nodes, as `forno.celo.org` does, can answer the latest block from one node and the next call from another a block behind. ## 0.2.0 Released 25 October 2026. - **Builder codes.** `createRemesso({ builderCodes })` adds your own ERC-8021 codes after Remesso's on every transaction the client sends. Remesso's executor then carries them on every payment of the schedules the client creates. See [builder codes](https://docs.remesso.xyz/docs/guides/builder-codes). - **`attributionFor(builderCodes)`** returns the suffix for a transaction you send yourself. - Builder codes are checked by the same rules the executor applies: up to three, Celo's charset, none of Remesso's own. A code the executor wouldn't carry is refused before signing. - Without `builderCodes`, every transaction carries exactly what 0.1.0 wrote. ## 0.1.0 Released 25 October 2026. The first release. - **Schedules:** create (with `amount` or `receive`), read, list, pause, resume, cancel and set a trigger. Amounts outside the executor's per-payment limits are refused before signing. - **Agent payments:** `pay` through a policy, once per idempotency key, with the payment recorded before it is broadcast. Policies can be created, read, found, revoked and given a recipient list. - **Payment requests:** create, read, list, answer, decline with a signature, pay as an agent and wait until paid. - **Payment events:** list, watch and wait for events from Remesso's indexer. - **Webhooks:** subscribe, rotate, revoke and replay, with `webhookHandler`, `verifyWebhook` and `parseWebhook`. - **Agents:** `agents.find` reads an ERC-8004 agent's wallet and payee profile. - **Node helpers:** `fileStore`, `safeFetchText` and `toNodeListener` from `remesso-sdk/node`. - When the account holds too little CELO, gas is paid in the transaction's own token (USDT, USDC, USDm or USA₮) through Celo's fee abstraction. - The deployment, ABIs and shared rules are generated from what is live and from the code Remesso's backend runs.