# DeroPay — Full Documentation Corpus > Every documentation page for deropay.derod.org, inlined as Markdown for long-context LLM ingestion. Curated link list: https://deropay.derod.org/llms.txt. Each page is also available individually at https://deropay.derod.org/.md. --- --- title: "DeroAuth API Reference" description: "Complete API reference for the dero-auth package — all exports, types, and interfaces." --- # API Reference Complete reference for all `dero-auth` package exports. ## Core (`dero-auth`) Message format, types, and validation utilities. ```ts ``` ## Crypto (`dero-auth/crypto`) Pure cryptographic operations — no network, no wallet. ```ts ``` ### Key Crypto Details - **Signature scheme**: Schnorr on BN256 (Barreto-Naehrig 254-bit) - **Hash function**: Keccak-256, reduced modulo curve order - **Address format**: Bech32 with 33-byte compressed G1 public key - **Generator**: `DERO_G = HashToPoint(Keccak256("DEROG"))` — DERO's custom generator - **Library**: `@noble/curves` (audited by Cure53) ## Server (`dero-auth/server`) Challenge generation, signature verification, and JWT sessions. ```ts ``` ### createChallengeServer Returns an object with: - `createChallenge(options)` — Generate a challenge message with nonce - `consumeNonce(nonce)` — Mark a nonce as used (returns boolean) - `isNonceValid(nonce)` — Check if nonce exists and is unused ### createSessionManager Returns an object with: - `createSession(payload)` — Create a JWT token - `verifySession(token)` — Verify and decode a JWT token ## Client (`dero-auth/client`) Browser-side XSWD connection and signing flow. ```ts ``` ### createAuthClient Returns an object with: - `xswd` — The underlying XSWDClient instance - `signIn(options?)` — Run the full auth flow (connect, challenge, sign, verify) - `disconnect()` — Close the wallet connection ## React (`dero-auth/react`) ```tsx ``` ## Next.js (`dero-auth/next`) ```ts ``` ### createAuthHandlers Return Value ```ts const { challengeHandler, // POST handler for /auth/challenge verifyHandler, // POST handler for /auth/verify challengeServer, // The underlying challenge server instance sessionManager, // The underlying session manager instance } = createAuthHandlers(config); ``` ### AuthHandlersConfig ```ts type AuthHandlersConfig = { domain: string; // Your domain (e.g., "myapp.com") uri: string; // Your full URI (e.g., "https://myapp.com") jwtSecret: string; // Secret for JWT signing (min 32 bytes) statement?: string; // Optional message shown to user expirationTime?: number; // Challenge expiry in seconds (default: 300) nonceStore?: NonceStore; // Custom nonce store (default: in-memory) }; ``` ### DeroMiddlewareConfig ```ts type DeroMiddlewareConfig = { jwtSecret: string; // Must match the handlers secret protectedPaths: string[]; // Paths to protect loginPath: string; // Where to redirect unauthenticated users }; ``` --- --- title: "Authentication Flow — Challenge, Sign, Verify" description: "Detailed walkthrough of the DeroAuth authentication flow: challenge generation, wallet signing, and server-side verification." --- # Authentication Flow DeroAuth uses a challenge-response pattern similar to SIWE (Sign-In With Ethereum), adapted for DERO's Schnorr signature scheme. ## Flow Diagram ``` User Browser Server | | | | Click "Sign In" | | |------------------------>| | | | POST /auth/challenge | | | { address } | | |------------------------>| | | { messageText, nonce } | | |<------------------------| | | | | Wallet: "Sign this?" | XSWD: SignData(message) | | [Approve] [Deny] | | |------------------------>| | | PEM signature | | | | POST /auth/verify | | | { signature, nonce } | | |------------------------>| | | Verify Schnorr sig | | | Check domain/nonce | | | { session (JWT) } | | |<------------------------| | Authenticated! | | ``` ## Step by Step ### 1. Challenge Generation The server creates a human-readable message for the user to sign: ``` myapp.com wants you to sign in with your DERO wallet: dero1qy...abc123 Sign in to My App URI: https://myapp.com Version: 1 Chain ID: dero-mainnet Nonce: k8j2m4n6p8q0 Issued At: 2026-02-14T12:00:00.000Z Expiration Time: 2026-02-14T12:05:00.000Z ``` Key properties: - **Domain-bound** — Prevents phishing (the domain is displayed in the wallet) - **Nonce** — Crypto-random, single-use, prevents replay attacks - **Expiry** — Short window (default 5 minutes) limits exposure - **Human-readable** — Users can verify what they're signing ### 2. Wallet Signing (XSWD) The browser connects to the user's local DERO wallet via the **XSWD protocol** (WebSocket): - **Engram desktop wallet**: `ws://localhost:44326` - **DERO CLI wallet**: `ws://localhost:10103` The XSWD client sends a `SignData` request with the challenge message. The wallet displays the message and asks the user to approve or deny. If approved, the wallet returns a **PEM-encoded Schnorr signature**. ### 3. Server Verification The server verifies the signature without needing a DERO node: 1. **Parse** the PEM signature format 2. **Decode** the DERO address to extract the BN256 public key 3. **Hash** the message with Keccak-256, reduced modulo the curve order 4. **Verify** the Schnorr signature against the public key 5. **Check** domain, nonce, and expiry 6. **Consume** the nonce (prevents replay) 7. **Issue** a JWT session token > [!NOTE] > The entire verification is **mathematical** — it uses `@noble/curves` to verify the Schnorr signature on the BN256 curve. No RPC call to a DERO node is needed. This means your auth server doesn't need to run a DERO daemon. ### 4. Session A standard JWT is issued containing the DERO address. Use it like any other session token: ```ts // Server: verify the session const sessions = createSessionManager({ secret: process.env.JWT_SECRET! }); const payload = await sessions.verify(token); // { address: "dero1qy...", iat: ..., exp: ... } ``` ## Replay Protection Nonces are single-use and stored server-side: - **Development**: In-memory store (default) - **Production**: Redis store with atomic consume (recommended for multi-instance) A nonce can only be used once — if an attacker captures a signed message, they can't replay it. --- --- title: "Cryptography — Schnorr Signatures on BN256" description: "How DeroAuth verifies DERO wallet signatures: Schnorr on BN256, custom generator point, Keccak-256 hashing, and Bech32 address encoding." --- # Cryptography DeroAuth performs signature verification using DERO's native cryptographic primitives — entirely in TypeScript. ## Schnorr Signatures on BN256 DERO uses **Schnorr signatures** on the **BN256** (Barreto-Naehrig 254-bit) elliptic curve. This is fundamentally different from Bitcoin/Ethereum which use ECDSA on secp256k1. ### Signature Scheme A Schnorr signature consists of `(s, e)` where: - `e = H(R || P || M)` — hash of the nonce point, public key, and message - `s = r - e * x` — the scalar response (where `x` is the private key, `r` is the nonce) Verification checks: `s*G + e*P == R` ### Hash Function DERO uses **Keccak-256** (not SHA-256) for signature hashing. The hash output is **reduced modulo the curve order** to produce a scalar. ```ts // Keccak-256 of the input, reduced mod curve order const scalar = reducedHash(messageBytes); ``` ## DERO's Custom Generator Point > [!WARNING] > **Critical detail**: DERO does **not** use the standard BN256 generator point `(1, 2)`. It uses a custom generator derived from hashing. This was a critical discovery during development — using the standard generator produces invalid verification results. The custom generator is defined in `dero-auth/crypto` and was reverse-engineered from the DERO Go source code. This is the single most important implementation detail for anyone attempting DERO signature verification. ## Address Format DERO addresses use **Bech32 encoding** with a 33-byte compressed G1 public key: ``` dero1qy[...rest of bech32...] ``` The `dero-auth/crypto` module provides encoding and decoding: ```ts // Decode address to get the public key bytes const publicKeyBytes = decodeAddress("dero1qy..."); // Returns: Uint8Array (33 bytes, compressed G1 point) // Encode a public key back to an address const address = encodeAddress(publicKeyBytes); // Validate format const isValid = isValidAddress("dero1qy..."); ``` ## PEM Signature Format DERO wallets produce signatures in **PEM format** — a base64-encoded block wrapped in header/footer lines: ``` -----BEGIN DERO SIGNED MESSAGE----- [base64 encoded signature data] -----END DERO SIGNED MESSAGE----- ``` ```ts const { signature, publicKey } = parsePEM(pemString); ``` ## Verification Pipeline The full verification process: ```ts const result = verifySignature({ message: challengeMessage, signature: pemSignature, address: "dero1qy...", }); if (result.valid) { // Signature is mathematically valid // result.address contains the verified DERO address } ``` Under the hood: 1. Parse PEM to extract raw signature bytes 2. Decode the DERO address to a compressed G1 public key 3. Decompress the public key to a full curve point 4. Hash the message with Keccak-256, reduce mod curve order 5. Verify the Schnorr equation: `s*G + e*P == R` ## Dependencies All cryptographic operations use audited libraries: - **`@noble/curves`** — BN256 curve operations (audited by Cure53) - **`@noble/hashes`** — Keccak-256 hashing (audited by Cure53) No custom cryptographic implementations. --- --- title: "Next.js Integration — DeroAuth Route Handlers & Middleware" description: "Set up DeroAuth in a Next.js 14/15 App Router project with API route handlers and auth middleware." --- # Next.js Integration DeroAuth provides ready-made API route handlers and auth middleware for Next.js 14+ App Router. ## Setup ### Create Shared Config ```ts // lib/dero-auth.ts export const { challengeHandler, // POST handler for challenge route verifyHandler, // POST handler for verify route challengeServer, // Underlying challenge server (for custom usage) sessionManager, // Underlying session manager (for custom usage) } = createAuthHandlers({ domain: "myapp.com", uri: "https://myapp.com", jwtSecret: process.env.JWT_SECRET!, statement: "Sign in to My App", // optional message shown to user expirationTime: 300, // optional: challenge expiry in seconds (default: 300) }); ``` ### Wire Up API Routes ```ts // app/api/auth/challenge/route.ts export const POST = challengeHandler; ``` ```ts // app/api/auth/verify/route.ts export const POST = verifyHandler; ``` ### Environment Variables ```env # .env.local JWT_SECRET=your-jwt-secret-at-least-32-bytes ``` ## Protecting Routes with Middleware DeroAuth includes a middleware that protects pages and API routes: ```ts // middleware.ts export const middleware = createDeroAuthMiddleware({ jwtSecret: process.env.JWT_SECRET!, protectedPaths: ["/dashboard", "/api/protected"], loginPath: "/login", }); export const config = { matcher: ["/dashboard/:path*", "/api/protected/:path*"], }; ``` Unauthenticated users hitting `/dashboard` are redirected to `/login`. Unauthenticated API requests get a `401` response. ## Reading the Auth Address In protected API routes or server components: ```ts export async function GET(request: Request) { const address = await getAuthAddress(request); if (!address) { return new Response("Unauthorized", { status: 401 }); } // address is the verified DERO address return Response.json({ address }); } ``` ## Full Example: Protected Dashboard ```tsx // app/dashboard/page.tsx (Server Component) export default async function DashboardPage() { const address = await getAuthAddress(headers()); if (!address) { redirect("/login"); } return (

Dashboard

Connected wallet: {address}

); } ``` > [!NOTE] > The middleware approach is recommended for protecting multiple routes. Use `getAuthAddress` for fine-grained checks within individual routes or server components. --- --- title: "DeroAuth Overview — Wallet-Based Authentication for DERO" description: "DeroAuth enables 'Sign in with your DERO wallet' — privacy-preserving authentication using Schnorr signatures on BN256." --- # DeroAuth Overview **Sign in with your DERO wallet.** No email. No password. Just cryptographic proof of wallet ownership. `dero-auth` is a TypeScript library that provides wallet-based authentication for DERO. It works like "Sign in with Ethereum" (SIWE), but for DERO's privacy chain. **Privacy advantage**: Unlike Ethereum auth (which exposes your full transaction history), authenticating with DERO reveals *nothing* about your on-chain activity. All transactions are encrypted. ## Features - **Pure TypeScript** signature verification (Schnorr on BN256) — no wallet needed server-side - **React components** — drop-in `` button and `useDeroAuth()` hook - **Next.js integration** — ready-made API route handlers and auth middleware - **XSWD client** — connects to DERO wallets (Engram, CLI) via WebSocket - **SIWE-style message format** — human-readable, domain-bound, replay-protected - **JWT sessions** — standard token-based session management - **Zero personal data** — just a cryptographic address ## How It Works 1. Server generates a **challenge** (human-readable message with domain, nonce, expiry) 2. User's wallet **signs** the challenge via XSWD (WebSocket to local wallet) 3. Server **verifies** the Schnorr signature purely in TypeScript — no RPC call to a node 4. **JWT session** is issued The entire verification is mathematical — no blockchain query needed. ## Why Not Just Use Ethereum Auth? | | Ethereum (SIWE) | DERO Auth | |---|---|---| | **Transaction privacy** | All transactions visible on-chain | Transactions encrypted, invisible to verifier | | **Wallet extension** | MetaMask required | Connects to local wallet via WebSocket | | **Personal data exposed** | Address links to full history | Address reveals nothing about activity | | **Verification** | Pure JS (ecrecover) | Pure JS (Schnorr on BN256) | ## Wallet Compatibility | Wallet | Connection | Status | |---|---|---| | Engram (desktop) | XSWD (`ws://localhost:44326`) | Supported | | DERO CLI wallet | XSWD (`ws://localhost:10103`) | Supported | ## Package Exports | Import | Purpose | |---|---| | `dero-auth` | Core types and message format | | `dero-auth/crypto` | Pure crypto: Schnorr verify, BN256, address parsing | | `dero-auth/server` | Challenge generation, verification, JWT sessions | | `dero-auth/client` | Browser SDK: XSWD wallet connection, signing flow | | `dero-auth/react` | `` button, provider, hooks | | `dero-auth/next` | Next.js API route handlers, auth middleware | ## Next Steps - [Quick Start](/dero-auth/quick-start.md) — Add wallet login to your app - [Authentication Flow](/dero-auth/auth-flow.md) — Detailed flow walkthrough - [Cryptography](/dero-auth/cryptography.md) — Schnorr, BN256, and DERO's unique curve --- --- title: "DeroAuth Quick Start — Add Wallet Login" description: "Add DERO wallet-based authentication to your app in minutes with dero-auth." --- # Quick Start Add wallet-based login to your app. ## Install ```bash bun add dero-auth ``` ```bash npm install dero-auth ``` ```bash pnpm add dero-auth ``` ## Set a JWT Secret ```bash export JWT_SECRET="$(openssl rand -base64 32)" ``` ## Next.js App Router ### Create API Routes ```ts // app/api/auth/challenge/route.ts const { challengeHandler } = createAuthHandlers({ domain: "myapp.com", uri: "https://myapp.com", jwtSecret: process.env.JWT_SECRET!, }); export const POST = challengeHandler; ``` ```ts // app/api/auth/verify/route.ts const { verifyHandler } = createAuthHandlers({ domain: "myapp.com", uri: "https://myapp.com", jwtSecret: process.env.JWT_SECRET!, }); export const POST = verifyHandler; ``` ### Add the Provider and Button ```tsx // app/layout.tsx export default function Layout({ children }) { return ( {children} ); } ``` ```tsx // app/login/page.tsx export default function LoginPage() { return ; } ``` That's it. The button handles the full flow: wallet connection, challenge signing, and verification. ## Custom Hook (Alternative) If you don't want the provider pattern: ```tsx "use client"; export default function LoginPage() { const { signIn, signOut, isAuthenticated, address, isLoading } = useDeroAuth(); if (isAuthenticated) { return (

