Skip to content
playriglabsPublic

Latest commit

 

History

421 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Mayarin

The merchant names one price in their own currency. The payer brings whatever they hold. Both numbers are exact.

Live on testnet

A programmable clearing layer for humans, applications and autonomous agents. Merchants price in their local currency and settle in a stablecoin; the payer brings any supported asset. Every settlement below is a transaction you can open on a block explorer.


The problem

A coffee roaster in Jakarta prices a bag at IDR 50,000 and wants to sell beyond Indonesia. A customer in Berlin wants to pay in EURC. An agent wants to pay for one API call and has never heard of an account.

To sell to either of them today, the merchant has to become an infrastructure team:

   accept crypto      →    pick a chain, hold a wallet, fund it with gas
                      →    watch a rate that moves while the customer decides
                      →    swap, hope the pool is deep enough, eat the slippage
                      →    reconcile a balance nobody can explain

The solution

One clearing layer between the two. The merchant's number is locked first, and everything else is derived from it.

   merchant prices       IDR 50,000, in their own currency
   payer holds           ETH, EURC, USDC, or another configured asset
   Mayarin locks         the merchant's stablecoin settlement minimum
   merchant receives     exactly the configured settlement amount

The merchant never sees a chain, a gas token, or a slippage setting. An autonomous agent is a payer class, not a product line — it reaches the same clearing engine, ledger and merchant as a person at a checkout, and is given one thing to sign.

How it works

   1  INTENT     the merchant states a price and a settlement asset
   2  LOCK       the quote engine freezes the settlement minimum
   3  PAY        contract call, deposit transfer, or one signed x402 authorization
   4  CONFIRM    the receipt is read back off the chain and matched
   5  RECORD     clearing transition + balanced ledger postings, one DB transaction

                                ↓

      merchant paid  ──►  exactly the invoice, or the payment does not advance

A submitted transaction is never proof of payment, and a webhook only wakes the engine — the chain is the truth.

Architecture

Ports and adapters: a domain package never imports a concrete adapter. apps/api/src/container.ts is the only file that knows which implementations run.

flowchart TB
    Payer["Human checkout · Application (SDK) · AI agent (x402)"]

    subgraph Edge["apps/* — Hono + Zod"]
        API["Payment Intents · Commerce · x402 · Webhooks"]
        Worker["chain-worker — watcher, indexer, executor"]
    end

    subgraph Core["packages/core/* — pure domain, zero I/O"]
        Intent["payment-intent"] --> Quote["quote + liquidity"] --> Clearing["clearing"] --> Ledger["ledger"]
        Chain["chain"] -- recordAssetReceived --> Clearing
    end

    subgraph Adapters["packages/providers/* + packages/db"]
        EVM["evm · viem"]
        Swap["uniswap · 0x · lifi"]
        Oracle["pyth · chainlink"]
        DB["Drizzle + Postgres"]
    end

    Payer --> Edge --> Core
    Core -. ports .-> Adapters
Loading

Three execution paths, chosen per payment:

Path The payer does Advances on
Contract Calls PaymentRouter with a signed order A PaymentCompleted log past confirmation depth
Deposit-match Sends a transfer to a unique per-intent address A confirmed deposit matched to the intent
x402 Signs one EIP-3009 authorization The receipt read back and matched

Full detail: Architecture · Clearing Engine · Agent payments.

Agent payments (x402)

Any Mayarin-gated endpoint becomes payable per call by an agent that has never registered, holds no API key, and never sees a checkout page. It gets a 402 with machine-readable terms, signs one EIP-3009 authorization for an exact amount, and gets the resource.

   agent calls endpoint   →   402 + terms
   agent signs            →   one EIP-3009 authorization, exact amount
   Mayarin settles        →   receipt read back and matched, ledger posted
   agent gets             →   the resource

An agent that does not hold the merchant's asset still pays. The swap is priced backwards from the invoice — exact-output, so the merchant gets exactly the invoice or the swap reverts:

   authorization   payer    →  operator    the agent's asset, exactly what it signed for
   swap            operator →  pool        exact-output, bounded by the authorization
                   pool     →  merchant    exactly the invoice
   surplus                                 what the pool did not need, owed back to the payer

Current boundaries: exact scheme only, EVM only, and the broadcaster pays gas. Full detail in docs/x402.md.

Deployed

Testnet only. Mainnet is deliberately unprovisioned.

Arbitrum Sepolia and Robinhood Chain Testnet (an Arbitrum Orbit L2) share one set of addresses:

