remessConnecting

Remesso for developers

Build with
remesso-sdk.

Schedules a payer signs once, agent payments capped on-chain by their person, payment requests, payment events and signed webhooks. TypeScript, on viem. Celo mainnet only.

The contracts are live and have not been audited by a person.

There is no testnet: everything here moves real money on Celo mainnet. Start with small amounts, a dollar rather than a payroll.

Quickstart: a payment and its webhook

About 15 minutes, most of it waiting. You need Node 20 or later.

  1. 1
    Install, in a new folder. The SDK is ES modules only, and tsx runs TypeScript without a build.
    npm init -y && npm pkg set type=module
    npm install remesso-sdk viem
    npm install -D tsx
  2. 2
    Make two keys, one that pays and one that receives. Save this as keys.ts and run npx tsx keys.ts twice:
    import { generatePrivateKey, privateKeyToAccount } from "viem/accounts";
    
    const key = generatePrivateKey();
    console.log(key, privateKeyToAccount(key).address);

    Send the payer about 1.05 USDT on Celo, and a little CELO for gas if you have it. With no CELO, the SDK pays gas in USDT. The receiver needs nothing. Use keys that hold only what you are willing to lose, and keep them in your shell:

    export PAYER_KEY=0x… RECEIVER_KEY=0x… RECEIVER=0x…
  3. 3
    Run a receiver. In one terminal, open a public HTTPS address to port 8787. A Cloudflare quick tunnel needs no account, and prints a trycloudflare.com URL:
    cloudflared tunnel --url http://localhost:8787

    Then save this as 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<void>((done) => server.listen(8787, done));
  4. 4
    Subscribe. Add these lines to the end of receiver.ts, and run it with the tunnel's URL: PUBLIC_URL=https://….trycloudflare.com npx tsx receiver.ts. Remesso first posts a challenge to the URL; the handler answers it for your address only, and the signing secret comes back once.
    const sub = await receiver.webhooks.subscribe({ url: process.env.PUBLIC_URL! });
    secret = sub.secret; // shown once: save it if the receiver must survive a restart
    console.log("subscribed", sub.subscription, "for", receiver.address);

    A quick tunnel gets a new URL each time it starts, and an address can hold 5 subscriptions: drop old ones with webhooks.revoke({ url }).

  5. 5
    Pay. In a second terminal, save this as pay.ts and run npx tsx pay.ts. One payment of 1 USDT, the smallest the executor sends: it approves the contract and creates the schedule, two transactions.
    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);
  6. 6
    Watch the receiver's terminal. Remesso finds a schedule created outside its app within about 5 minutes, and sends the payment within a minute of that. A minute or two later the handler gets payment.received with final: false: 0.9975 USDT, after the 0.25% fee. payment.finalized follows when Celo finalizes the block, 25 to 30 minutes later. A failed delivery is retried with the same webhook-id for 24 hours, so dedupe on it.

    No public endpoint? Pull the same events instead:

    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);
    }

Getting paid by request

Ask a wallet to pay you. The request lands in their Remesso inbox, and the link opens their form filled in. It carries req=, so the schedule they sign is reported against your request. Nothing is authorised until they sign in their own wallet.

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.link); // send it however you already reach them

const paid = await receiver.requests.waitUntilPaid(req.id);
console.log(paid.payment?.txs);

A requested amount is what you receive: their form signs grossFor of it, the amount plus the fee. Accepted is not paid; waitUntilPaid waits for a payment with a transaction. A payer using the SDK answers with the request's own terms:

await payer.schedules.create({ request: req.id }); // the request's terms, the fee on top

Agent payments

A person gives their agent's key a policy: one token, a cap per payment and per day, an expiry, and optionally a list of the only recipients allowed. They set it at remesso.xyz/agent-wallet, or in code. The agent then pays from the person's wallet, and the contract refuses anything outside the policy. No fee: what the agent pays is what arrives.

// The person, once:
await person.policies.create({
  agent: "0xAgentAddress",
  token: "USDT",
  perPayment: "5",
  perDay: "20",
  days: 30,
  budget: "100", // added to their USDT approval for the allowance contract
});
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", idempotencyKey: "order-1042" });
// r.status is "confirmed", or "pending": call again with the same key, never a new one

await agent.requests.pay(requestId); // its reference goes in the memo, so the requester sees it
  • A retry with the same idempotency key pays once, even when the first attempt timed out after it was sent.
  • fileStore keeps that record on disk, so it holds across restarts.
  • Without a key, an identical payment within ten minutes is refused unless you pass allowRepeat.
  • The agent's key pays its own gas: send it a little CELO.

Builder codes

Celo credits on-chain volume to the app that caused it, by the ERC-8021 code on each transaction (attribution tags). Pass your own, and every transaction this client sends carries it after Remesso's. Remesso's executor then carries it on every run of each schedule the client creates, so the volume a schedule moves counts for you too, for as long as it runs.

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 });

// A minute later, what Remesso read from the transaction that created it:
const { events } = await payer.events.list({ role: "from", kinds: ["schedule_created"] });
console.log(events.find((e) => e.refId === s.id.toString())?.details?.builderCodes); // ["your_code"]
  • Up to three codes, each 1 to 32 of a-z, 0-9 and _. The SDK refuses anything else before signing, by the rules the executor applies.
  • Without the SDK, tag your own createSchedule transaction with toDataSuffix or withAttribution from @celo/attribution-tags. Only Schema 0, the one they write, is read. A schedule created through an ERC-4337 bundle isn't read yet: the codes come from the outer transaction.
  • The suffix sits after the calldata, where the EVM ignores it. A code changes who is credited, never what a payment does.

What it costs

  • 0.25% of each scheduled payment, taken from it and rounded up: a 1 USDT payment delivers 0.9975. Policy payments carry no fee. Receiving costs nothing at all.
  • Per payment, the executor sends the equivalent of 1 to 1,000 dollars: 1 to 1,000 USDT, USDC, USA₮ or USDm; 1,500 to 1.5 million wARS; 5 to 5,000 wBRL; 3,200 to 3.2 million wCOP; 1,300 to 1.3 million cNGN. Outside that a schedule is valid on-chain and never runs, so the SDK refuses it before signing. runLimits(token) gives the bounds, and /agent publishes them under limits.perRun.
  • Gas, measured on our own transactions, not promised: creating a schedule is about 224,000 gas (about $0.004) plus an approval, paid by the payer. Each payment is about 91,875 gas, paid by Remesso's executor. A policy payment is about 85,000 gas, paid by the agent. That was at 202 gwei with CELO near $0.09; the numbers move with both.

Reference

Remesso never holds your keys. The contract is the authority on what moves: if an endpoint and the contract disagree, the contract is right. The source is on Bitbucket.