Signed in as {address}

); } return ( ); } ``` ## Production: Redis Nonce Store For production deployments with multiple instances, use a shared nonce store: ```ts const redis = createClient({ url: process.env.REDIS_URL! }); await redis.connect(); const nonceStore = createRedisNonceStoreFromNodeRedis(redis, { keyPrefix: "myapp:auth:nonce:", }); const { challengeHandler, verifyHandler } = createAuthHandlers({ domain: "myapp.com", uri: "https://myapp.com", jwtSecret: process.env.JWT_SECRET!, nonceStore, }); ``` > [!NOTE] > The default nonce store is in-memory and works for single-instance deployments. For multi-instance setups (e.g., behind a load balancer), use Redis to ensure replay protection works across all instances. ## What's Next - [Authentication Flow](/dero-auth/auth-flow.md) — Understand the full flow - [Cryptography](/dero-auth/cryptography.md) — How Schnorr verification works on DERO - [Next.js Integration](/dero-auth/nextjs.md) — Route protection and middleware --- --- title: "React Components — DeroAuth UI" description: "Drop-in React components for DeroAuth: SignInWithDero button, DeroAuthProvider, and useDeroAuth hook." --- # React Components DeroAuth provides drop-in React components for wallet-based login. ```tsx ``` ## DeroAuthProvider Wraps your app to provide authentication context: ```tsx export default function Layout({ children }) { return ( {children} ); } ``` | Prop | Type | Default | Description | |---|---|---|---| | `appName` | `string` | required | Displayed in wallet connection prompt | | `challengeEndpoint` | `string` | `/api/auth/challenge` | Server challenge endpoint | | `verifyEndpoint` | `string` | `/api/auth/verify` | Server verify endpoint | | `children` | `ReactNode` | required | Child components | ## SignInWithDero A pre-built button that handles the entire authentication flow: ```tsx function LoginPage() { return ( console.log("Signed in!", session.address)} onError={(error) => console.error("Auth failed:", error)} /> ); } ``` | Prop | Type | Description | |---|---|---| | `onSuccess` | `(session) => void` | Callback on successful authentication | | `onError` | `(error: Error) => void` | Callback on failure | | `label` | `string` | Button text (default: "Sign In with DERO") | | `className` | `string` | Additional CSS classes | The button automatically: 1. Connects to the local wallet via XSWD 2. Requests a challenge from the server 3. Asks the user to sign the challenge 4. Sends the signature to the server for verification 5. Stores the JWT session ## useDeroAuth Hook For building custom login UIs: ```tsx "use client"; export default function CustomLogin() { const { signIn, // Trigger the auth flow signOut, // Clear the session isAuthenticated, // Boolean: currently signed in address, // DERO address (or null) isLoading, // Boolean: auth in progress error, // Error object (or null) } = useDeroAuth(); if (isAuthenticated) { return (

Welcome, {address}

); } return (
{error &&

{error.message}

}
); } ``` ## useDeroAuthContext Same as `useDeroAuth` but requires being inside a `DeroAuthProvider`: ```tsx function UserBadge() { const { isAuthenticated, address } = useDeroAuthContext(); if (!isAuthenticated) return null; return Connected: {address?.slice(0, 12)}...; } ``` > [!NOTE] > Use `useDeroAuth` for standalone usage (no provider needed). Use `useDeroAuthContext` when you have a `DeroAuthProvider` wrapping your app and want shared state across components. --- --- title: "Autonomous Agent Payer — dero-pay/agent" description: "The agent side of DeroPay's x402 rail: an auto-payer that settles HTTP 402 challenges under a deny-by-default spend policy, plus attenuable credentials and per-call MCP tool payments. No facilitator, no extra contract." --- # Autonomous Agent Payer `dero-pay/agent` is the agent side of DeroPay's x402 receipt rail. Everything here pays the invoices minted by [`createX402RouteGuard` / `createPaymentHandlers`](/dero-pay/x402.md) — a plain DERO transfer to an integrated address, redeemed for an HMAC-signed receipt token. **No extra smart contract, no facilitator service.** > [!NOTE] > This is the counterpart to the [x402 Payment Guard](/dero-pay/x402.md). The guard mints the 402 challenge; the agent payer settles it. They share the same receipt rail, so an agent can pay any DeroPay-guarded route or MCP tool. ``` agent merchant (dero-pay/next) │ GET /api/protected/report │ ├────────────────────────────────────▶ createX402RouteGuard │ 402 { error, payment:{invoiceId, │ └─ engine.createInvoice() │ integratedAddress, ... } } │ ◀────────────────────────────────────┤ │ │ ├─ SpendPolicy.reserve(origin, amt) │ deny-by-default; throws on breach ├─ payer.transfer(integratedAddress) │ DERO moves on-chain │ │ └─ PaymentMonitor matches the │ GET /api/pay/status?invoiceId= │ payment ID, tracks confirmations ├────────────── poll ───────────────▶│ │ ... completed │ │ POST /api/pay/receipts/issue │ ├────────────────────────────────────▶ └─ issueReceiptFromInvoice() │ { receipt, claims } │ ◀────────────────────────────────────┤ │ GET /api/protected/report │ │ X-DeroPay-Receipt: │ ├────────────────────────────────────▶ └─ verifyPaymentReceipt() → 200 ``` ## Quick Start ```ts const payingFetch = createPayingFetch({ payer: createWalletRpcPayer(), // http://127.0.0.1:10103/json_rpc policy: new SpendPolicy({ allowOrigins: ["https://api.example.com"], maxAtomicPerRequest: 100_000n, // 1 DERO maxAtomicPerWindow: { amountAtomic: 500_000n, windowSeconds: 3600 }, }), onPayment: (evidence) => auditLog.write(evidence), }); const res = await payingFetch("https://api.example.com/api/protected/report"); ``` `payingFetch` is fetch-compatible: non-402 responses pass through untouched. On an `x402-deropay-draft` challenge it reserves spending authority, pays, waits for settlement, redeems the receipt, and retries the original request (buffered body included) with `X-DeroPay-Receipt`. ### Endpoint Discovery The 402 challenge does not carry the payment API's location, so the payer assumes the upstream convention `${origin}/api/pay` (status at `/api/pay/status`, redemption at `/api/pay/receipts/issue`). Override per deployment: ```ts createPayingFetch({ // ... paymentApi: "https://pay.example.com/api/pay", // or a resolver: // paymentApi: (challenge, resourceUrl) => "https://pay.example.com/api/pay", }); ``` ## Safety Properties The payer is built for autonomous operation, where a mistaken payment is real funds moving without a human in the loop. Its defaults are conservative: - **Deny-by-default.** An empty `allowOrigins` authorizes nothing. Every payment is reserved against the policy before the wallet is touched; denials throw `SpendPolicyError` / `CredentialError`. - **Loopback-only wallet.** `createWalletRpcPayer` refuses non-loopback wallet URLs unless `allowNonLoopback: true` — an autonomous payer pointed at a remote wallet is a key-exfiltration hazard. - **`createPayingFetch` never pays the same demand twice.** Concurrent calls through the auto-fetch that hit the same `(origin, resource, amount)` challenge share one in-flight payment. A call that times out while settling throws `X402SettlementTimeoutError` (`"deadline"`) and leaves a pending record — the next call resumes polling that invoice instead of paying again. A 402 that persists after the receipt was issued throws `X402PaymentRejectedError` rather than paying again. - **Origins bound *who*, not *where*.** The allowlist and spend caps constrain which servers you pay and how much — but the recipient (the integrated address the challenge names) is server-supplied, and there is no destination allowlist. An allowlisted server can direct the deposit anywhere within the cap; do not treat an allowlisted origin as a trusted recipient. - **Loud unpayables.** A 402 the payer cannot honor (foreign protocol, wrong network, malformed body) throws `X402UnpayableError` by default, so agent code cannot mistake an unpaid body for a paid one. Set `unpayable: "passthrough"` to hand the 402 back instead. - **Evidence.** Every settled payment invokes `onPayment` with `{ at, origin, resource, network, invoiceId, integratedAddress, amountAtomic, txid, receiptJti, receiptExpiresAt }`. ## Receipt Reuse Receipts are cached per `(origin, path)` and attached proactively until `claims.expiresAt` (server default TTL: 600s), so repeat calls don't re-pay. Against a guard with `enforceSingleUseReceipts` the cached receipt earns a 409 once burned; the payer evicts it and recovers through a fresh challenge automatically. Set `reuseReceipts: false` to skip caching (and that recovery round-trip) entirely. ## Settlement Timing `requiredConfirmations` (typically 3) means real block time — roughly 18s per confirmation on mainnet. Defaults: `settleTimeoutMs: 180_000`, `settlePollIntervalMs: 2_500`. On timeout the thrown `X402SettlementTimeoutError` carries `{ invoiceId, txid, integratedAddress, resource, origin, paymentApi, reason }`: | `reason` | Meaning | Recovery | |---|---|---| | `"deadline"` | Confirmations didn't land in time | Resumable — re-run the call, the same invoice is polled | | `"invoice_expired"` | The transfer left the wallet but the invoice lapsed | Reconcile with the merchant | ## Spending Policies Both policy classes implement the same `SpendGuard` interface (`reserve(origin, amountAtomic, context) → { commit, release }`), so they drop into `createPayingFetch` and `createPayingToolCaller` interchangeably. - **`SpendPolicy`** — origin allowlist, per-request cap, optional rolling-window cap. Reservation-based: two concurrent payments cannot both slip under a nearly exhausted window. - **`CredentialPolicy`** — macaroon-style attenuable capabilities built from HMAC chains (`mintSpendCredential`, `attenuate`, `verifyCredentialSignature`). Attenuation tightens the **spend cap** (lowest wins), shortens **expiry** (earliest wins), and narrows the **`resource-prefix`** (every required prefix must match the challenge's `resource`, e.g. `/api/`). Hand an attenuated credential to a sub-agent instead of a wallet. > [!WARNING] > A credential only restricts on the caveats it actually carries — one minted with no spend cap, no origin, or no expiry is **unrestricted on that axis**. Always set the caveats you depend on. Note the `origin` caveat currently matches *any-of* (adding an origin widens the allowed set rather than narrowing it), so do not rely on origin caveats alone to confine a sub-agent — pair them with a `resource-prefix` and a spend cap. For strict deny-by-default origin control, use `SpendPolicy`. ## MCP Paid Tools `createPaidToolGuard` (server) and `createPayingToolCaller` (client) put per-call payment gating on MCP tools over the same rail — transport-agnostic, no MCP SDK dependency. - The challenge is a `payment_required` tool result whose JSON matches the HTTP 402 body byte for byte (plus `settling: true` while a referenced invoice confirms). - The payment travels as the reserved tool argument `x402Payment`, carrying either the paid invoice's id (fresh redemption) or a receipt token (reuse, when the guard runs `singleUse: false`). - The guard mints invoices through the merchant's own `InvoiceEngine` and verifies receipts locally with the shared `receiptSecret`. Replaying the paid call *is* the settlement poll: the guard re-issues the SAME invoice's challenge until confirmations land, and the caller refuses to pay a second invoice for the same call (`X402ToolPaymentRejectedError`). - With `singleUse: true` (default) every redemption and receipt `jti` is burned in the store's replay ledger, so one payment unlocks exactly one call. > [!WARNING] > Unlike `createPayingFetch`, the `createPayingToolCaller` path does **not** currently de-duplicate *concurrent* identical tool calls (there is no shared in-flight guard). Two paid calls fired in parallel for the same tool can each mint and pay an invoice. Serialize paid tool calls if a double-pay would matter, until the in-flight guard lands. ```ts // server const { guard } = createPaidToolGuard({ getEngine: paymentHandlers.getEngine, receiptSecret: process.env.DEROPAY_RECEIPT_SECRET!, pricing: { amountAtomic: 50_000n }, // 0.5 DERO per call }); server.tool("summarize", guard("summarize", async (args) => run(args))); // client const callTool = createPayingToolCaller({ callTool: mcpClient.callTool, payer: createWalletRpcPayer(), policy, serverOrigin: "https://tools.example.com", }); ``` ## Demo A runnable end-to-end demo pays the [x402 example app](/dero-pay/x402.md#example-app) against a local chain. The agent needs its **own** wallet — paying the merchant wallet from itself won't register as an incoming transfer. ## See Also - [x402 Payment Guard](/dero-pay/x402.md) — the merchant side that mints the challenges this payer settles - [API Reference](/dero-pay/api-reference.md#agent-dero-payagent) — full `dero-pay/agent` export list --- --- title: "DeroPay API Reference" description: "Complete API reference for the dero-pay package — all exports, types, and interfaces." --- # API Reference Complete reference for all `dero-pay` package exports. Every subpath below is published **dual-format**: an ESM build resolved by `import` and a CommonJS build resolved by `require`, each with its own type declarations. The `./package.json` export is also exposed so tools can resolve the manifest directly. This means a CommonJS backend can `require("dero-pay/server")` while an ESM app `import`s the same package. ## Core (`dero-pay`) Types, pricing utilities, and payment ID generation. ```ts ``` ## RPC (`dero-pay/rpc`) ```ts ``` ## Server (`dero-pay/server`) ```ts ``` ## Client (`dero-pay/client`) ```ts ``` ## React (`dero-pay/react`) ```tsx ``` ## Next.js (`dero-pay/next`) ```ts ``` ### createPaymentHandlers Return Value ```ts const { createInvoiceHandler, // POST — create invoice statusHandler, // GET — invoice status by ID listInvoicesHandler, // GET — list invoices with filters statsHandler, // GET — invoice statistics webhookHandler, // POST — incoming webhook receiver healthHandler, // GET — engine health check escrowActionHandler, // POST — escrow lifecycle actions listEscrowsHandler, // GET — list escrow records claimEscrowInvoiceHandler, // POST — claim a pre-minted escrow invoice issueReceiptHandler, // POST — issue an x402 receipt after payment verifyReceiptHandler, // POST — verify an x402 receipt token getEngine, // () => Promise } = createPaymentHandlers(config); ``` ## x402 (`dero-pay/x402`) Framework-agnostic HTTP 402 primitives — use these when you are not on Next.js (the `dero-pay/next` `createX402RouteGuard` is the App Router wrapper over the same rail). ```ts ``` Focused subpaths are published for tree-shaking and cross-runtime use: | Import Path | Contents | |---|---| | `dero-pay/x402/types` | Zod schemas + inferred types (`PaymentPayload`, `PaymentRequirements`) | | `dero-pay/x402/server` | `build402Response`, `parsePaymentHeader`, facilitator client | | `dero-pay/x402/client` | `selectAcceptsEntry`, `buildPaymentHeader`, `payDeroRail` (async convenience payer) | | `dero-pay/x402/next` | `withX402` App Router wrapper | See the [x402 Payment Guard](/dero-pay/x402.md) page for the full guard flow. ## Agent (`dero-pay/agent`) The autonomous agent-side payer — settles x402 challenges under a spend policy with no facilitator. ```ts ``` See [Autonomous Agent Payer](/dero-pay/agent.md) for the full flow and safety properties. ## Escrow (`dero-pay/escrow`) ```ts ``` See [PREMINT Keeper](/escrow/keeper.md) for the pre-mint inventory flow. ## Router (`dero-pay/router`) ```ts ``` See the [Payment Router SDK](/payment-router/sdk.md) for full usage. ## Gateway (`dero-pay/gateway`) HTTP client for external platforms (Medusa, WooCommerce, etc.) to create invoices and check status against a running gateway server. ```ts ``` See the [Gateway Server](/guides/gateway-server.md) guide for the server side. ## Bridge (`dero-pay/bridge`) An outbound-only host daemon that pushes durable, at-least-once payment webhooks to a merchant — `InvoiceEngine` + a durable outbox + a delivery worker, with zero inbound listeners. Also ships a `deropay-bridge` CLI binary. ```ts ``` See the [Escrow documentation](/escrow/overview.md) for full escrow API details. ## Configuration Reference | Field | Type | Default | Description | |---|---|---|---| | `walletRpcUrl` | `string` | `localhost:10103` | Wallet RPC endpoint | | `daemonRpcUrl` | `string` | `localhost:10102` | Daemon RPC endpoint | | `rpcAuth` | `{ username, password }` | — | RPC authentication | | `chainId` | `DeroChainId` | `"dero-mainnet"` | Network identifier | | `defaultTtlSeconds` | `number` | `900` | Invoice expiration (15 min) | | `defaultRequiredConfirmations` | `number` | `3` | Confirmation depth | | `pollIntervalMs` | `number` | `5000` | Monitor poll interval | | `webhookUrl` | `string` | — | Webhook destination | | `webhookSecret` | `string` | — | HMAC signing secret | | `webhookMaxRetries` | `number` | `3` | Max delivery attempts | | `store` | `InvoiceStore` | `MemoryInvoiceStore` | Storage backend | | `enableEscrow` | `boolean` | `false` | Enable escrow subsystem | | `escrowFeeBasisPoints` | `number` | `0` | Escrow platform fee in basis points (0 = no fee, 250 = 2.5%) | | `escrowBlockExpiration` | `number` | `60` | Escrow block expiry | --- --- title: "Invoice Engine — Core Payment Orchestrator" description: "The InvoiceEngine is the central component of DeroPay, coordinating invoice creation, payment monitoring, storage, and webhook dispatch." --- # Invoice Engine The `InvoiceEngine` is the core orchestrator of DeroPay. It coordinates invoice creation, payment monitoring, storage, and webhook dispatch. ## Configuration ```ts const engine = new InvoiceEngine({ // Required: DERO wallet RPC endpoint walletRpcUrl: "http://127.0.0.1:10103/json_rpc", // Required: DERO daemon RPC endpoint daemonRpcUrl: "http://127.0.0.1:10102/json_rpc", // Optional: RPC authentication rpcAuth: { username: "", password: "" }, // Optional: webhook configuration webhookUrl: "https://mystore.com/webhooks/dero", webhookSecret: process.env.WEBHOOK_SECRET!, webhookMaxRetries: 3, // Optional: storage backend (defaults to MemoryInvoiceStore) store: new SqliteInvoiceStore({ path: "./invoices.db" }), // Optional: polling interval in ms (default: 5000) pollIntervalMs: 5000, // Optional: confirmations required (default: 3) defaultRequiredConfirmations: 3, // Optional: invoice expiry in seconds (default: 900 — 15 minutes) defaultTtlSeconds: 900, // Optional: escrow settings enableEscrow: true, escrowFeeBasisPoints: 0, // No platform fee (default; set 250 for 2.5%) escrowBlockExpiration: 60, // blocks until expiry }); ``` ## Lifecycle ```ts // Start the engine (begins payment monitoring) await engine.start(); // Create invoices const invoice = await engine.createInvoice({ name: "Product Name", description: "Product description", amount: 500000, // atomic units (5 DERO) metadata: { orderId: "ORD-123" }, // optional custom data }); // Query invoice status const status = await engine.getInvoice(invoice.id); // Stop the engine await engine.stop(); ``` ## Events The engine emits events you can listen to: ```ts engine.on("invoiceStatusChanged", (invoice, previousStatus) => { // Fires on every status transition }); engine.on("paymentDetected", (invoice, payment) => { // Fires when a matching transaction is found (before full confirmation) }); engine.on("paymentConfirmed", (invoice, payment) => { // Fires when payment reaches required confirmation depth }); engine.on("error", (error) => { // Fires on engine errors (RPC failures, etc.) }); ``` ## How Payment Detection Works 1. When an invoice is created, DeroPay generates a **unique payment ID** and combines it with the merchant's DERO address into an **integrated address** 2. The `PaymentMonitor` polls the wallet RPC for incoming transfers 3. Each transfer is checked for a matching payment ID 4. When found, the amount is verified and confirmations are tracked 5. Once the required confirmations are reached, the invoice is marked `completed` > [!NOTE] > **Integrated addresses** are a DERO feature where a payment ID is embedded directly in the address. This means each invoice has a unique address, making payment matching deterministic. ## Pricing Utilities DeroPay includes helpers for converting between DERO and atomic units: ```ts deroToAtomic("5.0"); // 500_000n (BigInt) deroToAtomic("0.001"); // 100n atomicToDero(500_000n); // "5.0" formatDero(500_000n); // "5.00000 DERO" ``` > [!WARNING] > DERO uses **5 decimal places** (1 DERO = 100,000 atomic units). Always use `deroToAtomic()` for conversions. --- --- title: "Next.js Integration — DeroPay Route Handlers & Middleware" description: "Set up DeroPay in a Next.js 14/15 App Router project with ready-made API handlers and middleware." --- # Next.js Integration DeroPay provides ready-made API route handlers and middleware for Next.js 14+ App Router. ```ts ``` ## Setup ### Create the Payment Handlers ```ts // lib/deropay.ts export const { createInvoiceHandler, // POST — create a new invoice statusHandler, // GET — check invoice status listInvoicesHandler, // GET — list all invoices statsHandler, // GET — invoice statistics webhookHandler, // POST — receive webhooks healthHandler, // GET — engine health check escrowActionHandler, // POST — perform escrow action listEscrowsHandler, // GET — list escrow records getEngine, // () => Promise } = createPaymentHandlers({ walletRpcUrl: process.env.DERO_WALLET_RPC!, daemonRpcUrl: process.env.DERO_DAEMON_RPC!, webhookSecret: process.env.WEBHOOK_SECRET!, }); ``` ### Wire Up the API Routes ```ts // app/api/pay/create/route.ts export const POST = createInvoiceHandler; ``` ```ts // app/api/pay/status/route.ts export const GET = statusHandler; ``` ```ts // app/api/webhooks/dero/route.ts export const POST = webhookHandler; ``` ### Environment Variables ```env # .env.local DERO_WALLET_RPC=http://127.0.0.1:10103/json_rpc DERO_DAEMON_RPC=http://127.0.0.1:10102/json_rpc WEBHOOK_SECRET=your-webhook-secret-here ``` ### Client-Side Payment Page ```tsx // app/pay/[invoiceId]/page.tsx export default function PayPage({ params, }: { params: { invoiceId: string }; }) { return ( window.location.href = "/thank-you"} /> ); } ``` ## Creating Invoices from Server Components ```tsx // app/checkout/page.tsx (Server Component) export default async function CheckoutPage() { async function handleCheckout(formData: FormData) { "use server"; const invoice = await createInvoice({ name: "Order", amount: deroToAtomic("10.0"), }); redirect(`/pay/${invoice.id}`); } return (
); } ``` ## x402 Payment Guard The x402 guard lets you charge for API access using standard HTTP `402 Payment Required` semantics — no accounts, no API keys, no subscriptions. ```ts // lib/deropay.ts export const paymentHandlers = createPaymentHandlers({ walletRpcUrl: process.env.DERO_WALLET_RPC!, daemonRpcUrl: process.env.DERO_DAEMON_RPC!, receiptSecret: process.env.DEROPAY_RECEIPT_SECRET!, }); export const x402Guard = createX402RouteGuard({ getEngine: paymentHandlers.getEngine, receiptSecret: process.env.DEROPAY_RECEIPT_SECRET!, policy: { name: "Premium API Access", amountAtomic: deroToAtomic("0.10"), requiredConfirmations: 3, resource: "/api/protected/report", }, }); ``` ```ts // app/api/protected/report/route.ts export const GET = x402Guard(async () => { return Response.json({ report: "paid content" }); }); ``` For the full feature set — dynamic pricing, quotas, replay protection, key rotation, and audit events — see the [x402 Payment Guard](/dero-pay/x402.md) reference. ## API Key Middleware DeroPay includes middleware for protecting payment API routes with API keys: ```ts // middleware.ts export const middleware = createDeroPayMiddleware({ apiKeys: [process.env.DEROPAY_API_KEY!], protectedPaths: ["/api/pay/create", "/api/pay/escrow"], publicPaths: ["/api/pay/status", "/api/pay/health"], headerName: "X-DeroPay-ApiKey", // default }); export const config = { matcher: ["/api/pay/:path*"], }; ``` Requests to protected paths must include the API key in the header. Public paths are accessible without authentication. > [!NOTE] > The payment handlers are designed for the **App Router** (`app/` directory). For Pages Router, use the `InvoiceEngine` directly in API routes under `pages/api/`. --- --- title: "DeroPay Overview — Payment Processing for DERO" description: "DeroPay is a TypeScript SDK for accepting DERO payments with invoices, payment monitoring, webhooks, escrow, and a merchant dashboard." --- # DeroPay Overview **DeroPay** is a full payment processing SDK for DERO — invoices, payment monitoring, webhooks, escrow, and a merchant dashboard. Think "Stripe for DERO" but self-hosted and privacy-preserving. > [!NOTE] > No actively maintained payment processor exists for current DERO (HE/Stargate). DeroPay is the first. ## Features - **Invoice Engine** — Create invoices with unique integrated addresses, track payments, manage lifecycle - **Payment Monitor** — Polls the DERO wallet for incoming transactions, tracks confirmations - **Webhook Dispatcher** — HMAC-signed HTTP POST notifications on payment state changes - **Pluggable Storage** — In-memory (dev) and SQLite (production) backends, or bring your own - **x402 Payment Guard** — Protect API routes with HTTP `402 Payment Required`; clients pay DERO and retry with a signed receipt - **Autonomous Agent Payer** — `dero-pay/agent` settles x402 challenges under a deny-by-default spend policy, so AI agents can pay for APIs and MCP tools without a facilitator - **Escrow System** — On-chain smart contract with full escrow lifecycle (deposit, release, refund, dispute, arbitration), with a PREMINT keeper that keeps checkout latency off the hot path - **React Components** — Drop-in ``, ``, `` components - **Next.js Integration** — Ready-made API route handlers and middleware - **XSWD Client** — Browser-side wallet connection for "pay from wallet" UX - **Self-Hosted Dashboard** — Admin UI for invoice management, payment history, wallet status ## How It Works ```mermaid sequenceDiagram participant Merchant participant DeroPay participant Customer participant Wallet as DERO Wallet participant Chain as DERO Network Merchant->>DeroPay: Create invoice (amount, description) DeroPay->>DeroPay: Generate payment ID + integrated address DeroPay-->>Customer: Show payment page (QR, address, timer) Customer->>Wallet: Send DERO to integrated address Wallet->>Chain: Encrypted transaction DeroPay->>Wallet: Poll for matching transactions Wallet-->>DeroPay: Transaction found DeroPay->>DeroPay: Verify amount, track confirmations DeroPay->>Merchant: Fire webhook (HMAC-signed) DeroPay-->>Customer: Payment confirmed ``` ## Invoice States ``` created → pending → confirming → completed ↘ expired ↘ partial (underpaid, still waiting) ``` | State | Description | |---|---| | `created` | Invoice generated, not yet displayed to customer | | `pending` | Awaiting payment, timer running | | `confirming` | Transaction detected, waiting for confirmations (default: 3) | | `completed` | Fully paid and confirmed | | `expired` | Payment window closed (default TTL: 900s / 15 min) | | `partial` | Some DERO received but less than required amount | ## Architecture ``` dero-pay/ ├── core/ # Types, payment ID generation, pricing utilities ├── rpc/ # Wallet & Daemon RPC clients (HTTP JSON-RPC) ├── monitor/ # Payment polling engine with confirmation tracking ├── webhook/ # HMAC-signed webhook dispatcher with retry ├── store/ # Pluggable storage (memory, SQLite) ├── server/ # Invoice engine orchestrator ├── client/ # Browser XSWD client + payment session ├── react/ # React components (Provider, PayWithDero, InvoiceView) ├── next/ # Next.js API handlers + middleware ├── x402/ # Framework-agnostic HTTP 402 primitives + guard ├── agent/ # Autonomous agent-side payer (spend policy, paying-fetch, MCP) ├── escrow/ # On-chain escrow contract wrapper + lifecycle manager ├── router/ # Payment router contract wrapper + manager ├── gateway/ # Standalone gateway server building blocks └── bridge/ # Shared bridge utilities ``` ## Package Exports | Import Path | Description | |---|---| | `dero-pay` | Core types, payment ID generation, pricing utilities (`deroToAtomic`, `formatDero`) | | `dero-pay/rpc` | Wallet and Daemon RPC clients | | `dero-pay/server` | Invoice engine, storage, monitor, webhooks | | `dero-pay/client` | Browser-side XSWD payment client | | `dero-pay/react` | React components and provider | | `dero-pay/next` | Next.js API route handlers and middleware | | `dero-pay/x402` | Framework-agnostic x402 primitives (`withX402`, `build402Response`) + `dero-pay/x402/{types,server,client,next}` | | `dero-pay/agent` | Autonomous agent-side payer — spend policy, paying-fetch, MCP paid-tool guards | | `dero-pay/escrow` | Escrow contract wrapper and lifecycle manager | | `dero-pay/router` | Payment router contract wrapper and manager | | `dero-pay/gateway` | HTTP client for external platforms to talk to a gateway | | `dero-pay/bridge` | Outbound-only daemon for durable, at-least-once payment webhooks | > [!NOTE] > Every subpath is published **dual-format** — an ESM build for `import` and a CommonJS build for `require` — so CommonJS backends (e.g. a Medusa plugin) can consume the SDK directly. ## Next Steps - [Quick Start](/dero-pay/quick-start.md) — Accept your first payment - [Invoice Engine](/dero-pay/invoice-engine.md) — Deep dive into the core - [x402 Payment Guard](/dero-pay/x402.md) — Protect API routes with HTTP 402 - [Autonomous Agent Payer](/dero-pay/agent.md) — Let agents pay x402 challenges automatically - [Escrow System](/escrow/overview.md) — On-chain escrow for trustless transactions --- --- title: "Payment Monitor — Transaction Polling & Confirmation Tracking" description: "The PaymentMonitor polls the DERO wallet for incoming transactions and tracks confirmations until invoices are complete." --- # Payment Monitor The `PaymentMonitor` is the component that watches for incoming DERO payments by polling the wallet RPC. ## How It Works 1. The monitor runs on a configurable interval (default: 5 seconds) 2. Each poll calls `GetTransfers` on the wallet RPC 3. Incoming transfers are matched against pending invoices by **payment ID** 4. When a match is found, the invoice transitions from `pending` → `confirming` 5. The monitor continues tracking until the required number of confirmations is reached 6. Once confirmed, the invoice moves to `completed` ## Configuration The payment monitor is managed by the `InvoiceEngine` — you typically don't create one directly. Configuration is passed through the engine: ```ts const engine = new InvoiceEngine({ walletRpcUrl: "http://127.0.0.1:10103/json_rpc", daemonRpcUrl: "http://127.0.0.1:10102/json_rpc", // Monitor-specific options pollInterval: 5000, // How often to check (ms) confirmationsRequired: 1, // Blocks to wait after detection }); ``` ## Confirmations DERO's DAG-based block structure means confirmations work differently than linear blockchains: - **1 confirmation** (default) — The transaction is included in a block - **Higher values** — More blocks have been mined since inclusion > [!NOTE] > For most use cases, 1 confirmation is sufficient on DERO. The encrypted transaction model and DAG structure make double-spend attacks significantly harder than on transparent chains. ## Partial Payments If a customer sends less DERO than the invoice amount: - The invoice transitions to `partial` status - The received amount is tracked - The monitor continues watching for additional payments - If the total reaches the invoice amount, it moves to `confirming` → `completed` - If the expiry window closes first, it moves to `expired` (with the partial amount recorded) --- --- title: "Prepaid Balances — dero-pay/prepaid" description: "Wallet-funded, prepaid DERO billing for agent-facing APIs. An agent tops up a wallet-scoped balance once, then spends it down per call — no API key to leak, no per-call payment round trip." --- # Prepaid Balances `dero-pay/prepaid` bills a wallet-scoped balance instead of a per-call payment. A wallet owner signs in with [DeroAuth](https://github.com/DHEBP/DeroAuth), funds the balance through a verified DeroPay invoice, and an agent spends it down automatically as it makes calls — the upstream provider credential never leaves your server. > [!NOTE] > This is the pattern sometimes called a **wallet-budget agent**: the agent authenticates with its own wallet instead of an API key, and its spending is bounded by whatever balance you funded — not by a revocable secret an agent could otherwise leak or overspend against. ``` agent your server (dero-pay/prepaid) DERO chain │ sign in (DeroAuth) │ │ ├──────────────────────────▶ session token │ │ POST /top-up │ │ ├──────────────────────────▶ createPrepaidHandlers │ │ { invoice } │ └─ x402 invoice, bound to wallet │ ◀──────────────────────────┤ │ │ pay invoice ─────────────┼──────────────────────────────────────▶ │ │◀── confirmed, verified receipt ──────┤ │ │ └─ PrepaidLedger.credit() (once) │ │ POST /v1/chat/... │ │ ├──────────────────────────▶ createMeteredProxy │ │ │ ├─ reserve() before upstream call │ │ │ ├─ forward to upstream (no creds) │ │ │ └─ capture() actual usage on 200, │ │ { output } │ release() on failure │ ◀──────────────────────────┤ │ ``` ## Installable Exports `dero-pay/prepaid` ships four pieces that compose independently: - **`PrepaidLedger`** — durable atomic-DERO balances on top of the memory or SQLite store: credit, reserve, capture, release, refund, and stale-hold inspection. - **`createPrepaidHandlers`** — top-up, balance, and transaction-history route handlers for a Next.js (or any fetch-Request-based) backend. - **`createPrepaidClient`** — the agent-side client: sign in, top up, check balance, list transactions. - **`createMeteredProxy`** — an allowlisted upstream proxy that reserves funds before forwarding a request and never forwards the wallet's own auth headers, cookies, or DeroPay payment headers to the upstream. ## Quick Setup ### Wire up the ledger and handlers ```ts // lib/prepaid.ts const store = new SqliteInvoiceStore({ path: "deropay.sqlite" }); export const ledger = new PrepaidLedger({ store }); export const prepaidHandlers = createPrepaidHandlers({ ledger, authenticate: verifyDeroAuthBearer, // your DeroAuth session verifier walletRpcUrl: process.env.DERO_WALLET_RPC!, daemonRpcUrl: process.env.DERO_DAEMON_RPC!, }); ``` ### Expose the top-up, balance, and transaction routes ```ts // app/api/v1/x402/top-up/route.ts export const POST = prepaidHandlers.topUpHandler; ``` ```ts // app/api/v1/x402/balance/[walletAddress]/route.ts export const GET = prepaidHandlers.balanceHandler; ``` ```ts // app/api/v1/x402/transactions/[walletAddress]/route.ts export const GET = prepaidHandlers.transactionsHandler; ``` ### Meter an upstream route ```ts export const meteredGateway = createMeteredProxy({ ledger, authenticate: verifyDeroAuthBearer, allowedUpstreamOrigins: ["https://api.your-inference-provider.com"], adapters: [chatAdapter], // computes the atomic charge for a given call }); ``` ## Topping Up (Agent Side) ```ts const client = createPrepaidClient({ baseUrl: "https://your-gateway.example.com", walletAddress: "deroi...", getAuthToken: () => session.token, }); // Generate the key ONCE per logical top-up attempt. const idempotencyKey = createTopUpIdempotencyKey(); await client.topUp(deroToAtomic("5.0"), idempotencyKey); ``` > [!WARNING] > **`idempotencyKey` is required, and it must be generated once per logical attempt — never once per HTTP call.** If a `topUp()` call times out after the payment actually landed server-side, retry it with the **same** `idempotencyKey`, not a fresh one. Reusing the key is what makes the retry safe; generating a new key on every call (including retries) defeats idempotency and can double-credit the wallet. ## Checking Balance and History ```ts const balance = await client.getBalance(); // => { balanceAtomic: "450000000000", reservedAtomic: "0" } const { transactions } = await client.getTransactions({ limit: 20 }); ``` ## How a Metered Call Is Charged `createMeteredProxy` never trusts the upstream response for cost before the fact: 1. **Reserve** — before contacting the upstream, the proxy places a hold for the route adapter's estimated maximum charge. If the balance can't cover it, the call is rejected before any upstream request is made. 2. **Forward** — the request goes to the allowlisted upstream origin only. The wallet's session token, cookies, and any `X-DeroPay-*` headers are stripped, not forwarded. 3. **Settle** — on a successful response, the adapter computes the actual usage-based charge and the proxy **captures** up to (never more than) the reserved amount. On a connection failure or non-2xx response, the hold is **released** in full. This reserve-then-capture order means a wallet is never charged for a call that failed, and never charged more than what was reserved up front. ## Security Notes - **Top-up credit is bound to a verified on-chain payment**, not merely to an idempotency key — the handler checks the paid invoice's resource/amount against what's being redeemed before crediting, so replaying an idempotency key without a matching new payment does not mint additional balance. - **`createMeteredProxy` strips credentials before forwarding.** The upstream never sees the wallet's DeroAuth session, cookies, or DeroPay-internal headers. - **Test your own receipt verification against a forged or under-confirmed receipt**, not just the happy path — a self-signed test receipt (signed with the same secret the handler verifies against) cannot catch a broken signature check. ## Example App A complete DeroAuth + prepaid + metered-inference gateway (chat, embeddings, image, audio, video adapters, plus a configurable rate card) is available in the monorepo: ``` apps/x402-example/ ``` Run it from the monorepo root: ```bash bun run dev:x402-example ``` --- --- title: "DeroPay Quick Start — Accept DERO Payments" description: "Get started with DeroPay in under 5 minutes. Create invoices, monitor payments, and handle webhooks." --- # Quick Start Accept your first DERO payment in under 5 minutes. ## Install ```bash bun add dero-pay ``` ```bash npm install dero-pay ``` ```bash pnpm add dero-pay ``` > [!NOTE] > Latest published release: **`dero-pay@0.5.1`** — ESM + CommonJS, no sourcemaps. See the [release notes](https://github.com/DHEBP/DeroPay/releases). Installs are unpinned above by design; pin a version if you need reproducible builds. ## Server-Side Usage ### Create an Invoice Engine ```ts const engine = new InvoiceEngine({ walletRpcUrl: "http://127.0.0.1:10103/json_rpc", daemonRpcUrl: "http://127.0.0.1:10102/json_rpc", webhookUrl: "https://mystore.com/webhooks/dero", webhookSecret: process.env.WEBHOOK_SECRET!, defaultTtlSeconds: 900, // 15 min invoice window defaultRequiredConfirmations: 3, // blocks to wait }); await engine.start(); ``` ### Create an Invoice ```ts const invoice = await engine.createInvoice({ name: "Widget Pro", description: "Premium widget subscription", amount: deroToAtomic("5.0"), // 5 DERO (500,000 atomic units) }); console.log(invoice.integratedAddress); // Customer sends DERO here console.log(invoice.id); // Track status with this ID ``` ### Listen for Payment Events ```ts engine.on("invoiceStatusChanged", (invoice, previousStatus) => { console.log(`Invoice ${invoice.id}: ${previousStatus} → ${invoice.status}`); if (invoice.status === "completed") { // Fulfill the order } }); ``` ## Next.js App Router DeroPay provides ready-made route handlers for Next.js: ```ts // app/api/pay/create/route.ts const { createInvoiceHandler, statusHandler } = createPaymentHandlers({ walletRpcUrl: "http://127.0.0.1:10103/json_rpc", daemonRpcUrl: "http://127.0.0.1:10102/json_rpc", }); export const POST = createInvoiceHandler; ``` ```ts // app/api/pay/status/route.ts export const GET = statusHandler; ``` ## React Components Drop-in components for your payment UI: ```tsx function PaymentPage({ invoiceId }: { invoiceId: string }) { return ( console.log("Paid!")} /> ); } ``` > [!NOTE] > The `` button connects to the customer's local DERO wallet via XSWD, letting them pay directly from their wallet with one click. ## What's Next - [Invoice Engine](/dero-pay/invoice-engine.md) — Full engine configuration and lifecycle - [Webhooks](/dero-pay/webhooks.md) — HMAC-signed webhook verification - [React Components](/dero-pay/react.md) — All available components and props - [Next.js Integration](/dero-pay/nextjs.md) — Complete Next.js setup guide --- --- title: "React Components — Payment UI" description: "Drop-in React components for DeroPay: PayWithDero button, InvoiceView, PaymentStatus, and DeroPayProvider." --- # React Components DeroPay provides drop-in React components for building payment UIs. ```tsx ``` ## DeroPayProvider Wraps your app (or payment section) to provide DeroPay context: ```tsx function App({ children }) { return ( {children} ); } ``` | Prop | Type | Description | |---|---|---| | `appName` | `string` | Your app name (shown in wallet connection prompts) | | `statusEndpoint` | `string` | API endpoint for invoice status polling | | `children` | `ReactNode` | Child components | ## InvoiceView Displays a complete payment interface with QR code, address, amount, and countdown: ```tsx router.push("/thank-you")} onExpired={() => console.log("Payment expired")} /> ``` | Prop | Type | Description | |---|---|---| | `invoiceId` | `string` | The invoice ID to display | | `onCompleted` | `() => void` | Callback when payment completes | | `onExpired` | `() => void` | Callback when invoice expires | | `showQR` | `boolean` | Show QR code (default: `true`) | | `className` | `string` | Additional CSS classes | ## PayWithDero A button that connects to the customer's local DERO wallet via XSWD and initiates a direct payment: ```tsx console.log("Payment sent!")} onError={(err) => console.error(err)} /> ``` | Prop | Type | Description | |---|---|---| | `invoiceId` | `string` | The invoice to pay | | `onSuccess` | `() => void` | Callback on successful send | | `onError` | `(error: Error) => void` | Callback on failure | | `label` | `string` | Button text (default: "Pay with DERO") | | `className` | `string` | Additional CSS classes | > [!NOTE] > The `PayWithDero` button uses the **XSWD protocol** to connect to the customer's locally-running DERO wallet (Engram or CLI). The customer approves the transaction in their wallet — no browser extension needed. ## PaymentStatus A compact status indicator: ```tsx ``` Shows the current state with appropriate icons and colors (pending, confirming, completed, expired). ## EscrowInvoiceView A specialized view for escrow-backed invoices, showing deposit status, escrow state, and resolution controls: ```tsx console.log("Funds released")} /> ``` ## useDeroPayContext Hook for building fully custom payment UIs: ```tsx "use client"; function CustomPaymentUI({ invoiceId }: { invoiceId: string }) { const ctx = useDeroPayContext(); // Access provider state and methods } ``` ## Full Example ```tsx function PaymentPage({ invoiceId }: { invoiceId: string }) { return (