Contract Address
PaymentRouter 0xEe7c5B5a9eeAf667A6EFb217A8a77534C873f7a9
EscrowFactoryV2 0x309bDD9aF097BF0F3FaafF718c5C6c9A7f511B18
DepositForwarderFactory 0x4b96e74b963efa7ab7d7497854fc87220bd092c4
PayoutBatcher 0xfad1c3ec064ae52676ce874c2ba4d644ff2ad0b9

Other chains:

Chain PaymentRouter DepositForwarderFactory
Base Sepolia 0xee7c5b5a… 0x69c72C51…
Ethereum Sepolia 0xE54E800b… 0xCa83514c…
Arc Testnet 0xee7c5b5a… 0x04cd74e7…

EscrowFactoryV2 and PayoutBatcher sit at the same addresses on these three chains too.

What you get

Price in your own currency IDR, USD, EUR and more — settle in a stablecoin
Payer brings any supported asset exact-output swaps priced backwards from the invoice
Money is never a float bigint minor units end to end, double-entry ledger
Agents pay per call x402 — no account, no API key, one signature
Commerce out of the box catalog, payment links, invoices, hosted checkout, dashboard, SDK
Buyer protection on-chain escrow: bonded disputes, an arbiter, auto-release
Recurring and bulk subscriptions, and batch payouts in one transaction
Anyone can check every settlement is a transaction on a public block explorer

Built with

Arbitrum

Arbitrum Sepolia and Robinhood Chain Testnet run the full contract set above. Settlement is chosen per chain: USDC on Arbitrum, USDG on Robinhood Chain, PYUSD where the merchant settles in it.

Arc (Circle)

Arc is the merchant's settlement rail. Its gas token and the merchant's settlement asset are the same thing, USDC, so a merchant never has to hold a second token to get paid.

Multi-step settlement with a condition. An agent holding EURC pays a merchant who is owed USDC, and signs only once. Three things then happen on chain: the authorization is settled, an exact-output swap runs, and the merchant is paid. If the pool cannot deliver the full invoice, the swap reverts, so the merchant gets the exact amount or nothing moves. Authorization 0xf08c141d…, swap 0x9f4bf25b….

Agent decisions tied to real signals. The demo agent (bun run demo:agent) prints each decision it makes, and the reason for it:

Decision Signal
Which rail to pay Requested chain, then the merchant's first compatible configured rail
Whether to sign The quote is checked against an oracle and refused if it has drifted
How much to spend A spend ceiling, checked before signing, so nothing over it is signed

Circle products

Used How
Arc testnet PaymentRouter, DepositForwarderFactory, x402 settlement
USDC, EURC USDC is what merchants settle in; EURC is a payer asset for cross-asset payments
Agent Stack (Circle Wallets) A contract-account wallet pays, and USDC verifies it through EIP-1271

Not used: App Kits, Nanopayments, Paymaster, CCTP, Gateway, StableFX. The payer pays no gas because Mayarin's operator broadcasts the signed authorization. That is not Paymaster.

Uniswap

Cross-asset x402: an agent holding EURC pays a merchant settled in USDC, through an exact-output swap priced backwards from the invoice — authorization 0x254b93ce…, swap 0xb1436735….

Quick start

Requires Bun 1.4+, Node 22.12+, and Docker.

git clone https://github.com/playriglabs/mayarin.git
cd mayarin
bun run setup            # install, write .env, start Postgres, migrate
bun run dev:all          # core API + chain worker + dashboard + checkout
import { createMayarin } from "@mayarin/sdk";

const mayarin = createMayarin({
  baseUrl: "https://api-testnet.mayarin.xyz",
  secretKey: process.env.MAYARIN_SECRET_KEY ?? "",
});

const link = await mayarin.commerce.paymentLinks.create(
  {
    kind: "fixed",
    merchant: {
      id: "merchant_123",
      name: "Toko Melati",
      city: "Jakarta",
      countryCode: "ID",
    },
    amount: { amount: "50000.00", asset: "IDR" },
  },
  { idempotencyKey: "order-4711" },
);

Commands, conventions and tests: AGENT.md. Design record: docs/. API reference: docs.mayarin.xyz.

Where this actually is, right now

mayarin.xyz — shipped and running on testnet: clearing core, double-entry ledger, contract and deposit execution, oracle-guarded quotes, commerce and hosted checkout, merchant dashboard, SDK, agent payments, buyer-protection escrow, subscriptions and batch payouts.

Not done, and not pretended otherwise:

  • Fiat rails (QRIS, bank transfer) and the off-ramp are later phases.
  • Mainnet is planned, not provisioned.
  • Testnet pool depth is not market depth; the oracle deviation guard is widened on testnet.

License

Apache License 2.0 — copyright 2026 Playrig Labs. Solidity sources under packages/contracts/payment-router stay MIT.

Releases

Packages

Used by

Contributors

Languages