Complete Your Payment

console.log("Paid!")} />
); } ``` --- --- title: "RPC Clients — DERO Wallet & Daemon Communication" description: "DeroPay provides typed JSON-RPC clients for the DERO wallet and daemon, handling all low-level communication." --- # RPC Clients DeroPay provides typed JSON-RPC clients for communicating with the DERO wallet and daemon. ```ts ``` ## Wallet RPC Client The wallet client handles payment-related operations: ```ts const wallet = new WalletRpcClient({ url: "http://127.0.0.1:10103/json_rpc", // Optional: auth: { username: "", password: "" } }); // Get wallet address const address = await wallet.getAddress(); // Get balance const balance = await wallet.getBalance(); // Get incoming transfers const transfers = await wallet.getTransfers({ in: true, minHeight: 1000000, }); // Make an integrated address (with embedded payment ID) const integrated = await wallet.makeIntegratedAddress({ paymentId: "abc123...", }); // Split an integrated address back to base address + payment ID const { address, paymentId } = await wallet.splitIntegratedAddress({ integratedAddress: "deri1q...", }); // Send DERO const result = await wallet.transfer({ destinations: [{ address: "dero1q...", amount: 500_000n }], // 5 DERO }); ``` ## Daemon RPC Client The daemon client handles blockchain queries: ```ts const daemon = new DaemonRpcClient({ url: "http://127.0.0.1:10102/json_rpc", }); // Get daemon info (height, version, network) const info = await daemon.getInfo(); // Get transaction details const tx = await daemon.getTransaction({ txid: "..." }); // Get smart contract state const sc = await daemon.getSC({ scid: "...", code: true }); // Install a smart contract const result = await daemon.installSC({ code: "..." }); // Invoke a smart contract function const invokeResult = await daemon.invokeSC({ scid: "...", sc_rpc: [{ name: "entrypoint", datatype: "S", value: "Release" }], }); ``` ## Configuration Types ```ts // Both accept: // { url: string; auth?: { username: string; password: string } } ``` > [!NOTE] > The `InvoiceEngine` uses these clients internally — you typically don't need to use them directly unless you're building custom payment flows or need direct access to the DERO node. ## Connection Both clients communicate over HTTP JSON-RPC 2.0. Make sure the wallet and daemon are running with their RPC servers enabled: ```bash # Wallet (mainnet) dero-wallet-cli --rpc-server --rpc-bind=127.0.0.1:10103 # Daemon (mainnet) derod --rpc-bind=127.0.0.1:10102 ``` --- --- title: "Storage Backends — Invoice Persistence" description: "DeroPay supports pluggable storage backends. Use in-memory for development and SQLite for production, or implement your own." --- # Storage Backends DeroPay uses a pluggable storage system for invoice persistence. Two backends are included, and you can implement your own. ## Built-in Backends The default backend — stores everything in memory. Data is lost when the process restarts. ```ts // MemoryInvoiceStore is the default — no config needed const engine = new InvoiceEngine({ walletRpcUrl: "http://127.0.0.1:10103/json_rpc", daemonRpcUrl: "http://127.0.0.1:10102/json_rpc", // store defaults to new MemoryInvoiceStore() }); ``` Good for: development, testing, prototyping. Persistent storage using SQLite via `better-sqlite3`. Invoices survive restarts. ```ts const store = new SqliteInvoiceStore({ path: "./data/invoices.db" }); const engine = new InvoiceEngine({ walletRpcUrl: "http://127.0.0.1:10103/json_rpc", daemonRpcUrl: "http://127.0.0.1:10102/json_rpc", store, }); ``` > [!NOTE] > `better-sqlite3` is an optional peer dependency. Install it separately: > ```bash > bun add better-sqlite3 > ``` ## Custom Storage Implement the `InvoiceStore` interface to use any database: ```ts const myStore: InvoiceStore = { async save(invoice: Invoice): Promise { // Persist the invoice }, async get(id: string): Promise { // Retrieve by ID }, async getByPaymentId(paymentId: string): Promise { // Retrieve by payment ID (used by the monitor) }, async update(id: string, updates: Partial): Promise { // Update invoice fields }, async list(filter?: InvoiceFilter): Promise { // List invoices with optional filtering }, }; ``` This makes it straightforward to use PostgreSQL, MySQL, Redis, or any other storage backend your application already uses. --- --- title: "Webhooks — HMAC-Signed Payment Notifications" description: "DeroPay sends HMAC-SHA256 signed webhooks on payment state changes. Learn how to configure and verify them." --- # Webhooks DeroPay sends HMAC-SHA256 signed HTTP POST notifications when invoice states change. This lets your backend react to payments in real-time. ## Configuration ```ts const engine = new InvoiceEngine({ walletRpcUrl: "http://127.0.0.1:10103/json_rpc", daemonRpcUrl: "http://127.0.0.1:10102/json_rpc", webhookUrl: "https://mystore.com/webhooks/dero", webhookSecret: process.env.WEBHOOK_SECRET!, }); ``` ## Webhook Payload When an invoice status changes, DeroPay POSTs a JSON payload: ```json { "event": "invoice.completed", "invoiceId": "inv_abc123", "status": "completed", "previousStatus": "confirming", "amount": 5000000000000, "paidAmount": 5000000000000, "timestamp": "2026-02-16T12:00:00.000Z", "metadata": { "orderId": "ORD-123" } } ``` ## Signature Verification Every webhook includes an `X-DeroPay-Signature` header containing the HMAC-SHA256 signature of the request body. **Always verify webhooks before processing:** ```ts // In your webhook handler export async function POST(request: Request) { const body = await request.text(); const signature = request.headers.get("X-DeroPay-Signature")!; const isValid = verifyWebhookSignature( body, signature, process.env.WEBHOOK_SECRET! ); if (!isValid) { return new Response("Invalid signature", { status: 401 }); } const event = JSON.parse(body); switch (event.event) { case "invoice.completed": // Fulfill the order break; case "invoice.expired": // Handle expiration break; case "invoice.partial": // Handle underpayment break; } return new Response("OK", { status: 200 }); } ``` > [!WARNING] > **Security**: Always verify the webhook signature. Without verification, an attacker could send fake payment confirmations to your endpoint. ## Webhook Events | Event | Trigger | |---|---| | `invoice.pending` | Invoice is awaiting payment | | `invoice.confirming` | Transaction detected, waiting for confirmations | | `invoice.completed` | Payment fully confirmed | | `invoice.expired` | Payment window closed | | `invoice.partial` | Underpayment detected | ## Retry Behavior If your webhook endpoint returns a non-2xx status code, DeroPay will retry with exponential backoff. Failed webhooks are logged and can be inspected in the merchant dashboard. --- --- title: "x402 Payment Guard — DERO-Native HTTP 402 for APIs" description: "Protect API routes with HTTP 402 Payment Required. Clients pay DERO, receive a signed receipt, and retry. No accounts, no API keys, no subscriptions." --- # x402 Payment Guard DeroPay implements the [x402 protocol](https://x402.org/) natively for DERO — protect any API route or resource with an HTTP `402 Payment Required` challenge. Clients pay DERO, receive a signed receipt, and retry. No accounts, no API keys, no subscriptions required. > [!NOTE] > This is a DERO-native implementation. It uses the x402 HTTP pattern (challenge → pay → retry with proof) but settles in DERO via DeroPay's invoice and monitoring infrastructure, not EVM stablecoins. ## Installable Exports The x402 rail ships as published subpaths of `dero-pay` — no monorepo checkout or in-tree build step: - **`dero-pay/next`** — `createX402RouteGuard` + `createPaymentHandlers`, the App Router guard used throughout this page. - **`dero-pay/x402`** — framework-agnostic x402 primitives (`withX402`, `build402Response`, `parsePaymentHeader`, `FacilitatorHttpClient`) plus the spend-policy and paying-fetch toolkit, for non-Next stacks. Also exposed as focused subpaths: `dero-pay/x402/{types,server,client,next}`. - **`dero-pay/agent`** — the autonomous agent-side payer that settles these challenges without a facilitator. See [Autonomous Agent Payer](/dero-pay/agent.md). Every subpath ships **dual-format** (ESM `import` and CommonJS `require`), so CommonJS backends can consume the same guard. ## How It Works ```mermaid sequenceDiagram participant Client as Client / AI Agent participant API as Protected API Route participant DeroPay participant Chain as DERO Network Client->>API: GET /api/protected/report API-->>Client: 402 Payment Required + challenge (invoice, address, amount) Client->>Chain: Send DERO to integrated address DeroPay->>Chain: Monitor for payment + confirmations Client->>DeroPay: POST /api/pay/receipts/issue (invoiceId) DeroPay-->>Client: Signed receipt token Client->>API: GET /api/protected/report + X-DeroPay-Receipt: API-->>Client: 200 OK + protected content ``` The critical design choice: the protected route **never touches the chain on the hot path**. Receipt verification is a local HMAC signature check — fast, reliable, and scales horizontally. ## Quick Setup ### Install and configure ```ts // lib/deropay.ts export const paymentHandlers = createPaymentHandlers({ walletRpcUrl: process.env.DERO_WALLET_RPC!, daemonRpcUrl: process.env.DERO_DAEMON_RPC!, receiptSecret: process.env.DEROPAY_RECEIPT_SECRET!, }); export const x402Guard = createX402RouteGuard({ getEngine: paymentHandlers.getEngine, receiptSecret: process.env.DEROPAY_RECEIPT_SECRET!, policy: { name: "Premium API Access", amountAtomic: deroToAtomic("0.10"), requiredConfirmations: 3, resource: "/api/protected/report", }, }); ``` ### Protect a route ```ts // app/api/protected/report/route.ts export const GET = x402Guard(async () => { return Response.json({ report: "paid content" }); }); ``` ### Expose receipt endpoints ```ts // app/api/pay/receipts/issue/route.ts export const POST = paymentHandlers.issueReceiptHandler; ``` ```ts // app/api/pay/receipts/verify/route.ts export const POST = paymentHandlers.verifyReceiptHandler; ``` ### Add environment variables ```env DERO_WALLET_RPC=http://127.0.0.1:10103/json_rpc DERO_DAEMON_RPC=http://127.0.0.1:10102/json_rpc DEROPAY_RECEIPT_SECRET=your-signing-secret-here ``` ## The Challenge Response When a client hits a protected route without a valid receipt, they receive: ```http HTTP/1.1 402 Payment Required Content-Type: application/json WWW-Authenticate: X402 protocol="x402-deropay-draft", asset="DERO", network="dero-mainnet", amount="100000000000", invoice_id="inv_abc123", address="deroi...", resource="/api/protected/report" Cache-Control: no-store ``` ```json { "error": "payment_required", "payment": { "protocol": "x402-deropay-draft", "network": "dero-mainnet", "asset": "DERO", "amountAtomic": "100000000000", "amountDisplay": "0.1", "invoiceId": "inv_abc123", "integratedAddress": "deroi...", "expiresAt": "2026-04-04T12:10:00.000Z", "requiredConfirmations": 3, "resource": "/api/protected/report" } } ``` Both the JSON body and the `WWW-Authenticate` header carry the same information — machine clients can use either. ## Retrying with a Receipt After payment is confirmed, issue a receipt and retry the request with one of these headers: ```bash # Option 1: DeroPay-native header curl https://your-api.com/api/protected/report \ -H "X-DeroPay-Receipt: " # Option 2: Standard Authorization header curl https://your-api.com/api/protected/report \ -H 'Authorization: X402 proof=""' ``` Both are accepted. The `Authorization` form improves compatibility with HTTP clients and agent frameworks that expect standard auth headers. Build the authorization header programmatically: ```ts const headerValue = formatX402AuthorizationHeader(receiptToken); // => X402 proof="" ``` ## Issuing a Receipt After the invoice completes, call the issue endpoint: ```bash curl -X POST https://your-api.com/api/pay/receipts/issue \ -H "Content-Type: application/json" \ -d '{ "invoiceId": "inv_abc123", "resource": "/api/protected/report", "ttlSeconds": 600 }' ``` Response: ```json { "token": "eyJhbGciOi...", "claims": { "invoiceId": "inv_abc123", "resource": "/api/protected/report", "amountAtomic": "100000000000", "expiresAt": 1712232600000 } } ``` ## Dynamic Pricing For metered APIs (LLM inference, compute, storage), use `X402PolicyResolver` — a function that reads the request and returns a policy per call: ```ts export const meteredGuard = createX402RouteGuard({ getEngine: paymentHandlers.getEngine, receiptSecret: process.env.DEROPAY_RECEIPT_SECRET!, policy: async (request) => { const url = new URL(request.url); const tokens = Number(url.searchParams.get("tokens") ?? "1000"); // Price per 100k tokens = 0.1 DERO const amountAtomic = deroToAtomic((tokens / 100_000).toFixed(5)); return { name: "Metered inference request", amountAtomic, resource: "/api/protected/inference", metadata: { tokens }, }; }, }); ``` ```ts // app/api/protected/inference/route.ts export const POST = meteredGuard(async (request) => { const { prompt } = await request.json(); // ... call your model return Response.json({ output: "..." }); }); ``` ## Usage Quotas Add route-level caps directly to the policy to prevent abuse without a separate rate-limiter: ```ts export const x402Guard = createX402RouteGuard({ getEngine: paymentHandlers.getEngine, receiptSecret: process.env.DEROPAY_RECEIPT_SECRET!, policy: { name: "Premium API Access", amountAtomic: deroToAtomic("0.10"), requiredConfirmations: 3, resource: "/api/protected/report", // Max 500 receipt uses per UTC day across this route maxReceiptsPerDay: 500, // Max 10 DERO total per rolling hour maxAtomicPerWindow: { amountAtomic: deroToAtomic("10"), windowSeconds: 3600, }, }, }); ``` When a quota is exceeded, the guard returns `429 Too Many Requests` and emits an audit event. > [!WARNING] > **Multi-instance deployments:** Quota enforcement requires a shared persistent store. Each instance must use the same `SqliteInvoiceStore` (or a custom shared backend) — per-instance in-memory stores will each enforce limits independently, allowing more traffic than intended. ## Replay Protection Enable single-use receipts to prevent a token from being reused after the first successful request: ```ts export const x402Guard = createX402RouteGuard({ getEngine: paymentHandlers.getEngine, receiptSecret: process.env.DEROPAY_RECEIPT_SECRET!, enforceSingleUseReceipts: true, // mark jti as used after first use policy: { name: "One-time report download", amountAtomic: deroToAtomic("1.0"), resource: "/api/protected/report", }, }); ``` Each receipt contains a unique `jti` (JWT ID). When single-use mode is enabled, the store marks the `jti` on first use — subsequent submissions of the same token return `409 Conflict`. This requires the store to implement `markReceiptJtiUsed`. Both `MemoryInvoiceStore` and `SqliteInvoiceStore` support this. ## Key Rotation Run two signing keys simultaneously for zero-downtime secret rotation: ```ts export const x402Guard = createX402RouteGuard({ getEngine: paymentHandlers.getEngine, // Map of keyId → secret receiptSecrets: { "v2": process.env.DEROPAY_RECEIPT_SECRET_V2!, "v1": process.env.DEROPAY_RECEIPT_SECRET_V1!, // keep during rotation window }, policy: { ... }, }); ``` ```ts // When issuing receipts, specify the active key export const paymentHandlers = createPaymentHandlers({ walletRpcUrl: process.env.DERO_WALLET_RPC!, daemonRpcUrl: process.env.DERO_DAEMON_RPC!, receiptSecret: process.env.DEROPAY_RECEIPT_SECRET_V2!, receiptKeyId: "v2", // embeds kid in new receipts }); ``` The guard verifies against all keys in the keyring. Old receipts signed with `v1` keep working until they expire. Once no `v1` receipts remain in circulation, remove it from the keyring. ## Audit Events Every x402 lifecycle event fires a structured `x402Audit` event on the `InvoiceEngine` event bus: ```ts const engine = new InvoiceEngine({ ... }); engine.on("x402Audit", (event) => { // pipe to your log aggregator, SIEM, or alerting system console.log("[x402]", JSON.stringify(event)); }); ``` | Event type | When it fires | |---|---| | `x402.challenge_issued` | A new 402 challenge was returned (invoice created) | | `x402.receipt_issued` | A signed receipt was issued after payment confirmed | | `x402.receipt_used` | A valid receipt was accepted and the handler was called | | `x402.receipt_rejected` | A receipt failed verification (see `reason` field) | The `reason` field on `receipt_rejected` events: | Reason | Meaning | |---|---| | `invalid_or_expired_receipt` | Signature invalid, token expired, or resource mismatch | | `receipt_replay_detected` | `jti` already marked as used (replay attempt) | | `receipt_daily_quota_exceeded` | `maxReceiptsPerDay` limit hit | | `atomic_window_quota_exceeded` | `maxAtomicPerWindow` limit hit | | `replay_store_not_configured` | Single-use mode enabled but store lacks `markReceiptJtiUsed` | | `quota_store_not_configured` | Quota policy set but store lacks `reserveX402Usage` | ## Dynamic Order IDs (Replay Protection for Custom Facilitators) > [!WARNING] > If you build a **custom facilitator or non-Next integration** on top of `dero-pay/x402`'s primitives directly (rather than `createX402RouteGuard`), read this section — it closes a real replay path. Every x402 payment is looked up on-chain by an `orderId`. If your integration hard-codes a single static `orderId` for a route — the obvious way to wire this up — every request to that route shares the same on-chain payment slot. The slot pays exactly once; every subsequent presentation of that same, now-public, payment tuple is a valid-looking free replay for anyone within the receipt's TTL. Pass an `orderIdMinter` to mint a fresh, server-issued order id per request instead: ```ts export const x402Guard = createX402RouteGuard({ getEngine: paymentHandlers.getEngine, receiptSecret: process.env.DEROPAY_RECEIPT_SECRET!, orderIdMinter: createOrderIdMinter(process.env.DEROPAY_ORDER_ID_SECRET!), policy: { name: "Premium API Access", amountAtomic: deroToAtomic("0.10"), requiredConfirmations: 3, resource: "/api/protected/report", }, }); ``` The minted id is `.` — stateless, so it validates across multiple server instances with no shared cache, and a caller cannot forge a server-issued id without the secret. **Leaving `orderIdMinter` unset is safe only if your own server already issues a distinct `orderId` out-of-band for every unit of work.** `createX402RouteGuard`'s default policy does this correctly; only worry about this if you're calling the lower-level `dero-pay/x402` primitives yourself. ## Payer Privacy > [!WARNING] > x402 payments settle at **ring size 2** and write the payer's plaintext DERO address into public contract state. **This is not sender-anonymous.** Do not describe x402 payments as private or anonymous to end users — only the amount and resource are hidden from casual observers, not the payer's identity. ## Security Summary | Layer | Mechanism | |---|---| | Authenticity | HMAC-SHA256 signed receipt — tampered tokens fail immediately | | Expiry | Short-lived TTL (default 10 min) — old tokens stop working automatically | | Resource binding | Receipt for `/report` cannot unlock `/inference` | | Replay protection | Optional `jti` one-time-use enforcement via store | | Amount enforcement | Receipt must satisfy `minAmountAtomic` for the route's policy | | Key rotation | `kid` in receipt header + keyring verification | | Quota enforcement | Store-backed usage reservations with atomic window tracking | | Audit trail | Structured events on every action for SIEM/logging | ## Example App A fully runnable Next.js example demonstrating both a static paid report route and a dynamic metered inference route is available in the monorepo: ``` apps/x402-example/ ``` Run it from the monorepo root: ```bash bun run dev:x402-example ``` Then open `http://localhost:3000`. --- --- title: "PREMINT Keeper — Pre-Minted Escrow Inventory" description: "The EscrowKeeper keeps a pool of pre-minted, confirmed empty escrow boxes stocked so checkout only has to bind order terms — moving the ~1-block mint-confirm latency off the hot path." --- # PREMINT Keeper Escrow deploys a fresh contract per transaction. Minting that contract and waiting for it to confirm takes about one block (~18s on mainnet), and doing it inline means every buyer waits through it at checkout. The **PREMINT keeper** removes that wait: a background loop mints empty escrow boxes *ahead of demand*, and checkout claims a pre-confirmed box and only has to **bind** the order terms. ``` mint (empty box) ──▶ confirm (GetSC gate) ──▶ pool: ready ──▶ claimEscrow binds terms keeper loop keeper loop (fast — no mint wait) ``` > [!NOTE] > The keeper is optional. Omit it and `EscrowManager` keeps its original mint-on-demand behavior. When the pool is empty, checkout also falls back to inline mint automatically — the keeper is a latency optimization, never a hard dependency. ## Enabling It Pass an inventory store to `EscrowManager`. When present, the manager builds a background `EscrowKeeper` that mints into that store; `claimEscrow` then binds a pooled box instead of minting inline. ```ts const manager = new EscrowManager({ walletRpcUrl: process.env.DERO_WALLET_RPC!, daemonRpcUrl: process.env.DERO_DAEMON_RPC!, // Turn on the PREMINT keeper by giving it an inventory store. // SqliteEscrowInventoryStore wraps a better-sqlite3 Database instance. escrowInventory: new SqliteEscrowInventoryStore(new Database("./escrow-inventory.db")), // Optional pool tuning (defaults shown). keeperOptions: { targetReady: 5, // confirmed boxes to keep on hand refillBelow: 2, // refill when ready count drops below this pollMs: 10_000, // interval between confirm + refill ticks }, }); await manager.start(); // starts the keeper loop (no-op if no inventory store) ``` Two inventory stores ship in `dero-pay/escrow`: | Store | Use | |---|---| | `MemoryEscrowInventoryStore` | Development / tests, single process only — `new MemoryEscrowInventoryStore()` | | `SqliteEscrowInventoryStore` | Production — pops boxes atomically across processes; wraps a `better-sqlite3` `Database` instance | > [!NOTE] > `better-sqlite3` is an optional peer dependency (`bun add better-sqlite3`). `SqliteEscrowInventoryStore` takes a constructed `Database` instance — `new SqliteEscrowInventoryStore(new Database("./escrow-inventory.db"))` — not a path string (unlike `SqliteInvoiceStore`, which takes `{ path }`). > [!WARNING] > **Multi-process deployments need a durable, atomically-popping store.** Two workers must never claim the same box. `SqliteEscrowInventoryStore` reports `durable = true`; the in-memory store does not and is safe only for a single process. The engine asserts durability for multi-process use. ## How the Pool Works Each keeper tick does two things: confirm minted boxes, then refill if the pool is low. Boxes move through three states: | State | Meaning | |---|---| | `minted` | SCID broadcast, not yet confirmed on-chain | | `confirmed` | Passed the GetSC gate — empty, owned by us, pool-ready | | `claimed` | Popped by a checkout to bind | **Confirm gate.** A minted box becomes pool-ready **only** once `GetSC` proves it reads back as `owner set` + `bound == 0` + `statusCode == 0` — empty and ours. Anything else stays `minted` and is retried next tick, so an unconfirmed SCID is never handed to a checkout (binding one would fail with "SC not found"). **Refill without over-minting.** When the ready count drops below `refillBelow`, the keeper mints up to `targetReady`. The deficit subtracts *both* confirmed and still-in-flight minted boxes, so a burst of ticks before the first mint confirms does not over-mint the pool. **Failed-bind recovery.** If a bind fails after a box is popped, the box is returned to the keeper (not leaked in the `claimed` state). The next tick re-verifies it via `GetSC` and either re-pools it (bind never landed) or retires it (bind landed, `bound != 0`). ## The Trap — Same Owner Mints and Binds The escrow contract's `Bind` is owner-gated (`IF SIGNER() != LOAD("owner")`), and `Initialize` sets `owner = SIGNER()` at mint. **A box minted by wallet A can only be bound by wallet A.** The keeper therefore mints with the manager's *own* `EscrowContract` instance — the same platform wallet that binds in `claimEscrow`. Do not hand the keeper a second signer. > [!WARNING] > One platform wallet mints **and** binds. Splitting the minter and the binder across two wallets breaks the owner-gated `Bind` — every pooled box would be unbindable. ## Monitoring Stock The exported `EscrowKeeper` class exposes `readyCount()` and emits events (`boxConfirmed`, `boxMinted`, `error`) so an app can alert on low inventory. Reading the store directly also works, since the same `EscrowInventoryStore` the manager mints into is yours to query: ```ts const inventory = new SqliteEscrowInventoryStore(new Database("./escrow-inventory.db")); const manager = new EscrowManager({ // ... escrowInventory: inventory, }); // Alert on low stock straight from the shared store. const ready = await inventory.countReady(); if (ready < 2) alertOncall("escrow pool low"); ``` > [!NOTE] > `EscrowManager` builds and owns its keeper internally. To wire keeper *events* (`boxConfirmed` / `boxMinted` / `error`) you can construct an `EscrowKeeper` directly against the same `EscrowContract` and store — but the minter must remain the platform wallet (see The Trap above). Keeper ticks are non-fatal: a mint RPC error or `GetSC` error emits `error` and the loop continues on the next tick. ## See Also - [Escrow Overview](/escrow/overview.md) — the full escrow lifecycle - [TypeScript SDK](/escrow/sdk.md) — `EscrowContract` and `EscrowManager` --- --- title: "Escrow System — Trustless On-Chain Payments" description: "DeroPay includes a full escrow system with an on-chain smart contract and TypeScript SDK for trustless buyer-seller transactions." --- # Escrow System DeroPay includes a full on-chain escrow system for trustless buyer-seller transactions. Funds are held in a DERO smart contract and released based on predefined conditions. ## Why Escrow? Standard invoices work for trusted merchants, but many scenarios require a neutral third party: - **Marketplaces** — Buyer pays, seller ships, funds release on confirmation - **Freelance work** — Client deposits, freelancer delivers, funds release on approval - **P2P trades** — Neither party trusts the other; contract holds funds until both agree - **Dispute resolution** — An arbitrator can intervene if buyer and seller disagree ## How It Works ```mermaid stateDiagram-v2 [*] --> AwaitingDeposit: Create escrow AwaitingDeposit --> Funded: Buyer deposits Funded --> Released: Seller confirmed, funds released Funded --> Refunded: Buyer requests refund (before expiry) Funded --> Expired: Time limit reached Funded --> Disputed: Either party disputes Disputed --> Arbitrated: Arbitrator resolves ``` ## Status Codes | Code | Status | Description | |---|---|---| | 0 | `awaiting_deposit` | Escrow created, waiting for buyer's DERO | | 1 | `funded` | Buyer has deposited, awaiting seller action | | 2 | `released` | Funds sent to seller | | 3 | `refunded` | Funds returned to buyer | | 4 | `expired_claimed` | Time limit reached, seller claimed funds | | 5 | `disputed` | Dispute raised, awaiting arbitrator | | 6 | `arbitrated` | Arbitrator has resolved the dispute | ## Components ### Smart Contract (`escrow.bas`) A DERO smart contract written in BASIC that holds funds and enforces escrow rules. See [Smart Contract](/escrow/smart-contract.md). ### TypeScript SDK - **`EscrowContract`** — Low-level wrapper around the contract's RPC functions - **`EscrowManager`** — High-level lifecycle manager that handles the full escrow flow See [TypeScript SDK](/escrow/sdk.md). ### Pre-Minted Inventory (Keeper) Escrow deploys a fresh contract per transaction, and minting a contract takes about one block to confirm. To keep that latency off the checkout path, `EscrowManager` can run a background **PREMINT keeper** that mints empty escrow boxes ahead of demand and hands a confirmed one to each checkout — so checkout only has to *bind* the order terms, not mint-confirm-bind. See [PREMINT Keeper](/escrow/keeper.md). > [!NOTE] > The escrow contract supports **platform fees** (in basis points), so marketplaces can take a cut of each transaction automatically at the contract level. > [!NOTE] > **Refund safety is verified on mainnet.** In an end-to-end test on DERO mainnet (block 7,348,673), a rejected/underpaid deposit refunded the buyer and left the escrow box fund-safe and reusable. That test exercises the reject/underpay path specifically — strong evidence for it, not a blanket guarantee for every failure mode. > [!WARNING] > Escrow deploys a **fresh contract for each transaction**. If you need fast, single-transaction payments without buyer protection, see the [Payment Router](/payment-router/overview.md) instead. For a full comparison, see [Escrow vs. Router](/payment-router/escrow-vs-router.md). --- --- title: "Escrow TypeScript SDK — EscrowContract & EscrowManager" description: "Use the dero-pay/escrow module to create, fund, release, refund, and arbitrate escrow transactions programmatically." --- # Escrow TypeScript SDK The `dero-pay/escrow` module provides two levels of abstraction for working with escrow: ```ts ``` ## EscrowManager (High-Level) The recommended way to work with escrow. Manages the full lifecycle: ```ts const escrow = new EscrowManager({ walletRpcUrl: "http://127.0.0.1:10103/json_rpc", daemonRpcUrl: "http://127.0.0.1:10102/json_rpc", }); // Create a new escrow const escrowId = await escrow.create({ buyerAddress: "dero1qy...", sellerAddress: "dero1qx...", amount: 500_000, // 5 DERO in atomic units (100,000 per DERO) expirySeconds: 86400, // 24 hours platformFeeBps: 0, // No platform fee (set 250 for 2.5%) arbitratorAddress: "dero1qz...", // optional }); // Buyer deposits await escrow.deposit(escrowId); // Seller releases (after delivery) await escrow.release(escrowId); // Or: buyer requests refund await escrow.refund(escrowId); // Or: either party disputes await escrow.dispute(escrowId); // Arbitrator resolves (sends to buyer or seller) await escrow.arbitrate(escrowId, { releaseTo: "seller" }); // Check status const status = await escrow.getStatus(escrowId); ``` ## EscrowContract (Low-Level) Direct wrapper around the smart contract's RPC functions: ```ts const wallet = new WalletRPC("http://127.0.0.1:10103/json_rpc"); const contract = new EscrowContract(wallet, contractAddress); // Call contract functions directly await contract.deposit(amount); await contract.release(); await contract.refund(); await contract.claimExpired(); await contract.dispute(); await contract.arbitrate(recipientAddress); // Read contract state const status = await contract.getStatus(); const balance = await contract.getBalance(); ``` ## Status Types ```ts // EscrowStatusCode constants: // AWAITING_DEPOSIT: 0 // FUNDED: 1 // RELEASED: 2 // REFUNDED: 3 // EXPIRED_CLAIMED: 4 // DISPUTED: 5 // ARBITRATED: 6 statusCodeToString(2); // "released" ``` > [!NOTE] > The `EscrowManager` handles contract deployment, status tracking, and state validation. Use `EscrowContract` directly only if you need fine-grained control or are integrating with an existing contract deployment. --- --- title: "Escrow Smart Contract — On-Chain DERO Contract" description: "The escrow.bas smart contract holds DERO funds in escrow with support for deposits, releases, refunds, disputes, and arbitration." --- # Escrow Smart Contract The escrow contract is written in DERO's BASIC-like smart contract language and deployed on-chain. It holds funds and enforces escrow rules without any trusted intermediary. ## Contract Functions | Function | Who Can Call | Description | |---|---|---| | `Deposit` | Buyer | Send DERO into escrow | | `Release` | Seller | Confirm delivery, release funds to seller | | `Refund` | Buyer | Request refund (if allowed by state) | | `ClaimExpired` | Buyer | Claim funds after expiry time | | `Dispute` | Buyer or Seller | Raise a dispute | | `Arbitrate` | Arbitrator | Resolve dispute (send to buyer or seller) | ## Roles - **Buyer** — Deposits DERO into escrow - **Seller** — Confirms delivery to trigger fund release - **Arbitrator** — Optional third party who resolves disputes All roles are identified by their DERO address, set at escrow creation time. ## Platform Fees The contract supports an optional platform fee in **basis points** (1 basis point = 0.01%): - Set at escrow creation - Automatically deducted when funds are released - Sent to the platform address (the contract deployer) - Not deducted on refunds ``` Example: 250 basis points = 2.5% platform fee On a 100 DERO escrow: 2.5 DERO → platform, 97.5 DERO → seller ``` ## State Transitions ``` Deposit() → status 0 → 1 (awaiting_deposit → funded) Release() → status 1 → 2 (funded → released) Refund() → status 1 → 3 (funded → refunded) ClaimExpired() → status 1 → 4 (funded → expired_claimed, only after expiry time) Dispute() → status 1 → 5 (funded → disputed) Arbitrate() → status 5 → 6 (disputed → arbitrated) ``` > [!WARNING] > The contract is **immutable** once deployed. Each escrow transaction deploys a fresh instance of the contract, so there's no shared state between different escrows. ## Deployment The contract is deployed via the DERO wallet RPC when a new escrow is created through the SDK. You don't need to deploy it manually — the `EscrowManager` handles this. See [TypeScript SDK](/escrow/sdk.md) for the programmatic interface. --- --- title: "Guide: Embeddable Payment Widget" description: "Add a 'Pay with DERO' button to any website with a single script tag. No framework required." --- # Embeddable Payment Widget Drop a single ` ``` > [!WARNING] > A hosted CDN build at `https://cdn.deropay.com/widget.js` is planned but **not live yet** — self-host until it is announced. ### Add a Payment Button ```html
``` That's it. A styled "Pay with DERO" button appears, and clicking it creates an invoice and opens the payment modal. ## Fiat Pricing Price in USD, EUR, or any supported currency — the gateway converts to DERO automatically: ```html
``` ## Configuration | Attribute | Required | Description | |-----------|----------|-------------| | `data-gateway` | Yes | Base URL of your DeroPay gateway server | | `data-api-key` | Yes | API key for invoice creation | | `data-amount` | * | Amount in DERO atomic units (100,000 = 1 DERO) | | `data-fiat-amount` | * | Amount in fiat (requires `data-currency`) | | `data-currency` | * | Fiat currency code (`usd`, `eur`, `gbp`, etc.) | | `data-name` | No | Invoice name / item description | | `data-callback-url` | No | URL to POST when payment completes | \* Provide either `data-amount` OR `data-fiat-amount` + `data-currency`. ## Listening for Payment Completion The widget fires a custom event when payment is confirmed: ```javascript document.getElementById("deropay-button") .addEventListener("deropay:completed", (event) => { console.log("Payment complete!", event.detail); // { invoiceId: "inv_abc123", amount: "150000" } // Redirect to thank-you page, update UI, etc. window.location.href = "/thank-you"; }); ``` ## Multiple Buttons on One Page Use the class `deropay-button` or the `data-deropay` attribute for multiple buttons: ```html
``` Each button operates independently — separate invoices, separate modals. ## How It Works Under the Hood 1. Widget reads configuration from `data-*` attributes 2. On click, creates an invoice via `POST /invoices` on your gateway 3. Opens a modal (inside Shadow DOM) with QR code, address, amount, countdown 4. Polls the gateway's `/status` endpoint every 4 seconds 5. On confirmation, shows success state and fires `deropay:completed` event ## Technical Details - **13KB** minified IIFE bundle — zero external dependencies - **Shadow DOM** isolates all styles from the host page - **QR code** generated client-side (no external service) - Works in all modern browsers - Self-hostable — serve `widget.global.js` from your own domain > [!WARNING] > The widget uses your API key client-side to create invoices. Use a **public/restricted API key** that can only create invoices, not read payment data. The gateway's API key auth is designed for this — invoice creation is the only write operation the widget performs. ## Building from Source ```bash cd packages/deropay-widget bun install bun run build # Output: dist/widget.global.js ``` --- --- title: "Guide: DeroPay Gateway Server" description: "Deploy the standalone DeroPay gateway server — the foundation for all ecommerce plugins, widgets, and payment links." --- # DeroPay Gateway Server The gateway server is a standalone HTTP server that wraps the `dero-pay` SDK into a clean REST API. It connects to your DERO wallet, creates invoices, monitors payments, fires webhooks, and manages escrow — all accessible via standard HTTP endpoints. This is the same architecture as BTCPay Server: the gateway is the engine, and everything else (plugins, widgets, payment links) is a thin client. ## Architecture ``` ┌─────────────────────────────────────────┐ │ YOUR INFRASTRUCTURE │ │ │ │ ┌──────────┐ ┌──────────┐ │ │ │ DERO │ │ DERO │ │ │ │ Daemon │ │ Wallet │ │ │ │ :10102 │ │ :10103 │ │ │ └────┬─────┘ └────┬─────┘ │ │ └──────┬────────┘ │ │ │ │ │ ┌──────▼──────┐ │ │ │ DeroPay │ │ │ │ Gateway │ ← your API key │ │ │ :3080 │ │ │ └──────┬──────┘ │ │ │ │ └──────────────┼───────────────────────────┘ │ REST API ┌──────────┼──────────────┐ │ │ │ ┌───▼───┐ ┌───▼────┐ ┌──────▼─────┐ │Widget │ │WooCom │ │Payment │ │ │ │Plugin │ │Links │ └───────┘ └────────┘ └────────────┘ ``` Everything runs on your own infrastructure. You control the wallet, the daemon, the gateway, and the data. ### Install and Configure ```bash git clone https://github.com/DHEBP/DeroPay.git cd DeroPay/apps/gateway cp .env.example .env ``` Edit `.env` with your wallet details: ```env DEROPAY_WALLET_RPC_URL=http://127.0.0.1:10103/json_rpc DEROPAY_DAEMON_RPC_URL=http://127.0.0.1:10102/json_rpc DEROPAY_API_KEY=your-secret-api-key DEROPAY_WEBHOOK_URL=https://yourstore.com/webhook DEROPAY_WEBHOOK_SECRET=your-webhook-secret ``` ### Generate an API Key ```bash bun run src/generate-key.ts ``` ### Start the Server ```bash bun install bun run start ``` ```bash docker compose up -d ``` The Docker Compose file includes the gateway, DERO daemon, and DERO wallet — a complete stack. ### Verify It's Running ```bash curl http://localhost:3080/health ``` ```json { "status": "ok", "wallet": "connected", "balance": "125.00000" } ``` ## API Endpoints ### Public (no API key) | Method | Route | Purpose | |--------|-------|---------| | `GET` | `/health` | Wallet connectivity + balance | | `GET` | `/status?invoiceId=x` | Invoice status (for checkout widgets) | | `GET` | `/price` | Current DERO price (USD, BTC) | | `GET` | `/convert?amount=9.99¤cy=usd` | Fiat → DERO conversion | ### Authenticated (requires `x-api-key` header) | Method | Route | Purpose | |--------|-------|---------| | `POST` | `/invoices` | Create an invoice | | `GET` | `/invoices/:id` | Get invoice details | | `GET` | `/invoices` | List/filter invoices | | `GET` | `/stats` | Aggregate payment stats | | `POST` | `/escrow/:id/:action` | Escrow operations | | `GET` | `/escrows` | List escrow invoices | ### Creating an Invoice ```bash curl -X POST http://localhost:3080/invoices \ -H "Content-Type: application/json" \ -H "x-api-key: your-api-key" \ -d '{ "name": "Order #1234", "amount": 150000, "ttl": 900 }' ``` With fiat pricing (auto-converts to DERO): ```bash curl -X POST http://localhost:3080/invoices \ -H "Content-Type: application/json" \ -H "x-api-key: your-api-key" \ -d '{ "name": "T-Shirt", "fiatAmount": 29.99, "currency": "usd" }' ``` > [!NOTE] > The gateway fetches live DERO prices from CoinGecko and TradeOgre (with fallback). Merchants price products in fiat; the gateway converts at checkout time. ## Webhooks The gateway sends HMAC-SHA256 signed HTTP POST notifications on every invoice state change: ```json { "invoiceId": "inv_abc123", "status": "completed", "amount": "150000", "amountReceived": "150000", "payments": [{ "txHash": "...", "amount": "150000" }] } ``` Verify the signature: ```typescript const isValid = verifyWebhookSignature( rawBody, request.headers["x-deropay-signature"], process.env.WEBHOOK_SECRET ); ``` ## What's Next The gateway is the foundation. Connect it to: - [**Payment Links**](/guides/payment-links.md) — share a checkout URL, no website needed - [**Embeddable Widget**](/guides/embeddable-widget.md) — drop a script tag on any website - [**Medusa.js Plugin**](/guides/medusa-plugin.md) — integrate with Medusa ecommerce - [**WooCommerce Plugin**](/guides/woocommerce.md) — integrate with WordPress/WooCommerce --- --- title: "Guide: Medusa.js Payment Plugin" description: "Accept DERO payments in your Medusa.js v2 store with the DeroPay payment provider plugin." --- # Medusa.js Payment Plugin The `medusa-payment-deropay` package is a Medusa v2 payment module provider that connects your Medusa store to a DeroPay gateway server. Customers can pay with DERO at checkout. ## Prerequisites - A running [DeroPay gateway server](/guides/gateway-server.md) - A Medusa.js v2 store ### Install the Plugin ```bash bun add medusa-payment-deropay ``` ### Configure in `medusa-config.ts` ```typescript module.exports = defineConfig({ // ... modules: [ { resolve: "@medusajs/medusa/payment", options: { providers: [ { resolve: "medusa-payment-deropay", id: "deropay", options: { gatewayUrl: "https://your-gateway:3080", apiKey: "your-api-key", webhookSecret: "your-webhook-secret", }, }, ], }, }, ], }); ``` ### Enable in a Region In the Medusa Admin, go to **Settings → Regions** and enable the "deropay" payment provider for the regions where you want to accept DERO. ### Configure Webhooks Set your DeroPay gateway to send webhooks to your Medusa instance: ``` Webhook URL: https://your-medusa-store.com/hooks/payment/deropay_deropay ``` ## Configuration Options | Option | Required | Description | |--------|----------|-------------| | `gatewayUrl` | Yes | Base URL of your DeroPay gateway server | | `apiKey` | Yes | API key for the gateway | | `webhookSecret` | Yes | HMAC-SHA256 secret for webhook verification | > [!WARNING] > As of `medusa-payment-deropay@0.3.0`, `webhookSecret` is **required** — the plugin refuses to start without it. In earlier versions it was optional, and leaving it unset silently disabled webhook signature verification entirely: anyone who knew a pending invoice's id could POST a forged webhook and have the order marked paid with no real payment. Upgrade and set `webhookSecret` before deploying. ## How It Works ### Payment Flow 1. **Customer selects DERO** at checkout → Medusa calls `initiatePayment` 2. Plugin creates an invoice on your gateway via `POST /invoices` 3. Invoice data (address, QR, amount) is stored in the payment session 4. **Your storefront** displays the payment details to the customer 5. Customer sends DERO from their wallet 6. Gateway detects payment → sends webhook → Medusa auto-completes the order ### Fiat Conversion If your Medusa store prices products in USD/EUR/etc., the plugin automatically sends the fiat amount and currency to the gateway, which converts to DERO at the current market rate. ### Capture Behavior DERO payments settle on-chain instantly — there's no separate "capture" step. When the gateway confirms the payment, it's done. The plugin's `capturePayment` method is a no-op. ### Refunds > [!WARNING] > On-chain DERO payments cannot be automatically refunded. The plugin logs a warning and returns success — process refunds manually by sending DERO back to the customer's address. ## Storefront Integration On your storefront, retrieve the payment session data to display the DERO payment interface: ```typescript // After initiating payment, the session data contains: const sessionData = paymentSession.data; // Display these to the customer: sessionData.integratedAddress // DERO address to send to sessionData.amount // Amount in atomic units sessionData.expiresAt // Invoice expiry timestamp ``` You can use the [embeddable widget](/guides/embeddable-widget.md) or build a custom payment UI using this data. ## Self-Hosted, Non-Custodial Both the Medusa store and the DeroPay gateway run on your infrastructure. No third-party payment service is involved. Funds go directly from the customer's wallet to yours. --- --- title: "Merchant Dashboard — Self-Hosted Admin UI" description: "Set up and run the DeroPay merchant dashboard for invoice management, payment history, and wallet status." --- # Merchant Dashboard DeroPay includes a self-hosted merchant dashboard — a Next.js admin UI for managing invoices, viewing payment history, and monitoring wallet status. ## Features - **Invoice management** — Create, view, and track invoices - **Payment history** — Searchable list of all transactions - **Wallet status** — Balance, connection health, sync status - **Real-time updates** — Live invoice status changes > [!WARNING] > **Live core vs. preview modules.** Invoices, payment history, wallet status, escrow, and webhooks are wired to the real DeroPay engine. The dashboard also ships a broader commerce surface — products, orders, inventory, promotions, price lists — as **UI previews backed by sample data**, plus extension-point stubs (for example, payouts) for you to implement. Treat those sections as scaffolding, not live features, until you wire them to your own backend. ## Setup ### Navigate to the Dashboard The dashboard is located in the DeroPay monorepo: ```bash cd apps/dashboard ``` ### Configure Environment ```bash cp .env.example .env.local ``` Edit `.env.local` with your wallet settings: ```env DERO_WALLET_RPC=http://127.0.0.1:10103/json_rpc DERO_DAEMON_RPC=http://127.0.0.1:10102/json_rpc WEBHOOK_SECRET=your-webhook-secret ``` ### Install and Run ```bash bun install bun dev ``` Open [http://localhost:3100](http://localhost:3100). ## Architecture The dashboard is a standalone Next.js 15 app that imports `dero-pay` as a workspace dependency. It communicates directly with your DERO wallet and daemon via JSON-RPC. ``` Dashboard (localhost:3100) ↓ dero-pay SDK ↓ JSON-RPC DERO Wallet (localhost:10103) DERO Daemon (localhost:10102) ``` > [!NOTE] > The dashboard runs on port **3100** by default to avoid conflicts with your main application on port 3000. ## Deployment For production, the dashboard should be: - Deployed behind authentication (it has full access to your payment engine) - Connected to your production wallet RPC - Not exposed to the public internet Consider using DeroAuth itself to protect the dashboard — authenticate with your merchant wallet to access the admin UI. --- --- title: "Guide: DERO Wallet Auth in Next.js" description: "End-to-end guide for adding DERO wallet authentication to a Next.js 15 App Router project." --- # DERO Wallet Auth in Next.js Add "Sign in with DERO" to your Next.js 15 app — from installation to protected routes. ## What You'll Build - Challenge and verify API routes - A login page with the `` button - Middleware-based route protection - A protected dashboard showing the user's DERO address ### Install Dependencies ```bash bun add dero-auth ``` ### Set Environment Variables ```env # .env.local JWT_SECRET=your-jwt-secret-at-least-32-bytes-long ``` Generate a strong secret: ```bash openssl rand -base64 32 ``` ### Create Auth Handlers ```ts // lib/dero-auth.ts export const { challengeHandler, verifyHandler } = createAuthHandlers({ domain: "localhost:3000", uri: "http://localhost:3000", jwtSecret: process.env.JWT_SECRET!, statement: "Sign in to My App", }); ``` ### Create API Routes ```ts // app/api/auth/challenge/route.ts export const POST = challengeHandler; ``` ```ts // app/api/auth/verify/route.ts export const POST = verifyHandler; ``` ### Add the Auth Provider ```tsx // app/layout.tsx export default function RootLayout({ children, }: { children: React.ReactNode; }) { return ( {children} ); } ``` ### Create the Login Page ```tsx // app/login/page.tsx export default function LoginPage() { return (

Welcome

window.location.href = "/dashboard"} />
); } ``` ### Protect Routes with Middleware ```ts // middleware.ts export const middleware = createDeroAuthMiddleware({ jwtSecret: process.env.JWT_SECRET!, protectedPaths: ["/dashboard"], loginPath: "/login", }); export const config = { matcher: ["/dashboard/:path*"], }; ``` ### Create the Protected Dashboard ```tsx // app/dashboard/page.tsx export default async function DashboardPage() { const address = await getAuthAddress(headers()); if (!address) { redirect("/login"); } return (

Dashboard

Connected wallet:

{address}
); } ``` > [!NOTE] > The user's DERO wallet (Engram or CLI) must be running locally for the XSWD connection to work. See [Prerequisites](/guides/prerequisites.md). --- --- title: "Guide: Accept DERO Payments in Next.js" description: "End-to-end guide for integrating DeroPay into a Next.js 15 App Router project." --- # Accept DERO Payments in Next.js A complete guide for integrating DeroPay into a Next.js 15 App Router project — from installation to webhook handling. ## What You'll Build - API routes for creating invoices and checking status - A payment page with QR code and "Pay with DERO" button - A webhook endpoint for fulfilling orders - Server-side invoice creation from checkout ### Install Dependencies ```bash bun add dero-pay ``` ### Set Environment Variables ```env # .env.local DERO_WALLET_RPC=http://127.0.0.1:10103/json_rpc DERO_DAEMON_RPC=http://127.0.0.1:10102/json_rpc WEBHOOK_SECRET=your-secret-here NEXT_PUBLIC_APP_URL=http://localhost:3000 ``` ### Create the Payment Engine ```ts // lib/deropay.ts export const { createInvoiceHandler, statusHandler, webhookHandler, engine, } = createPaymentHandlers({ walletRpcUrl: process.env.DERO_WALLET_RPC!, daemonRpcUrl: process.env.DERO_DAEMON_RPC!, webhookUrl: `${process.env.NEXT_PUBLIC_APP_URL}/api/webhooks/dero`, webhookSecret: process.env.WEBHOOK_SECRET!, }); ``` ### Create API Routes ```ts // app/api/pay/create/route.ts export const POST = createInvoiceHandler; ``` ```ts // app/api/pay/status/route.ts export const GET = statusHandler; ``` ```ts // app/api/webhooks/dero/route.ts export async function POST(request: Request) { const body = await request.text(); const signature = request.headers.get("X-DeroPay-Signature")!; if (!verifyWebhookSignature(body, signature, process.env.WEBHOOK_SECRET!)) { return new Response("Invalid signature", { status: 401 }); } const event = JSON.parse(body); if (event.event === "invoice.completed") { // Fulfill the order using event.metadata.orderId console.log("Payment completed:", event.invoiceId); } return new Response("OK"); } ``` ### Create the Payment Page ```tsx // app/pay/[invoiceId]/page.tsx "use client"; export default function PayPage({ params, }: { params: { invoiceId: string }; }) { const router = useRouter(); return (

Complete Payment

router.push("/thank-you")} />
); } ``` ### Create the Checkout Flow ```tsx // app/checkout/page.tsx export default function CheckoutPage() { async function checkout(formData: FormData) { "use server"; const invoice = await engine.createInvoice({ name: "Premium Plan", description: "Monthly subscription", amount: deroToAtomic("10.0"), // 10 DERO metadata: { orderId: "ORD-" + Date.now() }, }); redirect(`/pay/${invoice.id}`); } return (

Checkout

Premium Plan — 10 DERO/month

); } ``` > [!NOTE] > Make sure your DERO wallet and daemon are running before starting the dev server. See [Prerequisites](/guides/prerequisites.md) for setup instructions. --- --- title: "Guide: Payment Links & Hosted Checkout" description: "Create shareable payment links for DERO — no website needed. Works via email, social media, QR posters, or messaging." --- # Payment Links & Hosted Checkout Payment links let **any merchant** accept DERO — even without a website. Create an invoice, get a link, share it anywhere. ## How It Works 1. You create an invoice on your gateway (via API, dashboard, or CLI) 2. You get a checkout URL 3. You share it — email, social media, QR code on a poster, text message 4. Customer opens the link, sees the payment page, sends DERO 5. Page auto-updates to "Payment Confirmed" ## The Checkout Page The hosted checkout page (`apps/checkout/`) is a static site that displays a payment interface for any invoice. It shows: - DERO amount - QR code (scannable by any DERO wallet) - Integrated address (with copy button) - Countdown timer - Live status updates (polling every 4 seconds) - Success animation on confirmation The page is mobile-optimized — designed for links shared via messaging apps where customers open on their phone. ### Create an Invoice ```bash curl -X POST http://localhost:3080/invoices \ -H "Content-Type: application/json" \ -H "x-api-key: your-api-key" \ -d '{ "name": "Consulting Session", "fiatAmount": 50.00, "currency": "usd" }' ``` Response: ```json { "id": "inv_abc123", "amount": "250000", "integratedAddress": "deto1q...", "expiresAt": "2026-03-03T12:15:00Z" } ``` ### Build the Checkout URL ``` https://checkout.yoursite.com/?gateway=https://gateway.yoursite.com&invoiceId=inv_abc123 ``` | Parameter | Required | Description | |-----------|----------|-------------| | `gateway` | Yes | Base URL of your DeroPay gateway | | `invoiceId` | Yes | Invoice ID from step 1 | ### Share the Link Send it however you want: - **Email**: "Click here to pay for your consulting session" - **QR poster**: Print a QR code that encodes the checkout URL - **Social media**: Post the link with a description - **Invoice PDF**: Include the link in a traditional invoice document - **Point of sale**: Display the QR on a tablet at your counter ## Self-Hosting the Checkout Page The checkout page is a static site — deploy it anywhere: ```bash cd apps/checkout bun install bun run build # Output: dist/ — deploy to Vercel, Netlify, S3, or serve alongside your gateway ``` > [!NOTE] > The checkout page only uses the **public** `/status` endpoint on your gateway. No API key is exposed to customers. The merchant creates the invoice server-side (with their API key) and only shares the resulting URL. ## Use Cases | Scenario | How | |----------|-----| | **Freelancer** | Send a payment link after completing work | | **Market vendor** | Print a QR code poster at your booth | | **Online service** | Email a payment link with the order confirmation | | **Donation** | Post a "Support with DERO" link on your website or social | | **Invoice** | Include a "Pay Now" link in a PDF invoice | ## Design The checkout page follows the DeroPay design system — emerald green accents on a dark theme, mobile-first layout, and clean typography. It's branded but minimal, keeping the focus on the payment. --- --- title: "Prerequisites — DERO Wallet & Environment Setup" description: "Set up a DERO wallet and development environment for DeroPay and DeroAuth." --- # Prerequisites Before using DeroPay or DeroAuth, you need a running DERO wallet and a Node.js environment. ## Requirements - **Node.js** >= 20.0.0 - **DERO wallet** — Engram (desktop) or DERO CLI wallet - **DERO daemon** — synced to mainnet or testnet ## DERO Wallet Setup **Engram** is the official desktop wallet for DERO with built-in XSWD support. 1. Download from [GitHub Releases](https://github.com/DEROFDN/Engram/releases) 2. Create or restore a wallet 3. XSWD is enabled automatically on `ws://localhost:44326` > [!NOTE] > Engram is recommended for development — it provides the best XSWD experience for both auth and payments. The CLI wallet requires the `--rpc-server` flag to enable RPC access: ```bash # Mainnet dero-wallet-cli \ --wallet-file wallet.db \ --rpc-server \ --rpc-bind=127.0.0.1:10103 # Testnet dero-wallet-cli \ --wallet-file wallet.db \ --rpc-server \ --rpc-bind=127.0.0.1:40403 \ --testnet ``` ## DERO Daemon The daemon must be running and synced for payment monitoring: ```bash # Mainnet derod --rpc-bind=127.0.0.1:10102 # Testnet derod --rpc-bind=127.0.0.1:40402 --testnet ``` ## Default Ports | Service | Mainnet | Testnet | |---|---|---| | Wallet RPC | `127.0.0.1:10103` | `127.0.0.1:40403` | | Daemon RPC | `127.0.0.1:10102` | `127.0.0.1:40402` | | Engram XSWD | `ws://localhost:44326` | `ws://localhost:44326` | ## Install the SDKs ```bash # Payments bun add dero-pay # Authentication bun add dero-auth # Both bun add dero-pay dero-auth ``` ```bash npm install dero-pay npm install dero-auth ``` ```bash pnpm add dero-pay pnpm add dero-auth ``` > [!WARNING] > Both packages are **ESM-only** (`"type": "module"`). Make sure your project supports ES modules. ## Next Steps - [DeroPay Quick Start](/dero-pay/quick-start.md) — Accept your first payment - [DeroAuth Quick Start](/dero-auth/quick-start.md) — Add wallet login --- --- title: "Guide: WooCommerce Plugin" description: "Accept DERO payments on your WordPress/WooCommerce store with the DeroPay payment gateway plugin." --- # WooCommerce Plugin The DeroPay for WooCommerce plugin adds DERO as a payment method in your WooCommerce store. It's a thin PHP adapter that connects to your DeroPay gateway server. ## Prerequisites - WordPress with WooCommerce installed - A running [DeroPay gateway server](/guides/gateway-server.md) ### Install the Plugin 1. Download the latest `deropay-for-woocommerce.zip` from the [GitHub Releases](https://github.com/DHEBP/DeroPay/releases) page. 2. In your WordPress admin dashboard, go to **Plugins → Add New → Upload Plugin**. 3. Select the `.zip` file, install, and activate the plugin. ### Configure the Gateway Go to **WooCommerce → Settings → Payments → DeroPay** and enter: | Setting | Value | |---------|-------| | **Enable** | Yes | | **Gateway URL** | `https://your-gateway:3080` | | **API Key** | Your gateway API key | | **Webhook Secret** | Your webhook secret (must match gateway config) | ### Test a Payment 1. Add a product to your cart 2. Go to checkout 3. Select "Pay with DERO" 4. You'll see the DERO amount, QR code, and integrated address 5. Send DERO from your wallet 6. Order status updates automatically via webhook ## How It Works ``` Customer → WooCommerce → DeroPay Plugin → Gateway Server → DERO Wallet ↓ Webhook back ↓ Order marked "Processing" ``` 1. Customer selects "Pay with DERO" at checkout 2. Plugin calls `POST /invoices` on your gateway with the order total 3. Gateway creates an invoice and returns the payment details 4. Plugin displays QR code and address on the "Order Received" page 5. Gateway monitors the wallet for incoming payment 6. On confirmation, gateway sends a webhook to WooCommerce 7. Plugin verifies the HMAC signature and marks the order as "Processing" ## Fiat Pricing WooCommerce stores typically price products in USD, EUR, etc. The plugin sends the fiat amount and currency to the gateway, which converts to DERO at the current market rate. The customer sees both the fiat price and the DERO equivalent. ## Webhook Endpoint The plugin registers a webhook endpoint at: ``` https://yourstore.com/?wc-api=deropay_webhook ``` Configure your DeroPay gateway to send webhooks to this URL. > [!NOTE] > The webhook handler verifies the HMAC-SHA256 signature before processing. Make sure the webhook secret matches between your gateway and WooCommerce settings. ## Plugin Files | File | Purpose | |------|---------| | `deropay-for-woocommerce.php` | Main plugin file, registers gateway + webhook | | `includes/class-deropay-gateway.php` | `WC_Payment_Gateway` implementation | | `includes/class-deropay-api-client.php` | HTTP client for the gateway REST API | | `includes/class-deropay-webhook-handler.php` | HMAC-verified webhook handler | | `assets/dero-icon.svg` | Payment method icon | --- --- title: "Escrow vs. Payment Router — Which Smart Contract to Use" description: "A comparison of DeroPay's two smart contracts: escrow for buyer-protected transactions and payment router for instant merchant payments." --- # Escrow vs. Payment Router DeroPay includes two smart contracts for on-chain payments. They serve different use cases and have fundamentally different deployment models. ## At a Glance | | Escrow | Payment Router | |---|---|---| | **Deployment model** | One contract per transaction | One contract per merchant (reusable) | | **Transactions per payment** | 3 (deploy + deposit + confirm) | 1 (pay) | | **Settlement time** | ~54 seconds minimum | ~18 seconds (one block) | | **Buyer protection** | Yes — refunds, disputes, arbitration | No | | **Fee recipient** | Contract deployer (platform) | Configurable by merchant | | **Dispute resolution** | Yes (arbitrator role) | No | | **Pausable** | No | Yes (merchant can pause/resume) | | **On-chain state** | Per-transaction (amount, status, addresses) | Aggregate (total processed, count, fees) | | **SCID persistence** | Ephemeral — new SCID for each escrow | Persistent — same SCID for all payments | ## When to Use Escrow Use escrow when the buyer needs protection or the transaction requires trust: - **Marketplaces** — Buyer pays, seller ships, funds release on confirmation - **Freelance contracts** — Client deposits, freelancer delivers, funds release on approval - **P2P trades** — Neither party trusts the other - **High-value purchases** — Where the buyer wants the ability to dispute or get a refund - **Any transaction where delivery isn't instant** The escrow contract deploys a **fresh instance for each transaction**. Each escrow has its own SCID, its own state, and its own lifecycle (funded → released/refunded/disputed). There is no shared state between different escrows. ```ts const escrow = await manager.create({ seller: "dero1qy...seller", amount: 500_000, // 5 DERO expirySeconds: 86400, // 24 hours }); // escrow.scid is unique to this transaction ``` ## When to Use the Payment Router Use the payment router when the merchant just needs to get paid: - **Retail / e-commerce** — Customer pays, product ships immediately - **Subscriptions** — Recurring payments to the same merchant - **Donations** — One-time or recurring contributions - **Digital goods** — Instant delivery, no buyer protection needed - **Any "pay and go" transaction** The payment router is deployed **once** by the merchant and handles **every subsequent payment** through the same contract. The SCID is permanent and can be shared publicly. ```ts // Deploy once const router = await manager.deployRouter({ feeBasisPoints: 0 }); // router.scid is reused for every payment // Use for every payment await manager.pay(router.scid, "inv_001", 100_000n); await manager.pay(router.scid, "inv_002", 50_000n); await manager.pay(router.scid, "inv_003", 200_000n); // All through the same contract ``` ## Cost Comparison | | Escrow (per transaction) | Payment Router (per transaction) | |---|---|---| | **Contract deployment** | 1 deploy TX (gas fee) | 0 (already deployed) | | **Payment** | 1 deposit TX + 1 release TX | 1 pay TX | | **Total on-chain TXs** | 3 | 1 | | **Total gas cost** | ~3× base fee | ~1× base fee | Over many payments, the router is significantly cheaper because the deployment cost is amortized across all payments. ## Can I Use Both? Yes. A merchant can deploy a payment router for standard checkout and use escrow for high-value or trust-sensitive transactions. The two systems are independent — they use different contracts, different SDK modules, and different state. ```ts // Standard payments through the router const router = new RouterManager({ ... }); await router.pay(scid, "inv_standard_001", 10_000n); // High-value escrow for a custom project const escrow = new EscrowManager({ ... }); await escrow.create({ seller: "dero1q...", amount: 5_000_000 }); ``` > [!NOTE] > **Rule of thumb:** If the customer receives what they paid for immediately (digital goods, retail), use the payment router. If there's a delay between payment and delivery (freelance work, shipped goods, services), use escrow. --- --- title: "Payment Router — Instant On-Chain Payments" description: "A reusable per-merchant smart contract for fast, single-transaction DERO payments with optional fee splitting." --- # Payment Router The payment router is a reusable smart contract that a merchant deploys once and uses for unlimited payments. Customers call `Pay()` with DERO attached, and the contract instantly forwards the funds to the merchant — with an optional fee split to a configurable recipient. ## Why a Payment Router? Standard DeroPay invoices use the wallet's integrated addresses to match payments. That works well, but the payment router adds on-chain settlement: - **Single transaction** — Customer sends DERO, merchant receives it in the same block (~18 seconds) - **On-chain fee splitting** — Optional automatic split between merchant and a fee recipient (partner, referral, platform) - **Reusable** — One contract handles every payment. No per-transaction deployment - **Verifiable** — Anyone can read the contract source and fee structure on-chain - **Pausable** — Merchant can pause and resume the router at any time ## How It Works ``` Customer calls Pay("inv_123") with 10 DERO attached │ ▼ ┌─────────────────────────┐ │ Payment Router SC │ │ │ │ feeBasisPoints: 250 │ (2.5%) │ merchant: dero1q... │ │ feeRecipient: dero1q...│ └─────────┬───────────────┘ │ ┌─────┴─────┐ │ │ ▼ ▼ 9.75 DERO 0.25 DERO → merchant → feeRecipient ``` Everything happens atomically in a single on-chain transaction. ## On-Chain State The contract tracks aggregate statistics: | Key | Type | Description | |-----|------|-------------| | `merchant` | Address | Deployer's address — receives payouts | | `feeRecipient` | Address | Receives the fee split | | `feeBasisPoints` | Uint64 | Fee rate (100 = 1%, 250 = 2.5%, 0 = no fee) | | `totalProcessed` | Uint64 | Cumulative DERO processed (atomic units) | | `totalFees` | Uint64 | Cumulative fees collected (atomic units) | | `paymentCount` | Uint64 | Number of payments processed | | `paused` | Uint64 | 0 = active, 1 = paused | ## Components ### Smart Contract (`payment-router.bas`) A DERO smart contract written in DVM-BASIC that handles payment splitting and tracks statistics. See [Smart Contract](/payment-router/smart-contract.md). ### TypeScript SDK - **`RouterContract`** — Low-level wrapper around the contract's RPC functions - **`RouterManager`** — High-level lifecycle manager that handles deployment, payments, and state queries See [TypeScript SDK](/payment-router/sdk.md). > [!NOTE] > The payment router is **deployed once per merchant** and reused for every payment. This is fundamentally different from the [escrow system](/escrow/overview.md), which deploys a fresh contract for each transaction. See [Escrow vs. Router](/payment-router/escrow-vs-router.md) for a detailed comparison. > [!WARNING] > The fee structure is **immutable** once deployed. The merchant verifies the contract source and fee rate before deploying. This prevents anyone from changing the terms after the fact. --- --- title: "Payment Router TypeScript SDK — RouterContract & RouterManager" description: "Use the dero-pay/router module to deploy payment routers, send payments, and query on-chain state programmatically." --- # Payment Router TypeScript SDK The `dero-pay/router` module provides two levels of abstraction for working with payment routers: ```ts ``` ## RouterManager (High-Level) The recommended way to work with payment routers. Manages deployment, payments, and state queries: ```ts const manager = new RouterManager({ walletRpcUrl: "http://127.0.0.1:10103/json_rpc", daemonRpcUrl: "http://127.0.0.1:10102/json_rpc", }); // Deploy a router with no fee (merchant keeps 100%) const router = await manager.deployRouter(); console.log("SCID:", router.scid); // Deploy a router with 2.5% fee to a partner const router = await manager.deployRouter({ feeRecipientAddress: "dero1qy...partner-address", feeBasisPoints: 250, }); // Send a payment through the router const txid = await manager.pay(router.scid, "inv_abc123", 50_000n); // Query on-chain state const state = await manager.getOnChainState(router.scid); console.log("Payments:", state.paymentCount); console.log("Total:", state.totalProcessed); // Pause/resume await manager.pause(router.scid); await manager.resume(router.scid); // Update merchant address await manager.updateMerchant(router.scid, "dero1qy...new-address"); // Withdraw trapped funds await manager.withdrawTrapped(router.scid, 1000n); ``` ### Events ```ts manager.on("routerDeployed", (router) => { console.log("Deployed:", router.scid); }); manager.on("routerDeployFailed", (router, error) => { console.error("Failed:", error.message); }); manager.on("paymentProcessed", (scid, invoiceId, txid) => { console.log("Payment:", invoiceId, "TXID:", txid); }); manager.on("error", (error) => { console.error("Error:", error.message); }); ``` ### Import Existing Routers If you persist router records (e.g. in a database), you can re-import them on restart: ```ts manager.importRouter({ id: "local-uuid", scid: "abc123...", deployTxid: "abc123...", status: "active", feeRecipientAddress: "dero1qy...", feeBasisPoints: 250, createdAt: "2026-03-03T00:00:00Z", metadata: {}, }); ``` ## RouterContract (Low-Level) Direct wrapper around the smart contract's RPC functions: ```ts const wallet = new WalletRpcClient({ url: "http://127.0.0.1:10103/json_rpc" }); const daemon = new DaemonRpcClient({ url: "http://127.0.0.1:10102/json_rpc" }); const contract = new RouterContract(wallet, daemon); // Deploy const scid = await contract.deploy({ feeBasisPoints: 0 }); // Pay const txid = await contract.pay(scid, "inv_001", 100_000n); // Admin await contract.updateMerchant(scid, "dero1qy..."); await contract.pause(scid); await contract.resume(scid); await contract.withdrawTrapped(scid, 5000n); // Query state const state = await contract.getState(scid); // Check existence const exists = await contract.exists(scid); ``` ## On-Chain State Type ```ts type RouterOnChainState = { scid: string; merchant: string; feeRecipient: string; feeBasisPoints: number; totalProcessed: bigint; totalFees: bigint; paymentCount: number; paused: boolean; scBalance: number; }; ``` ## Key Constants - **DERO decimals:** 5 (1 DERO = 100,000 atomic units) - **Basis points:** 10,000 = 100%. Use 250 for 2.5%, 100 for 1%, 0 for no fee. - **Settlement time:** ~18 seconds (one DERO block) > [!NOTE] > The `RouterManager` handles deployment and event tracking. Use `RouterContract` directly only if you need fine-grained control or are building a custom integration. > [!WARNING] > After deploying a router, wait at least **45 seconds** before sending the first payment. DERO's encrypted balances require the deploy transaction to fully confirm before subsequent transactions can be broadcast. See [Prerequisites](/guides/prerequisites.md) for more on DERO transaction timing. --- --- title: "Payment Router Smart Contract — On-Chain DERO Contract" description: "The payment-router.bas smart contract handles instant payment splitting between a merchant and an optional fee recipient." --- # Payment Router Smart Contract The payment router contract is written in DERO's DVM-BASIC smart contract language. It receives payments and instantly forwards them to the merchant, with an optional fee split. ## Contract Functions | Function | Who Can Call | Description | |---|---|---| | `Initialize` | Deployer | Set up merchant, fee recipient, and fee rate | | `Pay` | Anyone | Send DERO through the router (splits to merchant + fee recipient) | | `UpdateMerchant` | Merchant | Change the merchant address | | `Pause` | Merchant | Stop accepting payments | | `Resume` | Merchant | Resume accepting payments | | `WithdrawTrapped` | Merchant | Recover any DERO accidentally trapped in the contract | | `GetStats` | Anyone | Returns `totalProcessed` | ## Roles - **Merchant** — The wallet that deploys the contract. Receives payment payouts. Can pause, resume, update their address, and withdraw trapped funds. - **Fee Recipient** — An optional address that receives a percentage of each payment. Set at deployment, cannot be changed. ## Deployment Model > [!WARNING] > The payment router is deployed **once per merchant** and reused for every payment. The merchant deploys it from their own wallet and owns it permanently. This is the opposite of the escrow contract, which deploys a fresh instance per transaction. The deployer's wallet address automatically becomes the `merchant`. The contract stores aggregate statistics (`totalProcessed`, `totalFees`, `paymentCount`) that accumulate across all payments. ## Fee Splitting The contract supports an optional fee split in **basis points** (1 basis point = 0.01%): - Set at deployment via `feeBasisPoints` — immutable after deploy - Automatically deducted from every payment - Sent to the `feeRecipient` address - If `feeBasisPoints` is 0, no fee is taken (merchant receives 100%) ``` Example: 250 basis points = 2.5% fee On a 10 DERO payment: 0.25 DERO → fee recipient, 9.75 DERO → merchant ``` ## Security Features The contract includes several hardened security measures: | Guard | Purpose | |-------|---------| | `DEROVALUE() > 0` on non-payable functions | Prevents accidental DERO trapping | | `feeBasisPoints <= 10000` | Fee cannot exceed 100% | | `merchant != feeRecipient` (when fee > 0) | Prevents misconfigured self-fee | | `payout > 0` / `fee > 0` guards | No zero-value transfers | | `paused` flag | Merchant can halt payments | | `SIGNER()` checks | Only merchant can call admin functions | | `EXISTS("merchant")` | Prevents re-initialization | ## Payment Flow ``` Pay("inv_123") with DEROVALUE() = 10,000 atomic │ ├── fee = 10000 × 250 / 10000 = 250 atomic ├── payout = 10000 - 250 = 9750 atomic │ ├── SEND_DERO_TO_ADDRESS(merchant, 9750) ├── SEND_DERO_TO_ADDRESS(feeRecipient, 250) │ ├── totalProcessed += 10000 ├── totalFees += 250 └── paymentCount += 1 ``` ## Mainnet Validation The payment router has been deployed and tested on DERO mainnet (2026-03-03): - Full cycle: deploy → pay (0.1 DERO) → verify split → pay (0.05 DERO) → verify accumulation - All funds forwarded correctly (SC balance = 0 after each payment) - Fee counters accumulated as expected - No trapped funds See [TypeScript SDK](/payment-router/sdk.md) for the programmatic interface.