diff --git a/.gitignore b/.gitignore index b7e43b1..d6a633f 100644 --- a/.gitignore +++ b/.gitignore @@ -26,3 +26,4 @@ yarn-debug.log* yarn-error.log* pnpm-debug.log* ADMIN_API.md +.tmp/ diff --git a/API.md b/API.md index c9f6f5e..6f02bc8 100644 --- a/API.md +++ b/API.md @@ -1,284 +1,32 @@ -**Overview** -This document summarizes the GOAT Flow Core APIs invoked by `goatflow-sdk-server`, in a Swagger-like format. It focuses on request/response shapes, auth requirements, and how each SDK method maps to Core endpoints. - -**Base Url** -Production: `https://flow-api.goat.network`. - -SDKs accept `{baseUrl}` in `GoatFlowClient`; paths below are appended to it. - -**Auth (HMAC-SHA256)** -Protected endpoints require these headers: -`X-API-Key`, `X-Timestamp`, `X-Nonce`, `X-Sign` - -Signature algorithm (from goatx402-core): -1. Take all query/body fields, add `api_key`, `timestamp` (Unix seconds), and `nonce`. -2. Remove `sign` if present, drop empty values. -3. Sort keys by ASCII and build `k1=v1&k2=v2`. -4. HMAC-SHA256 with `apiSecret`, hex-encode. - -Notes: -- Timestamp is validated within a short window (default 5 minutes in core). -- `X-Nonce` is required, must be unique per request, and is included in the signed params as `nonce`. -- Use server-side SDK only. Do not expose `apiSecret` in the frontend. - -**Fee Mechanism** -- **Fee Deduction:** Fees are deducted from merchant's fee balance when an order is created. -- **Insufficient Balance:** Order creation fails with `insufficient fee balance` error if balance is too low. -- **Fee Refund:** Canceling a `CHECKOUT_VERIFIED` order refunds the fee. -- **Recommendation:** Monitor fee balance regularly and top up to avoid service disruption. - -**Error Codes (Core Behavior)** -Core public APIs return HTTP status codes and an error message (field may be `error` or `message`). There is no stable `code` field for these endpoints in core at the moment. Treat HTTP status as the error code. - -Common HTTP statuses: -- `400` Bad Request. Validation failures and business rule errors. -- `401` Unauthorized. Missing or invalid auth headers. -- `403` Forbidden. Merchant mismatch or order ownership mismatch. -- `404` Not Found. Merchant or order does not exist. -- `500` Internal Error. Unexpected server errors. - -**Common Business Error Messages (HTTP 400)** - -*Order Creation Validation Errors:* -- `merchant_id is required` -- `from_address is required` -- `token_symbol is required` -- `dapp_order_id is required` -- `chain_id is required` -- `amount_wei is required` -- `amount_wei must be greater than 0` -- `invalid Ethereum address format` - -*Configuration and Resource Errors:* -- `merchant not found` -- `token not supported on chain ` -- `merchant callback contract not configured for merchant on chain ` -- `eip712_name not configured for merchant on chain ` -- `eip712_version not configured for merchant on chain ` -- `token capability not found for chain token
` -- `chain fee config not found for chain_id=, please configure it first` - -*Balance Insufficient Errors:* -- `insufficient fee balance: available=$X.XX, required=$Y.YY` -- `insufficient available balance: available=, required=` -- `insufficient TSS wallet balance on chain : available=, required=` - -*Flow-Specific Errors:* -- `callback_calldata is only supported for ERC20_3009 or ERC20_APPROVE_XFER flows` -- `TSS wallet
has not approved Permit2 contract for token on chain ` -- `no enabled TSS wallet found for chain and token ` - -*Cancellation Errors:* -- `cannot cancel order in status , only CHECKOUT_VERIFIED orders can be cancelled` - -**Endpoint Summary (SDK Mapping)** -| SDK Method (`goatflow-sdk-server`) | Core Endpoint | Auth | -| --- | --- | --- | -| `GoatFlowClient.createOrder` | `POST /api/v1/orders` | Yes | -| `GoatFlowClient.createOrderRaw` | `POST /api/v1/orders` | Yes | -| `GoatFlowClient.createCheckoutSession` | `POST /api/v1/checkout/sessions` | Yes | -| `GoatFlowClient.getOrderStatus` | `GET /api/v1/orders/{order_id}` | Yes | -| `GoatFlowClient.getOrderProof` | `GET /api/v1/orders/{order_id}/proof` | Yes | -| `GoatFlowClient.submitCalldataSignature` | `POST /api/v1/orders/{order_id}/calldata-signature` | Yes | -| `GoatFlowClient.cancelOrder` | `POST /api/v1/orders/{order_id}/cancel` | Yes | -| `GoatFlowClient.getMerchant` | `GET /merchants/{merchant_id}` | No | - -**POST /api/v1/orders** -| Item | Value | -| --- | --- | -| Summary | Create a payment order and return x402 Payment Required response | -| Auth | Required | -| SDK | `createOrder` returns normalized `Order`, `createOrderRaw` returns raw x402 | -| Success Status | `402 Payment Required` (expected) | -| Response Header | `PAYMENT-REQUIRED`: base64 of x402 JSON | - -Request Body: -| Field | Type | Required | Description | -| --- | --- | --- | --- | -| `dapp_order_id` | string | Yes | Your unique order id | -| `chain_id` | number | Yes | Source chain ID | -| `token_symbol` | string | Yes | Token symbol | -| `token_contract` | string | No | Token contract (optional) | -| `from_address` | string | Yes | Payer address | -| `amount_wei` | string | Yes | Amount in wei | -| `callback_calldata` | string | No | Hex calldata, only for DELEGATE flow | -| `merchant_id` | string | No | If provided, must match auth merchant | - -Success Response (x402 PaymentRequired, HTTP 402): -| Field | Type | Description | -| --- | --- | --- | -| `x402Version` | number | x402 protocol version | -| `resource` | object | x402 resource info | -| `accepts` | array | Payment options (scheme, network, amount, asset, payTo, extra) | -| `extensions.goatx402` | object | `destinationChain`, `expiresAt`, `paymentMethod`, `receiveType`; `signatureEndpoint` is included for `ERC20_3009` or whenever `calldata_sign_request` must be submitted (including Permit2 callbacks) | -| `order_id` | string | Core order id | -| `flow` | string | Payment flow (`ERC20_DIRECT`, `ERC20_3009`, `ERC20_APPROVE_XFER`) | -| `token_symbol` | string | Token symbol | -| `calldata_sign_request` | object | EIP-712 signing data when callback is enabled | - -**Best Practices for Error Handling:** -1. Always check for `insufficient fee balance` errors and alert operations team -2. Implement retry logic for 5xx errors with exponential backoff -3. Log full error response body for debugging (available in SDK error objects) -4. For token/chain configuration errors, guide users to supported payment methods -5. Handle order cancellation gracefully when payment fails or times out - -**GET /api/v1/orders/{order_id}** -| Item | Value | -| --- | --- | -| Summary | Get order status for polling | -| Auth | Required | -| SDK | `getOrderStatus` | -| Success Status | `200 OK` | - -Response Body: -| Field | Type | Description | -| --- | --- | --- | -| `order_id` | string | Core order id | -| `merchant_id` | string | Merchant id | -| `dapp_order_id` | string | Your order id | -| `chain_id` | number | Source chain ID | -| `token_contract` | string | Token contract | -| `token_symbol` | string | Token symbol | -| `from_address` | string | Payer address | -| `amount_wei` | string | Amount in wei | -| `status` | string | `CHECKOUT_VERIFIED`, `PAYMENT_CONFIRMED`, `INVOICED`, `FAILED`, `EXPIRED`, `CANCELLED` | -| `tx_hash` | string | Optional, when confirmed | -| `confirmed_at` | string | Optional, timestamp | - -**GET /api/v1/orders/{order_id}/proof** -| Item | Value | -| --- | --- | -| Summary | Get the server-issued payment record for reconciliation and on-chain verification | -| Auth | Required | -| SDK | `getOrderProof` | -| Success Status | `200 OK` | - -Response Body: -| Field | Type | Description | -| --- | --- | --- | -| `payload` | object | Payment-record payload (`order_id`, `tx_hash`, `log_index`, `from_addr`, `to_addr`, `amount_wei`, `from_chain_id`, `status`) | -| `signature` | string | Unsigned Keccak256 hash of seven payload fields concatenated without separators, in this exact order: `order_id`, `tx_hash`, `log_index`, `from_addr`, `to_addr`, `amount_wei`, `from_chain_id` (`status` is NOT covered). An integrity checksum of those fields only, not a cryptographic attestation | - -Verify `payload.tx_hash` and its transfer on-chain when independent proof of payment is required. - -**POST /api/v1/orders/{order_id}/calldata-signature** -| Item | Value | -| --- | --- | -| Summary | Submit user EIP-712 signature for callback calldata | -| Auth | Required | -| SDK | `submitCalldataSignature` | -| Success Status | `200 OK` | - -Request Body: -| Field | Type | Required | Description | -| --- | --- | --- | --- | -| `signature` | string | Yes | `0x` + 130 hex chars (65 bytes) | - -Response Body: -| Field | Type | Description | -| --- | --- | --- | -| `status` | string | `ok` | -| `order_id` | string | Order id | - -**POST /api/v1/orders/{order_id}/cancel** -| Item | Value | -| --- | --- | -| Summary | Cancel a pending order (only `CHECKOUT_VERIFIED`) | -| Auth | Required | -| SDK | `cancelOrder` | -| Success Status | `200 OK` | - -Response Body: -| Field | Type | Description | -| --- | --- | --- | -| `status` | string | `cancelled` | -| `order_id` | string | Order id | - -Notes: -- Core cancels only when the order is still `CHECKOUT_VERIFIED`. -- Cancellation restores reserved balance and refunds fee in core. -- **Important:** Always cancel unused orders to avoid wasting fees and resources. -- Best practice: Cancel orders when user closes payment page or payment times out. - -**GET /merchants/{merchant_id}** -| Item | Value | -| --- | --- | -| Summary | Get public merchant info | -| Auth | Not required | -| SDK | `getMerchant` | -| Success Status | `200 OK` | - -Response Body: -| Field | Type | Description | -| --- | --- | --- | -| `merchant_id` | string | Merchant id | -| `enabled` | boolean | Whether the merchant is enabled | -| `receive_type` | string | `DIRECT` or `DELEGATE` | -| `wallets` | array | `{ address, chain_id, token_symbol, token_contract }` | -| `api_key` | string | Empty on the public Core response; populated only in authenticated/internal merchant lookups | - -Note: SDKs may normalize missing display fields, for example using `merchant_id` as `name`. Core's public response does not include `name` or `logo`. - -**POST /api/v1/checkout/sessions** - -| Item | Value | -| --- | --- | -| Summary | Create a server-authoritative Hosted Checkout Session | -| Auth | Required (merchant HMAC) | -| SDK | `createCheckoutSession` | -| Success Status | `200 OK` | - -The authenticated API key determines the merchant. Do not include `merchant_id` -in the body. - -Common request fields: - -| Field | Type | Required | Description | -| --- | --- | --- | --- | -| `checkout_type` | string | Yes | `DIRECT` or `DELEGATE`; must match the merchant | -| `price` | decimal string | Conditional | DIRECT price, or cross-chain DELEGATE price | -| `chain_id` | number | Conditional | Legacy fixed-wei DELEGATE source chain | -| `fixed_amount_wei` | string | Conditional | Legacy fixed-wei DELEGATE amount | -| `acceptable_tokens` | JSON string | Conditional | Legacy DELEGATE token-contract array | -| `callback_calldata` | hex string | No | Legacy fixed-wei DELEGATE callback calldata | -| `client_reference_id` | string | No | Merchant correlation key, maximum 200 characters | -| `success_url` / `cancel_url` | string | No | Allowlist-gated hosted-page redirects | -| `line_items_json` | JSON string | No | Display line items | -| `public_metadata_json` | JSON string | No | Metadata visible in the public session view | -| `private_metadata_json` | JSON string | No | Stored metadata excluded from the public view | -| `expires_in` | number | No | Session lifetime in seconds | - -Use exactly one amount form for DELEGATE: - -- `price` selects cross-chain decimal-price mode; Core derives callback chain and - eligible source-chain/token candidates. -- `fixed_amount_wei` + `chain_id` + `acceptable_tokens` selects the compatibility - single-chain mode. - -The server SDK accepts arrays/objects for nested values and performs the required -JSON stringification before HMAC signing. - -Response: - -| Field | Type | Description | -| --- | --- | --- | -| `checkout_id` | string | Opaque `cs_…` handle passed to the browser SDK | -| `checkout_type` | string | `DIRECT` or `DELEGATE` | -| `url` | string | Platform-built hosted checkout URL | -| `expires_at` | number | Unix expiration time | - -**Public Hosted Checkout endpoints** - -The platform-hosted page, not the merchant application, normally owns these calls: - -| Endpoint | Purpose | -| --- | --- | -| `GET /checkout/v1/sessions/{checkout_id}` | Read safe public terms and current state | -| `GET /checkout/v1/sessions/{checkout_id}/status` | Poll compact status | -| `POST /checkout/v1/sessions/{checkout_id}/bind` | Bind payer/token and create the real order | -| `POST /checkout/v1/sessions/{checkout_id}/signature` | Verify and store a DELEGATE callback signature | - -The raw handle is a bearer capability. Keep it out of logs. Completion should be -confirmed from `quickpay.checkout.completed` or trusted backend order status; -the browser `onSuccess` callback is UX-only. +# GOAT Flow API Compatibility Page + +This root-level file is retained so existing repository and package links keep +working. The canonical API documentation now lives under `docs/`. + +Use: + +- [API Reference](./docs/goat-flow-api-reference.md) for endpoints, HMAC signing, + request/response shapes, status values, QuickPay, MPP, and error semantics. +- [Developer Quick Start](./docs/goat-flow-developer-quickstart.md) for a first + working integration. +- [Integration Guide](./docs/goat-flow-integration.md) for SDK boundaries, + fulfillment, retries, and production guidance. +- [Hosted Checkout](./docs/goat-flow-checkout.md) for product and session checkout. +- [GOAT Flow MPP Integration](./docs/mpp.md) for the independent MPP protocol + boundary and this deployment's paid-route and receipt profile. + +Compatibility notes: + +- Published packages use the `goatflow-*` names listed in + [`docs/README.md`](./docs/README.md#npm-packages). +- Protocol fields, JSON keys such as the `goatx402` extension, and + `GOATX402_*` environment variables retain their existing technical names. +- Merchant API credentials and HMAC signing belong on the backend. +- HTTP `402` is an expected success only for endpoints documented as payment + challenges, including order creation and the GOAT Flow profile's MPP + challenge endpoint. This does not redefine the standard MPP HTTP exchange. +- Runtime challenge, manifest, merchant, and portal configuration is + authoritative for enabled chains, tokens, limits, fees, and capabilities. + +Do not copy API schemas from historical revisions of this file. Update the +canonical [API Reference](./docs/goat-flow-api-reference.md) instead. diff --git a/DEVELOPER_FAST.md b/DEVELOPER_FAST.md index 569b33f..24b1389 100644 --- a/DEVELOPER_FAST.md +++ b/DEVELOPER_FAST.md @@ -1,317 +1,28 @@ -# GOAT Flow Developer Guide (Concise) - -## 1. Goal -A minimal integration guide for merchants, covering: -- create order -- frontend payment execution -- callback calldata signature -- error handling -- fee and order cancellation -- frontend SDK and server SDKs (TS / Go) - ---- - -## 2. Integration Boundaries -- Frontend (`goatflow-sdk`): wallet signing and token transfer only (EVM). -- Backend (TS package `goatflow-sdk-server` / [Go source module](goatx402-sdk-server-go/README.md)): call GOAT Flow Core APIs (HMAC authenticated). The Go module currently requires a local `replace`. -- Core: auth verification, order creation, fee charge, on-chain payment watching, state transition, proof issuance. - -**Security baseline:** `API_KEY` / `API_SECRET` must only exist on backend, never in frontend. - ---- - -## 3. Unified Environment Variables -Use this naming convention: - -```bash -GOATX402_API_URL=https://flow-api.goat.network -GOATX402_API_KEY=your_api_key -GOATX402_API_SECRET=your_api_secret -GOATX402_MERCHANT_ID=your_merchant_id -``` - -Note: `GOATX402_BASE_URL` in old docs has the same meaning as `GOATX402_API_URL`. Prefer `GOATX402_API_URL`. - ---- - -## 3.1 Recommended Browser Path: Hosted Checkout - -Use `goatflow-checkout` when the platform-hosted page should own wallet connection -and payment UX: - -```bash -pnpm install goatflow-checkout -``` - -A fixed DIRECT QuickPay product can open without a merchant backend: - -```ts -import { GoatCheckout } from 'goatflow-checkout' - -const goat = GoatCheckout({ origin: 'https://pay.goat.network' }) -goat.open({ merchant: 'merchant_123', productKey: 'mug' }) -``` - -Dynamic DIRECT and all DELEGATE checkout must be created on the backend: - -```ts -const session = await client.createCheckoutSession({ - checkoutType: 'DIRECT', - price: '19.95', - clientReferenceId: 'cart_123', -}) - -// Browser: -goat.open({ checkoutId: session.checkoutId }) -``` - -For cross-chain DELEGATE use `checkoutType: 'DELEGATE'` with `price`; Core -derives eligible source-chain/token candidates and the fixed callback chain. The -legacy single-chain form uses `chainId`, `fixedAmountWei`, and -`acceptableTokens`. - -Browser callbacks are UX-only. Fulfill from `quickpay.checkout.completed` or a -trusted backend status check. Full guide: `docs/x402-checkout.md`. - ---- - -## 4. Core Flow -1. Frontend requests your backend to create an order. -2. Backend calls `POST /api/v1/orders` (Server SDK). -3. Core returns x402 response (**HTTP 402 is success**), backend returns normalized `Order` to frontend. -4. If `order.calldataSignRequest` exists, frontend signs first and sends signature to backend. -5. Backend calls `POST /api/v1/orders/{id}/calldata-signature`. -6. Frontend calls `payment.pay(order)` and transfers to `order.payToAddress`. -7. Backend polls order status and fetches proof after confirmation. -8. Cancel unused orders in time (`CHECKOUT_VERIFIED` can be cancelled and refunded). - -**Key fact:** For all flows, user-side action is transfer to `payToAddress`. `ERC20_3009/APPROVE_XFER` is Core's settlement mechanism, not frontend "auto gasless payment". - ---- - -## 5. Frontend SDK (`goatflow-sdk`) -Install: - -```bash -pnpm install goatflow-sdk ethers -``` - -Example: - -```ts -import { PaymentHelper } from 'goatflow-sdk' -import { ethers } from 'ethers' - -const provider = new ethers.BrowserProvider(window.ethereum) -const signer = await provider.getSigner() -const payment = new PaymentHelper(signer) - -// order comes from your backend -if (order.calldataSignRequest) { - const signature = await payment.signCalldata(order) - await fetch(`/api/orders/${order.orderId}/signature`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ signature }), - }) -} - -const result = await payment.pay(order) -if (!result.success) { - console.error(result.error) -} -``` - -Notes: -- `goatflow-sdk` depends on `ethers` and is an EVM SDK. - ---- - -## 6. Server SDK (TypeScript) -Install: - -```bash -pnpm install goatflow-sdk-server -``` - -Initialize: - -```ts -import { GoatFlowClient } from 'goatflow-sdk-server' - -const client = new GoatFlowClient({ - baseUrl: process.env.GOATX402_API_URL!, - apiKey: process.env.GOATX402_API_KEY!, - apiSecret: process.env.GOATX402_API_SECRET!, -}) -``` - -Create order: - -```ts -const order = await client.createOrder({ - dappOrderId: `order_${Date.now()}`, - chainId: 137, - tokenSymbol: 'USDC', - tokenContract: '0x...', - fromAddress: '0xUser', - amountWei: '1000000', - // callbackCalldata: '0x...' // DELEGATE + callback scenario -}) -``` - -Submit calldata signature: - -```ts -await client.submitCalldataSignature(order.orderId, signature) -``` - -Poll status / get proof / cancel: - -```ts -const status = await client.getOrderStatus(order.orderId) - -if (status.status === 'PAYMENT_CONFIRMED') { - const proof = await client.getOrderProof(order.orderId) -} - -await client.cancelOrder(order.orderId) // only cancellable in CHECKOUT_VERIFIED -``` - ---- - -## 7. Server SDK (Go) - -The Go SDK is source-only and is not available from a standalone module -repository. Clone the public repository next to your application: - -```bash -git clone https://github.com/GOATNetwork/x402.git -``` - -Add this to your application's `go.mod`, adjusting the relative path if needed: - -```go -require github.com/goatnetwork/goatflow-sdk-server v0.0.0 - -replace github.com/goatnetwork/goatflow-sdk-server => ../x402/goatx402-sdk-server-go -``` - -Run `go mod tidy`. See the [Go SDK source instructions](goatx402-sdk-server-go/README.md) -for details. - -Example: - -```go -import goatflow "github.com/goatnetwork/goatflow-sdk-server" - -client := goatflow.NewClient(goatflow.Config{ - BaseURL: os.Getenv("GOATX402_API_URL"), - APIKey: os.Getenv("GOATX402_API_KEY"), - APISecret: os.Getenv("GOATX402_API_SECRET"), -}) - -order, err := client.CreateOrder(ctx, goatflow.CreateOrderParams{ - DappOrderID: "order_123", - ChainID: 137, - TokenSymbol: "USDC", - TokenContract: "0x...", - FromAddress: "0xUser", - AmountWei: "1000000", -}) -if err != nil { - if apiErr, ok := err.(*goatflow.APIError); ok { - log.Printf("status=%d message=%s", apiErr.Status, apiErr.Message) - } -} - -status, _ := client.GetOrderStatus(ctx, order.OrderID) -_ = status -``` - ---- - -## 8. Callback Signature (DELEGATE) -When `callbackCalldata` is sent during order creation and merchant config is valid, Core returns `calldataSignRequest`: - -1. Frontend calls `payment.signCalldata(order)` to generate user signature. -2. Backend submits signature to `/api/v1/orders/{id}/calldata-signature`. -3. Frontend executes `payment.pay(order)`. - -**Do not hardcode EIP-712 domain/type on frontend.** Always use `calldataSignRequest` returned in the order. - ---- - -## 9. Callback Contract Setup (`MerchantCallback`) -Recommended deployment via repository script (Upgradeable + Proxy): - -```bash -cd goatx402-contract -PRIVATE_KEY= forge script script/DeployMerchantCallback.s.sol:DeployMerchantCallback \ - --rpc-url \ - --broadcast -``` - -Merchant setup by GOAT Flow: - -Send the following fields to GOAT Flow for merchant setup: -- `merchant_id` -- `chain_id` -- `spent_address` -- `eip712_name` -- `eip712_version` - -Notes: -- The deploy script reads the deployer key from the `PRIVATE_KEY` environment variable. -- `eip712_name` and `eip712_version` are required in callback signature flow. -- Add GOAT Flow authorized caller (`x402d`) to callback contract allowlist. - ---- - -## 10. Error Codes (from `goatx402-core`) -For Core public APIs, HTTP status can be treated as error code: - -| HTTP | Meaning | Common Triggers | -| --- | --- | --- | -| 400 | Request validation / business rule failure | missing fields, invalid address, unsupported token, insufficient fee, invalid signature format, non-cancellable status | -| 401 | Authentication failure | missing/invalid `X-API-Key` / `X-Timestamp` / `X-Nonce` / `X-Sign`; `nonce` must be included in the signed params | -| 403 | Authorization failure | merchant mismatch, order not owned by current merchant | -| 404 | Resource not found | merchant/order/proof not found | -| 500 | Internal server error | Core internal exception | - -**Important:** `POST /api/v1/orders` returns **402 Payment Required** on success (x402 protocol), not a failure. - -Error body may use `error` or `message`. Handle both. - ---- - -## 11. Fee and Order Cancellation -- Fee is charged when creating an order. -- If order will not continue, cancel quickly (`CHECKOUT_VERIFIED` required). -- After cancel, Core releases reserved amount and refunds order fee. -- In Core expiration path, expired orders also release reservation and refund fee. - -Recommended practices: -1. Cancel from backend when frontend times out/user closes payment page. -2. Add scheduled cleanup on backend for long-unpaid orders. -3. Alert on `insufficient fee balance` to avoid checkout outage. - ---- - -## 12. Flow Quick Reference -| Flow | User Transfer Target | Description | -| --- | --- | --- | -| `ERC20_DIRECT` | merchant address | direct payment | -| `ERC20_3009` | TSS address | user pays TSS first, Core settles via EIP-3009 | -| `ERC20_APPROVE_XFER` | TSS address | user pays TSS first, Core settles via Permit2 | - ---- - -## 13. Minimum Go-Live Checklist -1. Store and isolate `API_SECRET` on backend only. -2. Add monitoring for `insufficient fee balance`. -3. Auto-cancel stale `CHECKOUT_VERIFIED` orders. -4. Verify order-status polling and proof retrieval flow. -5. Validate DELEGATE + callback `calldataSignRequest` signature submission flow. -6. For Hosted Checkout, subscribe to `quickpay.checkout.completed` and never - fulfill from the browser callback alone. +# GOAT Flow Developer Compatibility Page + +This root-level file is retained for existing links. The maintained developer +documentation is: + +1. [Developer Quick Start](./docs/goat-flow-developer-quickstart.md) for the shortest + path to a first DIRECT payment. +2. [Integration Guide](./docs/goat-flow-integration.md) for architecture, SDK usage, + lifecycle handling, and production checks. +3. [API Reference](./docs/goat-flow-api-reference.md) for the wire contract and HMAC + authentication. +4. [Hosted Checkout](./docs/goat-flow-checkout.md) for fixed products and dynamic + checkout sessions. +5. [GOAT Flow MPP Integration](./docs/mpp.md) for the independent MPP protocol + boundary and GOAT Flow's current agent-paid API adapter. +6. [DApp Integration Skill](./docs/goat-flow-dapp-integration/SKILL.md) for + repository-aware coding-agent integrations. + +Compatibility rules: + +- Keep `GOATX402_API_SECRET` and merchant signing on the backend. +- Treat browser callbacks as UX events, not fulfillment proof. +- Read payment terms and supported capabilities from runtime responses. +- Use the current `goatflow-*` public package names. Preserve fixed protocol + fields and `GOATX402_*` environment-variable names. + +Update the canonical documents above instead of adding detailed integration +instructions to this compatibility page. diff --git a/ONBOARDING.md b/ONBOARDING.md deleted file mode 100644 index 83633dc..0000000 --- a/ONBOARDING.md +++ /dev/null @@ -1,712 +0,0 @@ -# x402 Onboarding Guide - -This is the admin/operator + developer runbook. Merchant-facing onboarding lives in docs/x402-onboarding-guide.md and docs/merchant-guide.md. - -A complete admin/operator and developer runbook for GOAT Flow payments — covering **admin setup**, **developer integration**, and **end-to-end testing**. Includes both Dashboard and CLI workflows. - ---- - -## Table of Contents - -1. [Roles Overview](#1-roles-overview) -2. [Admin: Merchant Setup](#2-admin-merchant-setup) -3. [Developer: Project Setup](#3-developer-project-setup) -4. [Developer: Callback Contract Deployment](#4-developer-callback-contract-deployment) -5. [Admin: Finalize Configuration](#5-admin-finalize-configuration) -6. [Developer: Run the Demo](#6-developer-run-the-demo) -7. [User: Making a Payment (Browser)](#7-user-making-a-payment-browser) -8. [Developer: API-Only Test Payment](#8-developer-api-only-test-payment) -9. [Full Test Log (Real Example)](#9-full-test-log-real-example) -10. [Go-Live Checklist](#10-go-live-checklist) -11. [Troubleshooting](#11-troubleshooting) - ---- - -## 1. Roles Overview - -| Role | Responsibilities | -|---|---| -| **GOAT Flow Admin** | Create merchant, configure chains/tokens, set up fee balance, register callback contracts | -| **Developer (Merchant)** | Integrate SDK, deploy callback contract, run backend server, handle order lifecycle | -| **User (Payer)** | Connect wallet, approve payment, transfer tokens | - -### Network Labels Used in This Runbook - -Every network-specific value below is labeled as **Testnet3 (development)** or **Mainnet (production)**. - -| Network label | Chain | Chain ID | RPC URL | Block explorer | -|---|---|---:|---|---| -| **Testnet3 (development)** | GOAT Testnet3 | `48816` | `https://rpc.testnet3.goat.network` | `https://explorer.testnet3.goat.network` | -| **Mainnet (production)** | GOAT mainnet | `2345` | `https://rpc.goat.network` | `https://explorer.goat.network` | - -**Testnet3 (development) only:** faucet and gas-tip guidance applies only to GOAT Testnet3. Use the faucet at `https://bridge.testnet3.goat.network/faucet`; contract deployment examples use `--priority-gas-price 130000` and `--gas-price 1000000`. - -**Mainnet (production):** there is no faucet. Use production native gas and the live gas policy for the selected chain; do not copy Testnet3 gas-tip values into production unless an operator explicitly confirms them. - -Other production explorer labels from the supported-chain matrix include Metis `https://andromeda-explorer.metis.io`, Tempo `https://explore.tempo.xyz`, Base `https://basescan.org`, and X Layer `https://web3.okx.com/explorer/x-layer/evm`. - ---- - -## 2. Admin: Merchant Setup - -Admin setup can be done via the **Admin Dashboard** (web UI) or the **Admin API** (CLI/curl). - -### Admin Auth Model - -The legacy single `ADMIN_API_TOKEN` Bearer token was removed. Admin API access now uses multi-user session login with mandatory TOTP 2FA and per-route RBAC (`viewer`, `operator`, `super_admin`). Session tokens are stored server-side as `sha256(token)` and sent to clients as HttpOnly cookies (`__Host-admin_session` when secure cookies are enabled). - -Non-onboarding admin routes require a fully authenticated session: `POST /admin/auth/login` creates a partial session, then enrolled admins complete `POST /admin/auth/totp/verify`. Non-interactive operator scripts use `ADMIN_TOTP_CODE` for that second factor, matching `scripts/m4/pilot_onboard.sh` and `scripts/m4/rollback.sh`. If login returns `next: "change_password"` or `next: "totp_enroll"`, complete that onboarding in the Admin Dashboard before using CLI admin operations. - -### Prerequisites (CLI Session Auth) - -```bash -# Mainnet (production) -export API_URL="https://flow-api.goat.network" - -# Testnet3 (development) -# Use the operator-provided Testnet3/staging API base for that environment. -# Do not point Testnet3 chain examples at production unless that deployment is configured for Testnet3. -# export API_URL="" - -export ADMIN_USERNAME="" -export ADMIN_PASSWORD="" -export ADMIN_TOTP_CODE="" - -COOKIE_JAR="$(mktemp -t x402_admin_cookie.XXXXXX)" - -# 1) Password login: creates a partial session cookie. -curl -sS -X POST -H "Content-Type: application/json" \ - -c "$COOKIE_JAR" \ - "$API_URL/admin/auth/login" \ - -d "{\"username\":\"$ADMIN_USERNAME\",\"password\":\"$ADMIN_PASSWORD\"}" | jq . - -# 2) Mandatory TOTP: upgrades the session to full auth. -curl -sS -X POST -H "Content-Type: application/json" \ - -b "$COOKIE_JAR" -c "$COOKIE_JAR" \ - "$API_URL/admin/auth/totp/verify" \ - -d "{\"code\":\"$ADMIN_TOTP_CODE\"}" | jq . -``` - -### 2a. Create Merchant - -**Dashboard:** Go to Merchants → Create New Merchant - -**CLI:** -```bash -curl -s -X POST -b "$COOKIE_JAR" -c "$COOKIE_JAR" -H "Content-Type: application/json" \ - "$API_URL/admin/merchants" -d '{ - "merchant_id": "my_shop", - "name": "My Shop", - "receive_type": "DELEGATE", - "generate_api_keys": true -}' -``` - -Response (save these immediately!): -```json -{ - "merchant_id": "my_shop", - "api_key": "gHfeB7Il...", - "api_secret": "rIeDeF0M...", - "success": true -} -``` - -> ⚠️ **Save `api_key` and `api_secret` immediately.** The secret is only shown once. If lost, rotate keys with: -> ```bash -> curl -s -X POST -b "$COOKIE_JAR" -c "$COOKIE_JAR" "$API_URL/admin/merchants/my_shop/rotate-keys" -> ``` - -**Receive types:** -- `DIRECT` — user pays directly to merchant address -- `DELEGATE` — TSS-assisted EVM settlement via EIP-3009 or Permit2, with one - configured merchant callback chain and potentially a different eligible source chain - -### 2b. Add Merchant Token Addresses - -For each chain/token the merchant accepts, add a receiving address: - -**CLI:** -```bash -# Testnet3 (development): add USDT on GOAT Testnet3. -curl -s -X POST -b "$COOKIE_JAR" -c "$COOKIE_JAR" -H "Content-Type: application/json" \ - "$API_URL/admin/merchants/my_shop/addresses" -d '{ - "chain_id": 48816, - "token_contract": "0xdce0af57e8f2ce957b3838cd2a2f3f3677965dd3", - "symbol": "USDT", - "address": "0xYourMerchantWallet" -}' - -# Testnet3 (development): add USDC on GOAT Testnet3. -curl -s -X POST -b "$COOKIE_JAR" -c "$COOKIE_JAR" -H "Content-Type: application/json" \ - "$API_URL/admin/merchants/my_shop/addresses" -d '{ - "chain_id": 48816, - "token_contract": "0x29d1ee93e9ecf6e50f309f498e40a6b42d352fa1", - "symbol": "USDC", - "address": "0xYourMerchantWallet" -}' -``` - -**Token contracts by network:** - -| Network label | Chain | Chain ID | Token | Contract / source | -|---|---|---:|---|---| -| **Testnet3 (development)** | GOAT Testnet3 | `48816` | USDT | `0xdce0af57e8f2ce957b3838cd2a2f3f3677965dd3` | -| **Testnet3 (development)** | GOAT Testnet3 | `48816` | USDC | `0x29d1ee93e9ecf6e50f309f498e40a6b42d352fa1` | -| **Testnet (development)** | BSC Testnet | `97` | USDT | `0x85181e18011d60ffebdf78fda202c2f5896eecae` | -| **Testnet (development)** | BSC Testnet | `97` | USDC | `0xa4b9550a5835ba669edd759cf82e6ca2d5e2c0a2` | -| **Testnet (development)** | Sepolia | `11155111` | USDT | `0xb7af9c6da7c7e7ec69d06466d326b9c2a2fbc0f8` | -| **Testnet (development)** | Sepolia | `11155111` | USDC | `0xff6981ac8f983914a9ea8d27b13c07d8d62c4a3b` | -| **Mainnet (production)** | GOAT mainnet | `2345` | USDC/USDT or configured token | Read the live production value from the Admin Dashboard or `GET /admin/tokens?chain_id=2345` after session auth. Do not reuse Testnet3 token contracts on mainnet. | - -**Mainnet (production) token check:** -```bash -curl -s -b "$COOKIE_JAR" -c "$COOKIE_JAR" \ - "$API_URL/admin/tokens?chain_id=2345" | jq . -``` - -### 2c. Top Up Fee Balance - -Order creation deducts a fee (in USD) from the merchant's fee balance. **If the balance is zero, order creation will fail.** - -**CLI:** -```bash -# Top up $100 -curl -s -X POST -b "$COOKIE_JAR" -c "$COOKIE_JAR" -H "Content-Type: application/json" \ - "$API_URL/admin/fees/topup" -d '{ - "merchant_id": "my_shop", - "amount_usd": 100, - "description": "Initial topup" -}' - -# Check balance -curl -s -b "$COOKIE_JAR" -c "$COOKIE_JAR" "$API_URL/admin/fees/balance/my_shop" -``` - -> ⚠️ This is a common blocker — if order creation returns `insufficient fee balance`, top up here. - -### 2d. Verify Merchant Setup - -**CLI:** -```bash -# Full merchant details -curl -s -b "$COOKIE_JAR" -c "$COOKIE_JAR" "$API_URL/admin/merchants/my_shop" | jq . - -# Public merchant info (no auth needed) -curl -s "$API_URL/merchants/my_shop" | jq . -``` - -At this point you should see: -- ✅ Merchant enabled -- ✅ Token addresses configured -- ✅ Fee balance > 0 - ---- - -## 3. Developer: Project Setup - -### 3a. Clone and Install - -```bash -git clone https://github.com/GOATNetwork/x402.git -cd x402 - -# Install demo app dependencies -cd goatx402-demo -pnpm install - -# Install server SDK (repo directory is goatx402-sdk-server-ts; -# npm package name is goatflow-sdk-server) -cd ../goatx402-sdk-server-ts -pnpm install - -cd ../goatx402-demo -``` - -### 3b. Configure Environment - -Create `goatx402-demo/.env` with the credentials from the admin: - -```bash -GOATX402_MERCHANT_ID=my_shop -# Mainnet (production) -GOATX402_API_URL=https://flow-api.goat.network -# Testnet3 (development): use the operator-provided Testnet3/staging API base. -# GOATX402_API_URL= -GOATX402_API_KEY=your_api_key -GOATX402_API_SECRET=your_api_secret - -PORT=3001 -``` - -> ⚠️ **Never commit `.env` to version control.** It's already in `.gitignore`. - -### 3c. Verify API Connection - -```bash -# Start the backend -pnpm dev:server - -# In another terminal: -curl http://localhost:3001/api/health -# → {"status":"ok"} - -# Check merchant config — should show your chains and tokens -curl http://localhost:3001/api/config | jq . -``` - -If `chains` is empty, go back to [Step 2b](#2b-add-merchant-token-addresses). - ---- - -## 4. Developer: Callback Contract Deployment - -**Required for DELEGATE flow** (`ERC20_APPROVE_XFER` or `ERC20_3009`). Skip if using `DIRECT` flow. - -### 4a. Install Foundry - -```bash -curl -L https://foundry.paradigm.xyz | bash -foundryup -``` - -### 4b. Get Native Tokens for Gas - -**Testnet3 (development):** Use the [GOAT Testnet3 faucet](https://bridge.testnet3.goat.network/faucet) to get BTC for gas. - -**Mainnet (production):** No faucet. Fund the deployer with production native gas on GOAT mainnet (`chain_id=2345`) before deploying. - -### 4c. Deploy the Contract - -```bash -cd goatx402-contract - -# Install Solidity dependencies -forge install - -# Testnet3 (development): deploy on GOAT Testnet3 (chain_id=48816). -PRIVATE_KEY= forge script script/DeployMerchantCallback.s.sol:DeployMerchantCallback \ - --rpc-url https://rpc.testnet3.goat.network \ - --broadcast \ - --priority-gas-price 130000 \ - --gas-price 1000000 - -# Mainnet (production): deploy on GOAT mainnet (chain_id=2345). -# Use production gas policy; do not copy Testnet3 faucet/gas-tip assumptions. -PRIVATE_KEY= forge script script/DeployMerchantCallback.s.sol:DeployMerchantCallback \ - --rpc-url https://rpc.goat.network \ - --broadcast -``` - -> **Testnet3 (development) GOAT notes:** -> - Minimum gas tip: `130000` wei (transactions will be rejected below this) -> - Always set both `--priority-gas-price` and `--gas-price` -> -> **Mainnet (production) GOAT notes:** -> - Chain ID: `2345` -> - RPC: `https://rpc.goat.network` -> - Explorer: `https://explorer.goat.network` - -### 4d. Note the Proxy Address - -From the deployment output: -``` -== Return == -proxy: address -``` - ---- - -## 5. Admin: Finalize Configuration - -After the developer deploys the callback contract: - -### 5a. Register Callback Contract - -**CLI:** -```bash -# Testnet3 (development): callback contract on GOAT Testnet3 (chain_id=48816). -curl -s -X POST -b "$COOKIE_JAR" -c "$COOKIE_JAR" -H "Content-Type: application/json" \ - "$API_URL/admin/merchants/my_shop/callback-contracts" -d '{ - "chain_id": 48816, - "spent_address": "", - "eip712_name": "GoatX402 Pay Callback", - "eip712_version": "1" -}' - -# Mainnet (production): callback contract on GOAT mainnet (chain_id=2345). -# Replace spent_address with the proxy deployed on mainnet. -curl -s -X POST -b "$COOKIE_JAR" -c "$COOKIE_JAR" -H "Content-Type: application/json" \ - "$API_URL/admin/merchants/my_shop/callback-contracts" -d '{ - "chain_id": 2345, - "spent_address": "", - "eip712_name": "GoatX402 Pay Callback", - "eip712_version": "1" -}' -``` - -`eip712_name` and `eip712_version` must match the deployed contract's EIP-712 domain. The repository deploy script initializes `MerchantCallback` with `GoatX402 Pay Callback` / `1` unless the owner later reinitializes the domain. - -### 5b. Verify Complete Setup - -**CLI:** -```bash -curl -s -b "$COOKIE_JAR" -c "$COOKIE_JAR" "$API_URL/admin/merchants/my_shop" | jq . -``` - -The merchant should now have: -- [x] `enabled: true` -- [x] `addresses` — token addresses per chain -- [x] `callback_contracts` — callback contract per chain (DELEGATE only) -- [x] Fee balance > 0 - ---- - -## 6. Developer: Run the Demo - -```bash -cd goatx402-demo -pnpm dev -``` - -This starts: -- **Frontend (Vite + React):** http://localhost:3000 -- **Backend (Express):** http://localhost:3001 - ---- - -## 7. User: Making a Payment (Browser) - -1. Open http://localhost:3000 in a browser with MetaMask -2. Click **Connect MetaMask** -3. Switch to the correct network: **Testnet3 (development)** GOAT Testnet3 or **Mainnet (production)** GOAT mainnet -4. Select a token (e.g., USDT) and enter an amount -5. Click **Pay** -6. If the flow requires a calldata signature, MetaMask will prompt for an EIP-712 signature first -7. MetaMask will prompt for the token transfer to the payment target (`payTo` in the x402 challenge; legacy demo responses may call it `payToAddress`) -8. Wait for the payment to be confirmed - -**User prerequisites:** -- MetaMask or compatible EVM wallet -- The target network added to MetaMask: - - **Testnet3 (development):** GOAT Testnet3, chain ID `48816`, RPC `https://rpc.testnet3.goat.network`, explorer `https://explorer.testnet3.goat.network` - - **Mainnet (production):** GOAT mainnet, chain ID `2345`, RPC `https://rpc.goat.network`, explorer `https://explorer.goat.network` -- Sufficient token balance (e.g., USDT) -- Small amount of native tokens for gas - -**Getting tokens:** -- **Testnet3 (development):** Native BTC for gas from the [GOAT Testnet3 faucet](https://bridge.testnet3.goat.network/faucet) -- **Testnet3 (development):** Test USDT/USDC: ask the GOAT Flow team, or use a faucet contract if available -- **Mainnet (production):** Use production GOAT mainnet gas and the production token contracts configured in Admin Dashboard / `GET /admin/tokens?chain_id=2345` - ---- - -## 8. Developer: API-Only Test Payment - -For headless testing without a browser/wallet, you can use `curl` + Foundry's `cast`. The concrete payload below is **Testnet3 (development)**. For **Mainnet (production)**, set `chainId` to `2345`, use a production token contract from `GET /admin/tokens?chain_id=2345`, and use `https://rpc.goat.network`. - -### 8a. Create an Order - -```bash -# Testnet3 (development): GOAT Testnet3 chain_id=48816, seeded USDT. -curl -X POST http://localhost:3001/api/orders \ - -H "Content-Type: application/json" \ - -d '{ - "chainId": 48816, - "tokenSymbol": "USDT", - "tokenContract": "0xdce0af57e8f2ce957b3838cd2a2f3f3677965dd3", - "fromAddress": "0xYOUR_WALLET", - "amountWei": "1000000" - }' -``` - -Response from the demo backend (HTTP 200 JSON). The backend wraps the server SDK; Core's raw `POST /api/v1/orders` response is HTTP 402 = **success** in x402 protocol: -```json -{ - "orderId": "007a6713-...", - "flow": "ERC20_APPROVE_XFER", - "payToAddress": "0x8D5403Cd...", - "expiresAt": 1771831788 -} -``` - -### 8b. Send Payment On-Chain - -```bash -# Testnet3 (development): uses GOAT Testnet3 RPC and testnet-only gas-tip values. -cast send \ - "transfer(address,uint256)" \ - --private-key \ - --rpc-url https://rpc.testnet3.goat.network \ - --gas-limit 100000 \ - --priority-gas-price 130000 \ - --gas-price 1000000 - -# Mainnet (production): use GOAT mainnet RPC and production gas policy. -cast send \ - "transfer(address,uint256)" \ - --private-key \ - --rpc-url https://rpc.goat.network -``` - -### 8c. Poll Order Status - -```bash -curl http://localhost:3001/api/orders/ -``` - -Status transitions: -``` -CHECKOUT_VERIFIED → PAYMENT_CONFIRMED → INVOICED - → EXPIRED (timeout) - → CANCELLED (manual) -``` - ---- - -## 9. Full Test Log (Real Example) - -Two complete end-to-end tests performed on **Testnet3 (development)** GOAT Testnet3. These are historical development examples; for **Mainnet (production)** use GOAT mainnet chain ID `2345`, RPC `https://rpc.goat.network`, explorer `https://explorer.goat.network`, and production token contracts from the Admin Dashboard/API. - -### Test 1: Openclaw_001 (2026-02-23) - -**Setup:** Merchant pre-configured by admin via Dashboard. - -| Item | Value | -|---|---| -| Merchant | `Openclaw_001` | -| Chain | **Testnet3 (development)** GOAT Testnet3 (`48816`) | -| Token | **Testnet3 (development)** USDT (`0xdce0af57...`) | -| Test Wallet | `0x2612567DFf7B6e03340d153F83a7Ca899c0b6299` | -| Callback Contract | `0x3D62128a3b1601cbc015E8a98Eda9BA051319ed4` | - -**Flow:** - -```bash -# 1. Health check -$ curl http://localhost:3001/api/health -{"status":"ok"} - -# 2. Verify config — 3 chains, USDC+USDT each -$ curl http://localhost:3001/api/config -{"merchantId":"Openclaw_001","chains":[...3 chains...]} - -# 3. Testnet3 (development): create order (1 USDT) -$ curl -X POST http://localhost:3001/api/orders -H "Content-Type: application/json" \ - -d '{"chainId":48816,"tokenSymbol":"USDT","tokenContract":"0xdce0af57e8f2ce957b3838cd2a2f3f3677965dd3","fromAddress":"0x2612567DFf7B6e03340d153F83a7Ca899c0b6299","amountWei":"1000000"}' -{"orderId":"007a6713-d1f0-45b2-be6c-e1e45ee81564","flow":"ERC20_APPROVE_XFER","payToAddress":"0x8D5403Cd1deD2982c758594BEcb4571A5B864057"} - -# 4. Testnet3 (development): send 1 USDT to TSS wallet -$ cast send 0xdce0af57e8f2ce957b3838cd2a2f3f3677965dd3 \ - "transfer(address,uint256)" 0x8D5403Cd1deD2982c758594BEcb4571A5B864057 1000000 \ - --private-key $PK --rpc-url https://rpc.testnet3.goat.network \ - --gas-limit 100000 --priority-gas-price 130000 --gas-price 1000000 -# status: 1 (success) -# transactionHash: 0x96395b112eece299cc5d91e5d0f58a53180a5aac709e20fbd18de4f9905b911a - -# 5. Poll status -$ curl http://localhost:3001/api/orders/007a6713-d1f0-45b2-be6c-e1e45ee81564 -# Poll 1: {"status":"CHECKOUT_VERIFIED"} -# Poll 2: {"status":"PAYMENT_CONFIRMED","txHash":"0x96395b11...","confirmedAt":"2026-02-23T07:10:05Z"} -``` - -✅ **Result:** Payment confirmed in ~5 seconds. -🔗 **Testnet3 (development) explorer:** [View transaction](https://explorer.testnet3.goat.network/tx/0x96395b112eece299cc5d91e5d0f58a53180a5aac709e20fbd18de4f9905b911a) - -### Test 2: claw_demo (2026-02-24) - -**Setup:** Merchant created entirely via Admin CLI (no Dashboard needed). - -```bash -# Admin: Create merchant -$ curl -s -X POST -b "$COOKIE_JAR" -c "$COOKIE_JAR" -H "Content-Type: application/json" \ - "$API_URL/admin/merchants" -d '{ - "merchant_id":"claw_demo","name":"Claw Demo Shop","receive_type":"DELEGATE","generate_api_keys":true}' -{"api_key":"gHfeB7Il...","api_secret":"rIeDeF0M...","success":true} - -# Admin: Testnet3 (development) add USDT address -$ curl -s -X POST -b "$COOKIE_JAR" -c "$COOKIE_JAR" -H "Content-Type: application/json" \ - "$API_URL/admin/merchants/claw_demo/addresses" -d '{ - "chain_id":48816,"token_contract":"0xdce0af57e8f2ce957b3838cd2a2f3f3677965dd3","symbol":"USDT","address":"0x2612567DFf7B6e03340d153F83a7Ca899c0b6299"}' -{"success":true} - -# Admin: Testnet3 (development) add USDC address -$ curl -s -X POST -b "$COOKIE_JAR" -c "$COOKIE_JAR" -H "Content-Type: application/json" \ - "$API_URL/admin/merchants/claw_demo/addresses" -d '{ - "chain_id":48816,"token_contract":"0x29d1ee93e9ecf6e50f309f498e40a6b42d352fa1","symbol":"USDC","address":"0x2612567DFf7B6e03340d153F83a7Ca899c0b6299"}' -{"success":true} - -# Admin: Testnet3 (development) register callback contract -$ curl -s -X POST -b "$COOKIE_JAR" -c "$COOKIE_JAR" -H "Content-Type: application/json" \ - "$API_URL/admin/merchants/claw_demo/callback-contracts" -d '{ - "chain_id":48816,"spent_address":"0x3D62128a3b1601cbc015E8a98Eda9BA051319ed4","eip712_name":"GoatX402 Pay Callback","eip712_version":"1"}' -{"success":true} - -# Admin: Top up fee balance -$ curl -s -X POST -b "$COOKIE_JAR" -c "$COOKIE_JAR" -H "Content-Type: application/json" \ - "$API_URL/admin/fees/topup" -d '{"merchant_id":"claw_demo","amount_usd":100,"description":"Initial topup"}' -{"new_balance":100,"success":true} - -# Admin: Verify -$ curl -s -b "$COOKIE_JAR" -c "$COOKIE_JAR" "$API_URL/admin/merchants/claw_demo" | jq . -# → enabled: true, 2 addresses, 1 callback contract, fee balance: $100 - -# Developer: Testnet3 (development) create order to verify -$ curl -s -X POST http://localhost:3001/api/orders -H "Content-Type: application/json" \ - -d '{"chainId":48816,"tokenSymbol":"USDT","tokenContract":"0xdce0af57e8f2ce957b3838cd2a2f3f3677965dd3","fromAddress":"0x2612567DFf7B6e03340d153F83a7Ca899c0b6299","amountWei":"1000000"}' -{"orderId":"63c0d5e8-c33d-4d64-b4d4-c85051245f94","flow":"ERC20_APPROVE_XFER","payToAddress":"0x8D5403Cd1deD2982c758594BEcb4571A5B864057"} -``` - -✅ **Result:** Full **Testnet3 (development)** merchant setup + order creation via CLI only — no Dashboard needed. - ---- - -## 10. Go-Live Checklist - -### Admin -- [ ] Merchant created with API credentials (`generate_api_keys: true`) -- [ ] Token addresses added for all target chains -- [ ] Fee balance topped up (`admin/fees/topup`) -- [ ] Callback contract registered (if using DELEGATE flow) -- [ ] Fee balance monitoring/alerts in place - -### Developer -- [ ] `API_SECRET` stored server-side only, never in frontend -- [ ] `.env` is in `.gitignore` -- [ ] Order status polling implemented -- [ ] Proof retrieval implemented (for on-chain verification) -- [ ] Auto-cancel stale `CHECKOUT_VERIFIED` orders -- [ ] Error handling for all [common errors](API.md) -- [ ] If DELEGATE flow: calldata signature submission works - -### User-Facing -- [ ] Target network details documented for users (chain ID, RPC URL, explorer URL) -- [ ] **Testnet3 (development):** faucet and test token instructions documented -- [ ] **Mainnet (production):** production token contracts verified in Admin Dashboard/API -- [ ] Clear error messages when wallet is on wrong network -- [ ] Payment timeout handling with user feedback - ---- - -## 11. Troubleshooting - -### Admin Issues - -| Error | Cause | Fix (CLI) | -|---|---|---| -| `merchant not found` | Merchant not created | `POST /admin/merchants` | -| `insufficient fee balance` | Fee balance is 0 | `POST /admin/fees/topup` | -| `token not supported on chain` | No address for this chain/token | `POST /admin/merchants/{id}/addresses` | -| `callback contract not configured` | Missing callback contract | `POST /admin/merchants/{id}/callback-contracts` | -| Need to check merchant status | — | `GET /admin/merchants/{id}` | -| Need to check fee balance | — | `GET /admin/fees/balance/{id}` | - -### Developer Issues - -| Error | Cause | Fix | -|---|---|---| -| `Invalid API Key` (401) | Wrong credentials | Verify `API_KEY`/`API_SECRET`; rotate if needed | -| `HMAC signature invalid` (401) | Timestamp drift or SDK mismatch | Sync server clock (NTP); update SDK | -| Empty `chains` in config | No addresses configured | Admin: add addresses per chain/token | -| Server exits silently | Unhandled exception | Add `process.on('uncaughtException')` handler | -| Order stays `CHECKOUT_VERIFIED` | Payment not detected | Verify exact `amountWei` to the correct payment target (`payTo` / legacy `payToAddress`) | -| `gas required exceeds allowance` | Insufficient native balance | **Testnet3 (development):** get gas from the [GOAT Testnet3 faucet](https://bridge.testnet3.goat.network/faucet). **Mainnet (production):** fund with production native gas. | -| `gas tip cap below minimum` | Gas price too low | **Testnet3 (development):** set `--priority-gas-price 130000`. **Mainnet (production):** use production gas policy. | - -### User Issues - -| Issue | Fix | -|---|---| -| MetaMask wrong network | **Testnet3 (development):** add GOAT Testnet3 chain `48816`, RPC `https://rpc.testnet3.goat.network`. **Mainnet (production):** add GOAT mainnet chain `2345`, RPC `https://rpc.goat.network`. | -| "Insufficient funds" | **Testnet3 (development):** get test gas/tokens from faucet/team. **Mainnet (production):** fund production gas/token balances. | -| Transaction pending forever | Increase gas price; check network status | - ---- - -## Admin CLI Quick Reference - -```bash -# Setup: run the session login + mandatory TOTP flow in "Prerequisites (CLI Session Auth)" first. -# Mainnet (production) -export API_URL="https://flow-api.goat.network" -# Testnet3 (development): export API_URL="" instead. - -# Merchant CRUD -curl -s -b "$COOKIE_JAR" -c "$COOKIE_JAR" "$API_URL/admin/merchants" # List -curl -s -b "$COOKIE_JAR" -c "$COOKIE_JAR" "$API_URL/admin/merchants/my_shop" # Get -curl -s -X POST -b "$COOKIE_JAR" -c "$COOKIE_JAR" -H "Content-Type: application/json" \ - "$API_URL/admin/merchants" -d '{...}' # Create -curl -s -X PUT -b "$COOKIE_JAR" -c "$COOKIE_JAR" -H "Content-Type: application/json" \ - "$API_URL/admin/merchants/my_shop" -d '{...}' # Update - -# Merchant addresses -curl -s -X POST -b "$COOKIE_JAR" -c "$COOKIE_JAR" -H "Content-Type: application/json" \ - "$API_URL/admin/merchants/my_shop/addresses" -d '{...}' # Add -# Testnet3 (development): chain_id=48816. Mainnet (production): use 2345. -curl -s -X DELETE -b "$COOKIE_JAR" -c "$COOKIE_JAR" \ - "$API_URL/admin/merchants/my_shop/addresses/48816/USDT" # Remove - -# Callback contracts -curl -s -X POST -b "$COOKIE_JAR" -c "$COOKIE_JAR" -H "Content-Type: application/json" \ - "$API_URL/admin/merchants/my_shop/callback-contracts" -d '{...}' # Add -# Testnet3 (development): chain_id=48816. Mainnet (production): use 2345. -curl -s -X DELETE -b "$COOKIE_JAR" -c "$COOKIE_JAR" \ - "$API_URL/admin/merchants/my_shop/callback-contracts/48816" # Remove - -# Fees -curl -s -b "$COOKIE_JAR" -c "$COOKIE_JAR" "$API_URL/admin/fees/balance/my_shop" # Check -curl -s -X POST -b "$COOKIE_JAR" -c "$COOKIE_JAR" -H "Content-Type: application/json" \ - "$API_URL/admin/fees/topup" -d '{"merchant_id":"my_shop","amount_usd":100}' # Topup - -# System -curl -s -b "$COOKIE_JAR" -c "$COOKIE_JAR" "$API_URL/admin/health" # Health -curl -s -b "$COOKIE_JAR" -c "$COOKIE_JAR" "$API_URL/admin/stats" # Stats -curl -s -b "$COOKIE_JAR" -c "$COOKIE_JAR" "$API_URL/admin/orders?merchant_id=my_shop" # Orders -``` - ---- - -## Architecture Overview - -``` -┌─────────────────────────────────────────────────────────────────┐ -│ ADMIN (Dashboard / CLI) │ -│ │ -│ 1. Create merchant 4. Register callback contract │ -│ 2. Add token addresses 5. Top up fee balance │ -│ 3. Generate API keys 6. Monitor orders & fees │ -└──────────────────────────────┬──────────────────────────────────┘ - │ configures - ▼ -┌──────────────┐ ┌──────────────┐ ┌──────────────────────┐ -│ Frontend │────▶│ Backend │────▶│ GOAT Flow Core API │ -│ (User) │ │ (Developer) │ │ │ -│ │ │ │ │ • Order management │ -│ • Connect │ │ • HMAC auth │ │ • Payment watching │ -│ wallet │ │ • Create │ │ • Fee deduction │ -│ • Sign EIP- │ │ orders │ │ • Proof issuance │ -│ 712 data │ │ • Poll │ │ • Settlement │ -│ • Transfer │ │ status │ │ │ -│ tokens │ │ • Get proof │ │ │ -└──────────────┘ └──────────────┘ └──────────┬───────────┘ - │ watches - ▼ - ┌──────────────────────┐ - │ Blockchain │ - │ │ - │ • Token transfers │ - │ • Callback contracts │ - │ • TSS settlement │ - └──────────────────────┘ -``` - ---- - -## Further Reading - -- [API Reference](API.md) — Full API docs with request/response schemas -- [Developer Guide](DEVELOPER_FAST.md) — Technical integration deep dive -- [goatflow-sdk](goatx402-sdk/) — Frontend SDK (EVM wallets) -- [goatx402-sdk-server-ts](goatx402-sdk-server-ts/) — TypeScript server SDK repo directory; npm package name: `goatflow-sdk-server` -- [goatx402-sdk-server-go](goatx402-sdk-server-go/) — Server SDK (Go) -- [goatx402-contract](goatx402-contract/) — Callback contract (Foundry) diff --git a/README.md b/README.md index 3bb131d..1197479 100644 --- a/README.md +++ b/README.md @@ -1,99 +1,136 @@ -# GOAT Flow (`goat-x402`) +# GOAT Flow -This repository is the public integration reference for **GOAT Flow**, GOAT -Network's x402 payment platform. +This repository contains the public SDKs, examples, and supporting components +for integrating GOAT Flow x402 payments and its current MPP adapter on GOAT +Network. -## What x402 Is Used For +## Current Public Product -x402 is a payment standard for crypto-native applications. -It allows an app or API to request token payment in a structured way, so users can complete payment from their wallet and the service can verify settlement. +The current public merchant path is **DIRECT**: -In short, GOAT Flow makes blockchain payments a standard part of application -access, checkout, and service flows. +- the payer transfers an ERC-20 token to the merchant's configured receiving + address; +- GOAT Flow software creates and tracks the order record; and +- the merchant confirms fulfillment from server-side order status or webhooks. -## What This Project Provides - -This project demonstrates how to integrate x402 in practice, including: - -- drop-in hosted checkout for DIRECT and DELEGATE merchants -- order creation and browser wallet payment workflows -- TypeScript and Go server SDK integration -- QuickPay payer/agent tooling and MPP receipt middleware -- callback/settlement contracts and a runnable demo - -## Choose an Integration +For the smallest integration, use a merchant-configured QuickPay product with +the hosted checkout. For a custom wallet and order UI, combine the browser SDK +with a server SDK so API credentials remain on the backend. | Need | Start here | | --- | --- | -| Hosted browser checkout | [`docs/x402-checkout.md`](docs/x402-checkout.md) and `goatflow-checkout` | -| Custom wallet/order UI | `goatflow-sdk` + `goatflow-sdk-server` (TypeScript package or [Go source](goatx402-sdk-server-go/README.md)) | +| Hosted DIRECT checkout | [`docs/goat-flow-checkout.md`](docs/goat-flow-checkout.md) and `goatflow-checkout` | +| Custom wallet/order UI | `goatflow-sdk` plus `goatflow-sdk-server` or the Go server SDK | | Agent or CLI payment | `goatflow-quickpay` | -| Verify MPP receipts in an API | `@goatnetwork/mpp-middleware` or [Go source](goatx402-mpp-middleware-go/README.md) | -| Callback contracts | `goatx402-contract` | - -Hosted Checkout has two server-authoritative forms under one Checkout Sessions -API: DIRECT uses a decimal `price`; DELEGATE uses either cross-chain decimal -`price` mode or the compatibility single-chain `fixed_amount_wei` mode. Fixed -DIRECT QuickPay products can also be opened without a merchant backend. - -## Public Modules - -| Package / module | Source | Purpose | +| Merchant onboarding | [`docs/goat-flow-onboarding-guide.md`](docs/goat-flow-onboarding-guide.md) | +| Merchant operations | [`docs/merchant-guide.md`](docs/merchant-guide.md) | + +Hosted checkout and QuickPay identify a product by merchant and product key. +Price, accepted tokens, receiving addresses, and other payment configuration +remain server-authoritative. + +## Optional And Internal Components + +The repository also contains components that are not required for the public +DIRECT merchant flow: + +- [`MerchantCallback.sol`](goatx402-contract/src/MerchantCallback.sol) is a + reference UUPS receiver for an optional, operator-provisioned callback + transfer flow. Its + `withCalldata` self-call has no selector allowlist; review the + [security model](goatx402-contract/MERCHANT_CALLBACK.md#calldata-execution-semantics). +- [`TopupCallback.sol`](goatx402-contract/src/TopupCallback.sol) is dedicated + to the internal `topup-service`; it is not a general merchant contract. +- [`USDC.sol`](goatx402-contract/src/USDC.sol) and + [`USDT.sol`](goatx402-contract/src/USDT.sol) are configurable test tokens, + not production token deployments. +- [MPP](https://mpp.dev/overview) is an independent open protocol, not a GOAT + Flow protocol. The MPP clients and middleware in this repository implement + GOAT Flow's current JSON-endpoint and signed-receipt profile, require an + enabled Core environment, and have no checked-in interoperability test with + official MPP SDKs. +- The demo contains config-gated advanced and MPP examples in addition to its + default DIRECT checkout path. + +Do not deploy a callback contract merely to use DIRECT checkout. + +## Repository Map + +| Module | Purpose | Distribution status | | --- | --- | --- | -| `goatflow-checkout` | [`goatx402-checkout`](goatx402-checkout) | Framework-free popup/tab/redirect browser SDK | -| `goatflow-sdk` | [`goatx402-sdk`](goatx402-sdk) | Low-level EVM wallet payment and MPP client primitives | -| `goatflow-sdk-server` | [`goatx402-sdk-server-ts`](goatx402-sdk-server-ts) | HMAC-authenticated TypeScript server SDK | -| Go server SDK (source-only) | [`goatx402-sdk-server-go`](goatx402-sdk-server-go/README.md) | HMAC-authenticated Go SDK; clone this repo and use a local `replace` | -| `goatflow-quickpay` | [`goatx402-quickpay`](goatx402-quickpay) | Manifest-driven payer/agent library and CLI | -| `@goatnetwork/mpp-middleware` | [`goatx402-mpp-middleware-ts`](goatx402-mpp-middleware-ts) | Express/Fastify MPP receipt verification | -| Go MPP middleware (source-only) | [`goatx402-mpp-middleware-go`](goatx402-mpp-middleware-go/README.md) | Go HTTP receipt verification; clone this repo and use a local `replace` | -| Contracts | [`goatx402-contract`](goatx402-contract) | MerchantCallback, TopupCallback, and test tokens | -| Demo | [`goatx402-demo`](goatx402-demo) | Hosted Checkout plus advanced Classic/MPP examples | - -## Chain Support - -GOAT Flow supports configured **EVM mainnet** chains. Each merchant still needs -per-chain token, fee, receiving-address, and callback-contract configuration -before taking payments on a chain. - -| Chain | Chain ID | DIRECT | DELEGATE | -| --- | ---: | --- | --- | -| Ethereum | `1` | Yes | Yes | -| Polygon | `137` | Yes | Yes | -| BSC | `56` | Yes | Yes | -| Arbitrum | `42161` | Yes | Yes | -| Optimism | `10` | Yes | Yes | -| Avalanche | `43114` | Yes | Yes | -| Base | `8453` | Yes | Yes | -| Berachain | `80094` | Yes | Yes | -| X Layer | `196` | Yes | Yes | -| GOAT | `2345` | Yes | Yes | -| Metis | `1088` | Yes | No | -| Tempo | `4217` | Yes | No | - -DIRECT means the payer transfers ERC-20 tokens directly to the merchant receiving -address. DELEGATE means EIP-3009 or Permit2 settlement through the merchant -callback contract and TSS submission. The table describes merchant settlement -chains: Metis and Tempo are DIRECT-only there. Eligible cross-chain DELEGATE -source payments are derived from live token/TSS configuration. +| [`goatflow-checkout`](goatx402-checkout/README.md) | Framework-free hosted-checkout browser SDK | Release-managed npm package | +| [`goatflow-sdk`](goatx402-sdk/README.md) | EVM buyer-wallet transfer and GOAT Flow MPP-profile client primitives | Release-managed npm package | +| [`goatflow-sdk-server`](goatx402-sdk-server-ts/README.md) | HMAC-authenticated TypeScript server SDK | Release-managed npm package | +| [`github.com/goatnetwork/goatflow-sdk-server`](goatx402-sdk-server-go/README.md) | HMAC-authenticated Go server SDK | Go module source | +| [`goatflow-quickpay`](goatx402-quickpay/README.md) | Manifest-driven payer/agent library and CLI | Release-managed npm package | +| [`@goatnetwork/mpp-middleware`](goatx402-mpp-middleware-ts/README.md) | Express/Fastify verification for the GOAT Flow MPP receipt extension | Source package; not in the npm release runbook | +| [`github.com/goatnetwork/goatflow-mpp-middleware-go`](goatx402-mpp-middleware-go/README.md) | Go HTTP verification for the GOAT Flow MPP receipt extension | Go module source | +| [`goatx402-contract`](goatx402-contract/README.md) | Optional/internal callbacks and local test tokens | Foundry project | +| [`goatx402-demo`](goatx402-demo/README.md) | DIRECT checkout plus optional advanced and MPP examples | Private local demo | + +The npm release procedure covers exactly the four packages marked +"Release-managed npm package"; see [`RELEASING.md`](RELEASING.md). + +## Chain And Token Configuration + +Runtime chain and token availability is configuration-driven. A merchant should +use the chains and tokens returned by the merchant API or shown in the Merchant +Portal rather than relying on a hard-coded repository list. + +### Supported Mainnet Chains + +The operator-supplied mainnet documentation baseline, reviewed July 23, 2026, +is listed below. It is not encoded as one authoritative matrix in this +repository; runtime API and portal configuration remain controlling. + +The separate [GOAT Network x402 overview](https://docs.goat.network/docs/build/x402) +may also list Polygon (`137`) and Avalanche (`43114`). They are intentionally +excluded from this GOAT Flow operator baseline until enabled by the active +deployment. Do not infer GOAT Flow availability from the protocol overview +alone. + +| Chain | Chain ID | Explorer | +| --- | ---: | --- | +| GOAT Network | `2345` | [explorer.goat.network](https://explorer.goat.network) | +| Ethereum | `1` | [etherscan.io](https://etherscan.io) | +| BSC | `56` | [bscscan.com](https://bscscan.com) | +| Arbitrum | `42161` | [arbiscan.io](https://arbiscan.io) | +| Optimism | `10` | [optimistic.etherscan.io](https://optimistic.etherscan.io) | +| Base | `8453` | [basescan.org](https://basescan.org) | +| Berachain | `80094` | [berascan.com](https://berascan.com) | +| X Layer | `196` | [X Layer Explorer](https://web3.okx.com/explorer/x-layer/evm) | +| Metis | `1088` | [andromeda-explorer.metis.io](https://andromeda-explorer.metis.io) | +| Tempo | `4217` | [explore.tempo.xyz](https://explore.tempo.xyz) | + +This table is a documentation baseline, not a per-merchant entitlement. Confirm +the enabled chain/token pairs and receiving addresses in the target environment +before integration or launch. + +For DIRECT, each enabled `(chain, token)` needs a valid merchant receiving +address and the deployment's associated service-fee/token configuration. Testnet aliases +inside `goatx402-contract/foundry.toml` are development conveniences and do not +describe the public production matrix. ## Development Documentation -Use `docs/README.md` as the canonical documentation hub. +Use [`docs/README.md`](docs/README.md) as the documentation hub. Quick references: -- `DEVELOPER_FAST.md` - concise SDK/backend integration guide. -- `API.md` - Core API and HMAC authentication reference. -- `docs/x402-checkout.md` - hosted Checkout Sessions and browser SDK. -- `docs/README.md` - structured public docs index, including QuickPay, Checkout, MPP, and agent integration paths. -Production API base URL: `https://flow-api.goat.network`. +- [Developer Quick Start](docs/goat-flow-developer-quickstart.md) - concise integration path. +- [API Reference](docs/goat-flow-api-reference.md) - Core API and HMAC authentication. +- [`docs/goat-flow-checkout.md`](docs/goat-flow-checkout.md) - hosted checkout. +- [`goatx402-demo/README.md`](goatx402-demo/README.md) - runnable demo modes. +- [`goatx402-contract/README.md`](goatx402-contract/README.md) - contract scope, + tests, and deployment tooling. + +The complete Mainnet and Testnet3 service-origin map is maintained in +[`docs/README.md`](docs/README.md#service-origins). The production API base +URL is `https://flow-api.goat.network`. ## License -No license has been declared for this repository as a whole. Unless and until -one is added, all rights are reserved by the project owners (GOAT Network); -contact them before any external use or redistribution. Exception: the four -npm packages — `goatflow-sdk`, `goatflow-sdk-server`, -`goatflow-quickpay`, and `goatflow-checkout` — are each MIT-licensed (see the -`LICENSE` file inside their source directories). +No single repository-wide license has been declared. The four release-managed +npm packages each include their own MIT `LICENSE` file. Other directories may +carry package-level license metadata; review the relevant module before reuse +or redistribution. diff --git a/RELEASING.md b/RELEASING.md index be635bb..412a559 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -1,55 +1,66 @@ # Releasing the npm packages -This repo publishes four npm packages: `goatflow-sdk` (from `goatx402-sdk/`), -`goatflow-sdk-server` (from `goatx402-sdk-server-ts/`), -`goatflow-quickpay` (from `goatx402-quickpay/`), and `goatflow-checkout` -(from `goatx402-checkout/`). They are the GOAT Flow-branded successors of the -corresponding `goatx402-*` packages and begin as fresh npm package names. The -automated publish workflow was removed for security reasons (unrestricted +This repo release-manages exactly four npm packages: + +| npm package | Directory | Required entry artifacts | +| --- | --- | --- | +| `goatflow-sdk` | `goatx402-sdk/` | `dist/index.js`, `dist/index.d.ts` | +| `goatflow-sdk-server` | `goatx402-sdk-server-ts/` | `dist/index.js`, `dist/index.d.ts` | +| `goatflow-quickpay` | `goatx402-quickpay/` | `dist/index.js`, `dist/index.d.ts`, `dist/cli.js` | +| `goatflow-checkout` | `goatx402-checkout/` | `dist/index.js`, `dist/index.d.ts`, `dist/checkout.global.js` | + +The private demo, Foundry project, Go modules, and +`goatx402-mpp-middleware-ts/` are outside this npm runbook. The presence of a +`package.json`, package name, or `prepublishOnly` script is not authorization to +publish a new package. Adding another release-managed package requires an +explicit process change, license/repository metadata review, release gates, and +approval before its first publication. + +The automated publish workflow was removed for security reasons (unrestricted trigger, unpinned actions); until a hardened workflow replaces it, releases follow this manual runbook. Every step is required — ad-hoc publishes drifting from git is the root cause this process exists to prevent. -## First GOAT Flow release bootstrap - -`goatflow-quickpay` declares `goatflow-sdk ^0.2.0` as an optional dependency. -Until `goatflow-sdk` exists on npm and satisfies pnpm's `minimumReleaseAge`, -`goatx402-quickpay/pnpm-workspace.yaml` temporarily links the sibling -`goatx402-sdk/` directory and the lockfile records -`goatflow-sdk: link:../goatx402-sdk`. This keeps the preparation branch's -frozen install reproducible, but it is not a valid final release state for -QuickPay because it tests against local source rather than the registry. - -The first branded release therefore uses two separate release cycles. Do not -merge the broad branding/docs PR before Cycle A: doing so would advertise fresh -package names before they exist on npm. - -1. **Cycle A — `goatflow-sdk@0.2.1` only.** Prepare a separate SDK-only release - PR from current `main`, including the SDK's complete branded release state. - Merge it, run all gates from a clean checkout of the merge commit, tag, - publish, and complete the registry smoke test. -2. **Refresh QuickPay on the still-unmerged broad branding branch.** Wait until - `goatflow-sdk@0.2.1` satisfies `minimumReleaseAge` (currently 24 hours). Keep - the persistent `allowBuilds`/`onlyBuiltDependencies` policy in - `goatx402-quickpay/pnpm-workspace.yaml`, but remove the temporary `packages`, - `linkWorkspacePackages`, and `sharedWorkspaceLockfile` entries and their - bootstrap comment. Run `pnpm update goatflow-sdk --lockfile-only` from - `goatx402-quickpay/`, verify `package.json` still contains the intended - `^0.2.0` range, assert `! grep -q 'link:' pnpm-lock.yaml`, and pass frozen - install plus every QuickPay gate. Update the branch on the Cycle A `main` - before its final review. -3. **Cycle B — `goatflow-quickpay@0.3.0`, - `goatflow-sdk-server@0.3.0`, and `goatflow-checkout@0.1.0`.** Stamp their - changelogs only after the registry-backed QuickPay lockfile passes. Merge the - broad branding/docs PR only when all three packages are release-ready, then - immediately validate and release them from that exact `main` merge commit. - The QuickPay tag and publish must never use the temporary workspace-link - state. - -Deprecating an old `goatx402-*` npm package is a separate, explicit release -action. Do it only after all corresponding `goatflow-*` packages have passed -post-publish verification and the user has authorized the deprecation message -and scope. +## GOAT Flow package identities + +The current released identities are `goatflow-sdk@0.2.1`, +`goatflow-sdk-server@0.3.0`, `goatflow-quickpay@0.3.0`, and +`goatflow-checkout@0.1.0`. Repository directory names remain `goatx402-*` and +must not be mistaken for npm package names. + +Deprecating or otherwise modifying an older `goatx402-*` npm package remains a +separate, explicit release action. Do not infer authorization from a GOAT Flow +release or from documentation changes. + +## Current Blockers And Known Issues + +Do not tag or publish while any release blocker remains: + +- **Current blocker:** all four package-local `pnpm-workspace.yaml` files omit + `packages`. pnpm `9.15.9` rejects the required install and gate commands with + `packages field missing or empty`. Repair and merge the workspace files + through a normal PR, then rerun every package gate. +- **Current blocker:** the repository does not pin pnpm with a root or + package-level `packageManager` field, Corepack contract, or equivalent + machine-enforced version. Record and enforce the reviewed pnpm version before + treating the workspace gate as reproducible. +- **Current blocker:** a generated declaration that ships in the Checkout npm + tarball still contains an obsolete example origin: + `goatx402-checkout/dist/types.d.ts` mentions `pay.goat.network`. Correct the + source comment, rebuild `dist`, and verify the tarball contains only the + active origins from `docs/README.md`. +- **Per-release blocker:** the candidate must be the exact tip of canonical + `GOATNetwork/x402` `main`, validated from a clean checkout whose `origin` + points to that repository. +- **Per-release blocker:** build one actual `.tgz` per package from that clean + commit, record its identity, and publish that exact file. A dry run or a + later rebuild is not the release artifact. + +Separately, the Foundry project is outside this npm runbook and currently +installs `forge-std` without a pinned revision. That blocks reproducible +contract build or deployment sign-off; see +[`goatx402-contract/README.md`](goatx402-contract/README.md#prerequisites). It +does not add the contract project to the npm release scope. ## Publish repository — GOATNetwork/x402 @@ -73,8 +84,10 @@ a commit that was not tested, and nothing from a dirty tree may ship. For every package being released: - set the intended version in `package.json`; -- replace its `Unreleased` CHANGELOG heading with the release date (do not - rewrite dates for versions already published); +- add a topmost ` - YYYY-MM-DD` entry to its `CHANGELOG.md`; if an + `Unreleased` section exists, rename that section, otherwise create the + version heading above the existing history; +- do not rewrite, reorder, or otherwise clean up historical release entries; - confirm `repository.url` still points to `GOATNetwork/x402`; and - merge all code, metadata, tests, and CHANGELOG changes into canonical `main` through the normal PR flow. @@ -88,14 +101,23 @@ working tree. Use a fresh clone or detached worktree at the exact merge commit. For example: ```bash +canonical_url="$(git remote get-url origin)" +case "$canonical_url" in + https://github.com/GOATNetwork/x402.git|git@github.com:GOATNetwork/x402.git) ;; + *) echo "origin is not canonical: $canonical_url" >&2; exit 1 ;; +esac + git fetch origin main -git worktree add --detach /tmp/x402-release +test "$(git rev-parse origin/main)" = "" +git worktree add --detach /tmp/x402-release "" cd /tmp/x402-release test "$(git rev-parse HEAD)" = "" test -z "$(git status --porcelain=v1)" ``` -Do not reuse the feature-branch worktree that prepared the release. +Do not reuse the feature-branch worktree that prepared the release. If +canonical `main` moves before tagging, stop and decide through a new PR/release +review whether the candidate must be rebuilt from the newer tip. ### 3. Run package gates @@ -107,21 +129,46 @@ pnpm install --frozen-lockfile npm run typecheck --if-present npm run test:run npm run build -npm pack --dry-run --json +mkdir -p /tmp/x402-release-tarballs +npm pack --json --pack-destination /tmp/x402-release-tarballs +``` + +The package-local `pnpm-workspace.yaml` must be accepted by the pinned/approved +pnpm version before any gate can count. If pnpm reports +`packages field missing or empty`, stop the release and repair the workspace +configuration through a normal PR. Do **not** use `--ignore-workspace` as a +release workaround: it bypasses the workspace file that carries pnpm +supply-chain/build policy. + +Review the pack JSON, including package name, version, filename, included +files, shasum, and integrity. Record those values plus an independent checksum +of the resulting `.tgz`, for example: + +```bash +shasum -a 256 "/tmp/x402-release-tarballs/" ``` -Review the pack JSON, including package name, version, included files, shasum, -and integrity. Record the shasum for comparison after publication. The -`prepublishOnly` hook repeats tests during `npm publish`, but is only a backstop -and does not replace this gate. +Run the tests explicitly as shown above; do not rely on lifecycle hooks as a +substitute. If source, metadata, dependencies, or the release commit changes, +discard the tarball and restart validation. + +The `goatx402-quickpay/` and `goatx402-checkout/` directories currently define an explicit +`typecheck` script. SDK and Server do not, so `--if-present` intentionally skips +that command for those two packages; their `build` commands still run `tsc` +against the shipping build configuration. Also run the package-specific smoke tests against the built output: -- QuickPay: exercise `--help`, `-h`, `help`, and any argument-boundary case - changed by the release. -- Checkout: verify the ESM entry and evaluate the shipped browser IIFE, - confirming it installs `window.GoatCheckout`. -- SDK and Server: import their ESM entry points and inspect non-empty exports. +- SDK: import `dist/index.js` from Node ESM and require non-empty exports. +- Server: import `dist/index.js` from Node ESM and require non-empty exports. +- QuickPay: import `dist/index.js`; exercise `dist/cli.js --help`, `-h`, `help`, + and any argument-boundary case changed by the release. +- Checkout: import `dist/index.js` and evaluate `dist/checkout.global.js` in a + browser-like global, confirming it installs `window.GoatCheckout`. + +Do not treat a successful TypeScript compile as proof that the package tarball +contains these files. The actual `npm pack --json` file list and `.tgz` are the +authoritative pre-publish artifacts. ### 4. Check npm identity, versions, and tags @@ -136,7 +183,7 @@ For each package/version, query npm immediately before tagging. The target version must not appear: ```bash -npm view versions --json --prefer-online +npm view '' versions --json --prefer-online ``` The intended annotated tag must be absent both locally and remotely: @@ -168,21 +215,28 @@ Read the remote tag back with `git ls-remote --tags` and confirm its peeled ### 6. Publish in dependency order -Preserve this relative order for whichever packages are in the release: +Preserve this order for whichever packages are in the release: 1. `goatflow-sdk` 2. `goatflow-quickpay` 3. `goatflow-sdk-server` 4. `goatflow-checkout` -QuickPay advertises the SDK 0.2.x line as an optional dependency, so the SDK -must be visible on npm before QuickPay is published. From each package's -directory in the tagged clean checkout, run: +QuickPay currently advertises `goatflow-sdk` through `^0.2.0` as an optional +dependency, so a newly released SDK in that +range must be visible on npm before QuickPay is published. Checkout and Server +do not currently depend on the other release-managed packages, but retaining +one deterministic order makes the evidence easier to audit. + +Publish the already validated tarball for each package: ```bash -npm publish +npm publish "/tmp/x402-release-tarballs/" ``` +npm must receive the exact `.tgz`; do not run bare `npm publish`, which repacks +the package directory and creates a second, unverified artifact. + npm may require a separate browser authorization for each publish. Keep the original command running until authorization returns. If a publish command is interrupted or its result is uncertain, query the exact version on npm before @@ -203,9 +257,21 @@ npm view '@' \ npm view '' dist-tags --json --prefer-online ``` -The registry shasum must equal the `npm pack --dry-run --json` shasum, and the -expected release must be the `latest` dist-tag unless the release deliberately -uses another tag. +The registry shasum and integrity must equal the values from the actual +pre-publish pack. Download the registry artifact to a separate directory and +compare bytes with the release tarball: + +```bash +mkdir -p /tmp/x402-registry-tarballs +npm pack '@' \ + --json --pack-destination /tmp/x402-registry-tarballs +cmp "/tmp/x402-release-tarballs/" \ + "/tmp/x402-registry-tarballs/" +``` + +The expected release must be the `latest` dist-tag unless the release +deliberately uses another tag. A checksum, integrity, or byte comparison +mismatch is a failed release; do not republish the same version. ### 8. Run post-publish smoke tests @@ -220,29 +286,32 @@ npm install --ignore-scripts --no-audit --no-fund --prefer-online \ '@' ``` -Import every released package from Node ESM and require non-empty exports. Run -QuickPay's installed CLI, including: +Import every released package from Node ESM and require non-empty exports. For +Checkout, also evaluate the installed `dist/checkout.global.js` and confirm +`window.GoatCheckout` exists. Run QuickPay's installed CLI, including: ```bash npx --no-install goatflow-quickpay --help ``` -Run Checkout's installed browser IIFE as well. For Cycle B, use -`npm ls goatflow-quickpay goatflow-sdk --depth=1` to confirm QuickPay actually -resolves the intended published SDK version. +Run Checkout's installed browser IIFE as well. When QuickPay and the SDK are +released together, use `npm ls goatflow-quickpay goatflow-sdk --depth=1` to +confirm QuickPay actually resolves the intended SDK version. + +Inspect the installed package contents as well as the imports. A local workspace +link, adjacent build output, or npm cache entry must not be allowed to satisfy +the smoke test accidentally. ### 9. Refresh QuickPay's SDK lock resolution After publishing an SDK version accepted by QuickPay's optional dependency -range, refresh `goatx402-quickpay/pnpm-lock.yaml` so it resolves that registry -version. This change must pass the normal PR flow. For the first GOAT Flow -release, make the refresh on the still-unmerged broad branding branch described -above; for later SDK releases, use a separate post-release PR. Retain the -persistent workspace build policy while removing only the temporary -sibling-package link. +range, refresh `goatx402-quickpay/pnpm-lock.yaml` so it resolves that version. +This is a separate post-release repository change and must pass the normal PR +flow. -The active pnpm supply-chain policy may reject a package until it satisfies -`minimumReleaseAge` (currently a 24-hour window). This is expected: +A repository-external or user-level pnpm supply-chain policy may reject a newly +published package until it satisfies `minimumReleaseAge`. Treat that rejection +as an expected safety gate: - never add an exclusion or weaken the policy to make the refresh pass; - never commit a lockfile that fails `pnpm install --frozen-lockfile`; @@ -260,7 +329,8 @@ be audited without reconstructing terminal history: - annotated tag names and peeled targets; - exact npm package names and versions; - test counts and package-specific smoke results; -- dry-run and registry shasums (and integrity values when useful); +- release tarball filenames, pack JSON, SHA-256 checksums, registry shasums and + integrity values, and registry byte-comparison results; - final dist-tags and resolved inter-package dependency versions; and - any deferred lockfile refresh with its eligibility time. diff --git a/docs/README.md b/docs/README.md index 2954c17..42ba3ad 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,196 +1,177 @@ # GOAT Flow Docs -This directory is the public documentation hub for **GOAT Flow**. It is designed to help different readers quickly find the right material based on their role and use case. +This is the public documentation hub for **GOAT Flow**, the GOAT Network +commerce and transfer-verification software for merchants, applications, and +agents using the x402 protocol. -The documentation here is intended to support four major needs: -- understanding what GOAT Flow is -- understanding why it matters -- onboarding merchants and operators -- integrating x402 into products, applications, and agent workflows +GOAT Flow provides four related integration surfaces: ---- - -## 1. If you are new to GOAT Flow - -Start here if you are seeing GOAT Flow for the first time and want the fastest path to understanding the product. +- authenticated x402 order creation and status/proof APIs +- hosted checkout for server-created sessions and QuickPay products +- public QuickPay discovery and payer-transfer tooling +- GOAT Flow's current integration profile for the open Machine Payments + Protocol (MPP), including challenges, receipts, and merchant middleware -### Recommended reading order -1. `what-is-x402.md` - A high-level introduction to what x402 is, what problem it solves, and how GOAT Flow fits into the payment flow. +GOAT Flow uses the **DIRECT** flow: the buyer transfers an ERC-20 token to the +merchant receiving address. -2. `why-x402.md` - Explains the product value, adoption logic, and the business scenarios where GOAT Flow is most useful. +[MPP](https://mpp.dev/overview) is an independent open protocol, not a GOAT Flow +protocol. The current MPP client and middleware implement a GOAT-specific +JSON-endpoint and signed-receipt profile; they are not official MPP SDKs and do +not establish generic MPP interoperability. -3. `x402-direct-vs-delegate.md` - Explains the two payment modes and when each one should be used. - -4. `x402-faq.md` - A quick-answer reference for common questions around payment flow, user experience, fees, wallets, and operational expectations. +GOAT Flow provides commerce and verification software for this flow. It observes +and verifies the on-chain transfer; customer funds do not pass through GOAT Flow. --- -## 2. For developers - -This section is for developers, technical PMs, integration engineers, and anyone implementing GOAT Flow in a product. +## Service Origins -### Recommended reading order -1. `x402-developer-quickstart.md` - The fastest path to understanding the integration flow and getting started with implementation. +| Surface | Testnet3 origin | Mainnet origin | Audience | +| --- | --- | --- | --- | +| GOAT Flow | — | `https://flow.goat.network` | Public application | +| Merchant Portal | `https://flow-merchant.testnet3.goat.network` | `https://flow-merchant.goat.network` | Merchants and merchant team members | +| Admin Portal | `https://flow-admin.testnet3.goat.network` | `https://flow-admin.goat.network` | Authorized GOAT Flow operators only | +| QuickPay / Hosted Checkout and same-origin public API | `https://flow-quickpay.testnet3.goat.network` | `https://flow-quickpay.goat.network` | Buyers, agents, and checkout integrations | +| Flow API / standalone MPP Core | `https://flow-api.testnet3.goat.network` | `https://flow-api.goat.network` | Authenticated server and explicitly configured standalone MPP integrations | -2. `x402-api-reference.md` - Covers API semantics, authentication, order creation, payment flow, status handling, and proof retrieval. +No Testnet3 counterpart to `flow.goat.network` is included in the current +deployment list. Do not reuse Mainnet credentials, merchant IDs, or +configuration in Testnet3. The Admin Portal is not a merchant or public API +integration surface. -3. `x402-checkout.md` - Covers the recommended hosted browser checkout, unified DIRECT/DELEGATE - Checkout Sessions, server SDK creation, and fulfillment boundaries. +The QuickPay client derives session and MPP paths from the trusted +`flow-quickpay` link origin and ignores absolute endpoint substitution from a +manifest. `flow-api` is the configured merchant API and standalone MPP Core +origin; do not silently swap these origins in client code even when a deployment +currently proxies equivalent routes. -4. `x402-agent-integration-guide.md` - Covers agent workflows, AI-assisted implementation patterns, and agent-oriented integration design. - -5. `../goatx402-quickpay/README.md` - Covers the QuickPay public payer/agent library and CLI, including `inspect`, - `pay-x402`, `pay-product`, and `pay-mpp`. +--- -6. `x402-dapp-integration-prompts/` - Prompt pack and OpenAI app metadata for AI-assisted dapp integration. +## npm Packages -7. `x402-direct-vs-delegate.md` - Important for understanding the operational difference between payment modes and how they affect product behavior. +The following packages are published to the public npm Registry. The `latest` +versions below were verified against the Registry on July 23, 2026; query npm and use a lockfile when +selecting an exact production version. All four packages require Node.js 18 or +later. -8. `x402-integration.md` - A deeper integration and architecture reference for developers who need more detail after the quickstart. +| Package | `latest` | Primary use | Install | +| --- | ---: | --- | --- | +| [`goatflow-sdk`](https://www.npmjs.com/package/goatflow-sdk) | `0.2.1` | Browser wallet transfers and the current GOAT Flow MPP adapter | `npm install goatflow-sdk` | +| [`goatflow-sdk-server`](https://www.npmjs.com/package/goatflow-sdk-server) | `0.3.0` | TypeScript merchant backend and HMAC-authenticated APIs | `npm install goatflow-sdk-server` | +| [`goatflow-checkout`](https://www.npmjs.com/package/goatflow-checkout) | `0.1.0` | Hosted Checkout browser integration | `npm install goatflow-checkout` | +| [`goatflow-quickpay`](https://www.npmjs.com/package/goatflow-quickpay) | `0.3.0` | QuickPay payer/agent library and CLI | `npm install goatflow-quickpay` | -### Developer goal -By reading the documents above, a developer should be able to understand: -- how orders are created -- how the frontend and backend coordinate payment flow -- what DIRECT and DELEGATE mean in practice -- how proof, callbacks, and settlement fit into integration design -- how Hosted Checkout, QuickPay Products, QuickPay agents, and MPP fit into payment flows +The TypeScript MPP middleware package name +`@goatnetwork/mpp-middleware`, the Go modules, contracts, and demo are not +public npm packages. Follow their package README files for source-based use; +do not infer Registry availability from a local `package.json`. --- -## 3. For business, BD, and partnerships +## Start Here -This section is for business-facing teammates who need to understand where GOAT Flow fits, how to explain it, and which scenarios it is best suited for. +1. [What is GOAT Flow](./what-is-goat-flow.md) explains the product, protocol, and + current payment surfaces. +2. [Why GOAT Flow](./why-goat-flow.md) explains the implementation-backed product + value and tradeoffs. +3. [GOAT Flow FAQ](./goat-flow-faq.md) answers common questions about payments, runtime + configuration, QuickPay, MPP, errors, and security boundaries. -### Recommended reading order -1. `what-is-x402.md` - Use this to understand the concept clearly and explain GOAT Flow at a high level. +--- -2. `why-x402.md` - Use this to understand the business value, adoption logic, and why GOAT Flow matters in merchant and agent economy contexts. +## Developer Path -3. `x402-direct-vs-delegate.md` - Important for explaining the product model and why different merchants may choose different payment modes. +1. [Developer Quick Start](./goat-flow-developer-quickstart.md) +2. [API Reference](./goat-flow-api-reference.md) +3. [Hosted Checkout](./goat-flow-checkout.md) +4. [Integration Guide](./goat-flow-integration.md) +5. [GOAT Flow MPP Integration](./mpp.md) +6. [DApp Integration Skill](./goat-flow-dapp-integration/SKILL.md) -4. `merchant-guide.md` - Useful for understanding the merchant-facing flow in practice, including onboarding, dashboard usage, API key setup, and operational steps. +The root-level [`API.md`](../API.md) and +[`DEVELOPER_FAST.md`](../DEVELOPER_FAST.md) files redirect historical links to +the maintained documents above. -5. `x402-faq.md` - Useful as a short-form business-facing support document for common questions. +Package references: -### Business goal -By reading the documents above, business-facing teammates should be able to understand: -- what GOAT Flow is -- how to position it clearly -- which scenarios fit DIRECT vs DELEGATE -- how merchant setup and usage look in practice +- [Browser payment SDK](../goatx402-sdk/README.md) +- [Server SDK](../goatx402-sdk-server-ts/README.md) +- [Go Server SDK](../goatx402-sdk-server-go/README.md) +- [Hosted Checkout SDK](../goatx402-checkout/README.md) +- [QuickPay library and CLI](../goatx402-quickpay/README.md) +- [TypeScript MPP middleware](../goatx402-mpp-middleware-ts/README.md) +- [Go MPP middleware](../goatx402-mpp-middleware-go/README.md) --- -## 4. For operations, onboarding, and merchant support - -This section is for operations teammates, onboarding managers, merchant support staff, and anyone responsible for helping merchants get started and use the system correctly. +## Merchant and Operations Path -### Recommended reading order -1. `x402-onboarding-guide.md` - Explains the onboarding flow and the steps merchants need to complete before going live. +1. [Onboarding Guide](./goat-flow-onboarding-guide.md) +2. [Merchant Guide](./merchant-guide.md) +3. [GOAT Flow FAQ](./goat-flow-faq.md) -2. `merchant-guide.md` - A practical merchant operations guide with screenshots, configuration examples, and merchant-side setup steps. +Merchant registration, approval, account recovery, 2FA, API-key rotation, fee +configuration, and portal permissions are deployment-operated concerns. Follow +the deployed portal and the merchant-facing guides for those procedures; the +public SDK types do not define their complete policy. -3. `merchant-guide.md` account recovery and security sections - Merchant account recovery, password changes, self-service 2FA, admin-assisted password reset, admin-assisted 2FA reset, self-service Topup, and QuickPay Products. - -4. `x402-faq.md` - A useful reference for answering common merchant questions and clarifying expected system behavior. +--- -5. `x402-direct-vs-delegate.md` - Useful when a merchant needs help deciding which payment mode is more appropriate. +## Choose an Integration Surface -### Operations goal -By reading the documents above, operations and support teammates should be able to understand: -- how merchants register and configure the system -- what settings and setup steps matter most -- what common merchant questions look like -- how to explain payment mode differences in plain language +| Need | Recommended surface | Important boundary | +| --- | --- | --- | +| Create and track a backend payment | Server SDK order API | HTTP 402 is the expected create-order challenge | +| Fixed-price public item | QuickPay Product + Checkout SDK | Product price is server-authoritative | +| Dynamic or server-priced purchase | Hosted Checkout Session | Backend creates the amount and terms | +| Custom amount, tip, or donation | QuickPay custom-amount flow | Browser-supplied amount is untrusted for fulfillment | +| Agent payment for a protected API route | Current GOAT Flow MPP profile | Success requires the profile's signed `Payment-Receipt` | --- -## 5. By topic +## Runtime Configuration -### Product understanding -- `what-is-x402.md` -- `why-x402.md` +Do not hardcode a global chain, token, minimum, or maximum list from narrative +documentation. -### Payment mode design -- `x402-direct-vs-delegate.md` +- Authenticated integrations receive payment terms from the x402 challenge. +- Public QuickPay integrations discover token limits, products, and MPP routes + from the merchant manifest. +- The server SDK's public merchant lookup exposes the merchant's configured + receive type and token entries. -### Merchant onboarding and usage -- `x402-onboarding-guide.md` -- `merchant-guide.md` +Fees, registration approval, account-security policy, and webhook event names +vary by environment. Confirm them with the active portal and API. -### Merchant account recovery and operations -- `merchant-guide.md` account recovery and security sections - -### Technical integration -- `x402-developer-quickstart.md` -- `x402-api-reference.md` -- `x402-checkout.md` -- `x402-integration.md` +--- -### Hosted Checkout, QuickPay, QuickPay Products, and MPP -- `x402-checkout.md` -- `../goatx402-checkout/README.md` -- `../goatx402-quickpay/README.md` -- `merchant-guide.md` QuickPay Products sections -- `x402-api-reference.md` +## Fulfillment Rule -### Agent and AI-driven integration -- `x402-agent-integration-guide.md` -- `x402-dapp-integration-prompts/` +Checkout browser callbacks are UX signals, not payment proof. Fulfill only after +a trusted backend status check or an authenticated webhook whose event name, +signature rules, and payload have been confirmed for the deployment. -### Quick answers and common questions -- `x402-faq.md` +In the current GOAT Flow MPP profile, the protected resource validates the +profile's signed `Payment-Receipt` with the supplied middleware before +continuing to the handler. --- -## 6. Full document list +## Document Index -| Document | Primary Audience | Main Purpose | +| Document | Primary audience | Purpose | | --- | --- | --- | -| `what-is-x402.md` | Everyone | Explains what x402 is and what GOAT Flow does | -| `why-x402.md` | Business, product, partnerships | Explains why GOAT Flow matters and where it creates value | -| `x402-direct-vs-delegate.md` | Everyone | Explains the two payment modes and their differences | -| `x402-onboarding-guide.md` | Operations, onboarding, merchants | Explains the onboarding flow and go-live steps | -| `merchant-guide.md` | Merchants, operations, support | Practical merchant setup and usage guide with screenshots | -| `x402-developer-quickstart.md` | Developers | Fastest path to integration | -| `x402-api-reference.md` | Developers | API semantics, lifecycle, and integration details | -| `x402-checkout.md` | Developers | Hosted Checkout, unified DIRECT/DELEGATE sessions, and fulfillment guidance | -| `x402-integration.md` | Developers, technical PMs | Deeper architecture and SDK integration reference | -| `x402-agent-integration-guide.md` | Developers, AI agent builders | Guidance for agent and AI-assisted integration workflows | -| `x402-dapp-integration-prompts/` | Developers, AI agent builders | Prompt pack and OpenAI app metadata for dapp integration | -| `../goatx402-quickpay/README.md` | Developers, AI agent builders | QuickPay payer/agent library and CLI reference | -| `../goatx402-checkout/README.md` | Frontend developers | Framework-free hosted checkout browser SDK reference | -| `merchant-guide.md` account recovery sections | Merchants, operations, support | Change password, forced password change, admin-assisted password reset, and 2FA reset | -| `merchant-guide.md` QuickPay Products sections | Merchants, operations, support | Product-key based QuickPay checkout setup and operations | -| `x402-faq.md` | Everyone | Common questions and short-form answers | - ---- - -## 7. Notes - -- Images referenced by `merchant-guide.md` are stored in `docs/images/`. -- This `docs/` directory is intended to serve as the public-facing documentation set in the repository. -- Older root-level files such as `API.md`, `DEVELOPER_FAST.md`, and `ONBOARDING.md` may still exist in the repository. The files in `docs/` are intended to function as the newer structured documentation set. +| [What is GOAT Flow](./what-is-goat-flow.md) | Everyone | Product and protocol overview | +| [Why GOAT Flow](./why-goat-flow.md) | Product, business, developers | Product value and tradeoffs | +| [GOAT Flow FAQ](./goat-flow-faq.md) | Everyone | Payments, configuration, errors, and security | +| [Onboarding Guide](./goat-flow-onboarding-guide.md) | Merchants, operations | Portal onboarding and go-live workflow | +| [Merchant Guide](./merchant-guide.md) | Merchants, support | Portal configuration and operations | +| [Developer Quick Start](./goat-flow-developer-quickstart.md) | Developers | First integration | +| [API Reference](./goat-flow-api-reference.md) | Developers | API contract | +| [Hosted Checkout](./goat-flow-checkout.md) | Frontend and backend developers | Product and session checkout | +| [Integration Guide](./goat-flow-integration.md) | Developers, technical PMs | Detailed architecture and SDK usage | +| [GOAT Flow MPP Integration](./mpp.md) | Agent and API developers | Protocol boundary plus GOAT-specific challenge, transfer, receipt, and middleware | +| [DApp Integration Skill](./goat-flow-dapp-integration/SKILL.md) | Coding agents | Integration workflow, deliverables, and acceptance criteria | + +Support: [Support@goat.network](mailto:Support@goat.network) diff --git a/docs/goat-flow-api-reference.md b/docs/goat-flow-api-reference.md new file mode 100644 index 0000000..dc2ecc9 --- /dev/null +++ b/docs/goat-flow-api-reference.md @@ -0,0 +1,679 @@ +# GOAT Flow API Reference + +This reference describes the API and wire behavior used by the TypeScript and +Go server SDKs, browser SDK, QuickPay package, and GOAT Flow MPP adapter. + +For a tutorial, start with the +[Developer Quick Start](./goat-flow-developer-quickstart.md). For package composition +and production boundaries, see the [Integration Guide](./goat-flow-integration.md). + +## 1. Origins and configuration + +```bash +GOATX402_API_URL=https://flow-api.goat.network +GOATX402_API_KEY=your_api_key +GOATX402_API_SECRET=your_api_secret +GOATX402_MERCHANT_ID=your_merchant_id +``` + +| Surface | Testnet3 origin | Mainnet origin | +| --- | --- | --- | +| GOAT Flow | — | `https://flow.goat.network` | +| Merchant Portal | `https://flow-merchant.testnet3.goat.network` | `https://flow-merchant.goat.network` | +| Admin Portal (authorized operators only) | `https://flow-admin.testnet3.goat.network` | `https://flow-admin.goat.network` | +| Flow API / standalone MPP Core | `https://flow-api.testnet3.goat.network` | `https://flow-api.goat.network` | +| Public QuickPay / Hosted Checkout and same-origin API | `https://flow-quickpay.testnet3.goat.network` | `https://flow-quickpay.goat.network` | + +The merchant API URL is configurable in both server SDKs. Test and private +deployments may use different origins. Merchant integrations do not call the +Admin Portal. + +The QuickPay client derives its public session and MPP paths from the trusted +QuickPay link origin. It does not switch to `flow-api` from manifest endpoint +fields. Use `flow-api` for authenticated merchant API calls and explicitly +configured standalone MPP. + +## 2. HMAC authentication + +Protected merchant endpoints require: + +- `X-API-Key` +- `X-Timestamp` +- `X-Nonce` +- `X-Sign` + +The server SDK algorithm is: + +1. Convert top-level body values to strings. +2. Add `api_key`, `timestamp` (Unix seconds), and `nonce`. +3. Remove `sign` and empty-string values. +4. Sort keys lexicographically. +5. Join as `key=value&key=value`. +6. HMAC-SHA256 with the API secret and hex encode. + +Current authenticated SDK `GET` methods have no query parameters and therefore +sign only the authentication fields. + +The signing format is scalar-only. Hosted Checkout arrays/maps are sent as JSON +strings: + +- `acceptable_tokens` +- `line_items_json` +- `public_metadata_json` +- `private_metadata_json` + +Do not expose the API secret or HMAC code in the browser. + +## 3. Merchant endpoint summary + +The current public merchant contract uses DIRECT. Operator callback rows are +compatibility reference for environments explicitly provisioned by the GOAT +operator; their presence in an SDK does not enable them for a merchant. + +| Method | Endpoint | Auth | Success | +| --- | --- | --- | --- | +| Create order | `POST /api/v1/orders` | HMAC | `402` | +| Create Hosted Checkout Session | `POST /api/v1/checkout/sessions` | HMAC | `200` | +| Read order | `GET /api/v1/orders/{order_id}` | HMAC | `200` | +| Read proof | `GET /api/v1/orders/{order_id}/proof` | HMAC | `200` | +| Submit operator callback signature | `POST /api/v1/orders/{order_id}/calldata-signature` | HMAC | `200` | +| Cancel order | `POST /api/v1/orders/{order_id}/cancel` | HMAC | `200` | +| Read merchant | `GET /merchants/{merchant_id}` | Public | `200` | + +HTTP `402` is a success only where the endpoint defines a payment challenge +(create order and the GOAT Flow MPP profile's challenge endpoint). + +Both current server SDKs accept `402` as success only for order creation. +Status, proof, checkout, signature, and cancellation calls fail closed on an +unexpected `402`. + +## 4. Create order + +```http +POST /api/v1/orders +``` + +### Request + +| JSON field | Type | Required | TypeScript | Go | +| --- | --- | --- | --- | --- | +| `dapp_order_id` | string | Yes | `dappOrderId` | `DappOrderID` | +| `chain_id` | integer | Yes | `chainId` | `ChainID` | +| `token_symbol` | string | Yes | `tokenSymbol` | `TokenSymbol` | +| `token_contract` | string | No | `tokenContract` | `TokenContract` | +| `from_address` | string | Yes | `fromAddress` | `FromAddress` | +| `amount_wei` | integer string | Yes | `amountWei` | `AmountWei` | +| `callback_calldata` | hex string | No | `callbackCalldata` | `CallbackCalldata` | + +### Raw response + +```http +HTTP/1.1 402 Payment Required +Content-Type: application/json +PAYMENT-REQUIRED: +``` + +```json +{ + "x402Version": 2, + "resource": { + "url": "https://flow-api.goat.network/api/v1/orders/{order_id}", + "description": "Payment", + "mimeType": "application/json" + }, + "accepts": [ + { + "scheme": "exact", + "network": "eip155:2345", + "amount": "10000000", + "asset": "0xToken", + "payTo": "0xMerchant", + "maxTimeoutSeconds": 600, + "extra": { + "flow": "ERC20_DIRECT", + "tokenSymbol": "USDC" + } + } + ], + "extensions": { + "goatx402": { + "destinationChain": "eip155:2345", + "expiresAt": 1780000000, + "paymentMethod": "transfer", + "receiveType": "DIRECT" + } + }, + "order_id": "order-id", + "flow": "ERC20_DIRECT", + "token_symbol": "USDC" +} +``` + +The documented flow is `ERC20_DIRECT`. The browser transfers the ERC-20 token +to the merchant receiving address returned as `payTo`. + +Operator-provisioned callback fields and signature submission are retained as +compatibility reference in [Appendix A](#appendix-a-operator-provisioned-callback-compatibility). + +### Normalized server `Order` + +`createOrder()` normalizes the first `accepts[]` entry: + +| Server field | Source | +| --- | --- | +| `orderId` | `order_id` | +| `flow` | top-level `flow`, then `accepts[0].extra.flow`, default `ERC20_DIRECT` in TS | +| `tokenSymbol` | top-level `token_symbol`, then `accepts[0].extra.tokenSymbol` | +| `tokenContract` | `accepts[0].asset` | +| `payToAddress` | `accepts[0].payTo` | +| `fromChainId` | parsed from `accepts[0].network` | +| `payToChainId` | parsed from `extensions.goatx402.destinationChain` | +| `amountWei` | `accepts[0].amount` | +| `expiresAt` | `extensions.goatx402.expiresAt` | +| `calldataSignRequest` | top-level `calldata_sign_request` | + +These are normalization outputs, not response-validation guarantees. The +TypeScript client can fall back to request values for token, source chain, and +amount and defaults a missing flow to `ERC20_DIRECT`; the Go client only falls +back to the requested source chain. Either client can therefore expose empty or +zero values when a deployment returns an incomplete challenge. Validate every +field needed by the browser before presenting a transfer. + +The browser `Order` instead requires `chainId` and `fromAddress`. Map explicitly: + +```ts +function toClientOrder(serverOrder: ServerOrder, fromAddress: string): ClientOrder { + return { + orderId: serverOrder.orderId, + flow: serverOrder.flow, + tokenSymbol: serverOrder.tokenSymbol, + tokenContract: serverOrder.tokenContract, + fromAddress, + payToAddress: serverOrder.payToAddress, + chainId: serverOrder.fromChainId, + amountWei: serverOrder.amountWei, + expiresAt: serverOrder.expiresAt, + calldataSignRequest: serverOrder.calldataSignRequest, + } +} +``` + +## 5. Read order status + +```http +GET /api/v1/orders/{order_id} +``` + +Response fields mapped by the SDKs: + +| Wire field | TypeScript field | Go field | +| --- | --- | --- | +| `order_id` | `orderId` | `OrderID` | +| `merchant_id` | `merchantId` | `MerchantID` | +| `dapp_order_id` | `dappOrderId` | `DappOrderID` | +| `chain_id` | `chainId` | `ChainID` | +| `token_contract` | `tokenContract` | `TokenContract` | +| `token_symbol` | `tokenSymbol` | `TokenSymbol` | +| `from_address` | `fromAddress` | `FromAddress` | +| `amount_wei` | `amountWei` | `AmountWei` | +| `status` | `status` | `Status` | +| `tx_hash` | `txHash` | `TxHash` | +| `confirmed_at` | `confirmedAt` | `ConfirmedAt` | + +Current SDK status values: + +- `CHECKOUT_VERIFIED` +- `PAYMENT_CONFIRMED` +- `INVOICED` +- `FAILED` +- `EXPIRED` +- `CANCELLED` + +`PAYMENT_CONFIRMED` and `INVOICED` are successful terminal states for the +Server SDK order waiters. Core can advance a DIRECT order from +`PAYMENT_CONFIRMED` to `INVOICED` inside one watcher transaction, so a poller +may observe only `INVOICED`. + +The polling helpers differ in timing and retry policy. TypeScript reads +immediately, retries network failures, request timeouts, `408`, `429`, and +server errors, and surfaces other deterministic 4xx errors. Each request has a +30-second deadline bounded by the remaining overall timeout. Go waits one +interval before its first read and retries status-read errors until a later +poll, timeout, or context cancellation; its default HTTP client also has a +30-second timeout. + +## 6. Read proof + +```http +GET /api/v1/orders/{order_id}/proof +``` + +```json +{ + "payload": { + "order_id": "order-id", + "tx_hash": "0x...", + "log_index": 0, + "from_addr": "0x...", + "to_addr": "0x...", + "amount_wei": "10000000", + "from_chain_id": 2345, + "status": "INVOICED" + }, + "signature": "0x..." +} +``` + +Retrieve proof after a trusted successful order status. + +The historical field name `signature` is misleading: this value is not a +signature or attestation. It is Keccak256 over these seven payload fields, +concatenated without separators in this exact order: + +```text +order_id || tx_hash || log_index || from_addr || to_addr || amount_wei || from_chain_id +``` + +The digest does not cover `status` or any other field. Anyone can recompute it. +Verify `payload.tx_hash` on-chain when independent proof is required. + +## 7. Cancel order + +```http +POST /api/v1/orders/{order_id}/cancel +Content-Type: application/json + +{} +``` + +The SDK contract permits cancellation while the order is +`CHECKOUT_VERIFIED`. Do not assume another state is cancellable. + +## 8. Read merchant + +```http +GET /merchants/{merchant_id} +``` + +This endpoint is public. + +The TypeScript client currently expects a wire response with `merchant_id`, +optional `name`/`logo`, `receive_type` (`DIRECT`), and `wallets[]`, then maps +wallets to `supportedTokens`. + +The Go `MerchantInfo` type currently declares `supported_tokens` directly. +Because these two clients expect different token-list field names, +verify the response shape of your target deployment before relying on the Go +`SupportedTokens` field. + +## 9. Hosted Checkout Sessions + +```http +POST /api/v1/checkout/sessions +``` + +Authenticated with merchant HMAC. The API key determines the merchant. + +### Request + +Create a DIRECT session: + +```json +{ + "checkout_type": "DIRECT", + "price": "9.99", + "client_reference_id": "cart-123", + "line_items_json": "[{\"name\":\"Mug\",\"amount\":\"9.99\"}]" +} +``` + +The public DIRECT field mapping is: + +| Wire field | TypeScript | Go | Use | +| --- | --- | --- | --- | +| `checkout_type` | `checkoutType` | `CheckoutType` | Use `DIRECT` for public merchant checkout | +| `price` | `price` | `Price` | Decimal product or cart price | +| `success_url` | `successUrl` | `SuccessURL` | Optional allowlisted success redirect | +| `cancel_url` | `cancelUrl` | `CancelURL` | Optional allowlisted cancel redirect | +| `client_reference_id` | `clientReferenceId` | `ClientReferenceID` | Optional correlation/idempotency reference | +| `expires_in` | `expiresIn` | `ExpiresIn` | Optional lifetime in seconds | +| `line_items_json` | `lineItems` | `LineItems` | JSON-stringified display items | +| `public_metadata_json` | `publicMetadata` | `PublicMetadata` | JSON-stringified public metadata | +| `private_metadata_json` | `privateMetadata` | `PrivateMetadata` | JSON-stringified merchant-only metadata | + +Use the server SDK so nested values are serialized consistently with HMAC. + +Operator-provisioned fields, deprecated wrappers, and callback trust boundaries +are isolated in +[Appendix A](#appendix-a-operator-provisioned-callback-compatibility). They are +not part of public merchant onboarding. + +### Response + +```json +{ + "checkout_id": "cs_...", + "checkout_type": "DIRECT", + "url": "https://flow-quickpay.goat.network/checkout?cs=cs_...", + "expires_at": 1780000000 +} +``` + +The exported `CheckoutSession.checkoutType` / Go `CheckoutType` response field +is typed as `string`. Known current values are `DIRECT` and `DELEGATE`; handle an +unknown future value explicitly. + +The browser receives only the opaque checkout ID: + +```ts +const goat = GoatCheckout({ origin: 'https://flow-quickpay.goat.network' }) +goat.open({ checkoutId: session.checkoutId }) +``` + +The public page normally owns: + +- `GET /checkout/v1/sessions/{checkout_id}` +- `GET /checkout/v1/sessions/{checkout_id}/status` +- `POST /checkout/v1/sessions/{checkout_id}/bind` +- `POST /checkout/v1/sessions/{checkout_id}/signature` for an + operator-provisioned callback signature when required + +Treat the checkout ID as a bearer capability. Browser `onSuccess` is not proof +for fulfillment. + +## 10. QuickPay + +QuickPay links are public and same-origin: + +| Surface | Endpoint | +| --- | --- | +| Web/agent entry | `GET /quickpay/{merchant_id}` | +| Agent instructions | `GET /quickpay/{merchant_id}/agent.md` | +| Manifest | `GET /quickpay/{merchant_id}/manifest.json` | +| Discovery | `GET /quickpay/v1/merchants/{merchant_id}` | +| Create x402 session | `POST /quickpay/v1/x402/sessions` | +| Read x402 session | `GET /quickpay/v1/x402/sessions/{session_id}` | + +The `goatflow-quickpay` package accepts only canonical +`/quickpay/{merchant_id}` links over HTTPS (or HTTP loopback for local +development) and derives all called endpoints from that trusted origin. + +### Create public x402 session + +```json +{ + "merchant_id": "merchant_123", + "payer_addr": "0xUser", + "chain_id": 2345, + "token_contract": "0xToken", + "amount_wei": "10000000", + "memo": "invoice-123", + "idempotency_key": "invoice-123:user-456" +} +``` + +Use either: + +- `amount_wei` for custom amount +- `product_key` for a server-priced product + +That JSON is the raw public API shape. The library uses camelCase options: +`amount`, `tokenSymbol`/`tokenContract`, `chainId`, `memo`, and +`idempotencyKey`; it derives `merchant_id` from the trusted QuickPay URL and +`payer_addr` from the payment backend. Product mode uses `productKey`. The +current library does not expose a `clientReferenceId` option, so do not pass raw +snake_case fields to `QuickPayClient.payX402()` or `payProduct()`. + +The package validates the trusted origin, merchant identity, and the individual +token, Product, or route entry selected for a payment. Current boundaries: + +- non-array `tokens` or `routes` values are normalized to empty lists rather + than rejected as a malformed manifest; +- `payX402()` does not require the manifest's `custom_amount` flag before + requesting a raw custom-amount session; and +- Product min/max enforcement remains server-authoritative even when the client + performs local price and token checks. + +Treat manifest validation as client-side discovery and preflight, not a +replacement for server validation. + +CLI commands: + +```bash +npx goatflow-quickpay inspect +npx goatflow-quickpay pay-x402 --amount 10 --token USDC --chain 2345 +npx goatflow-quickpay pay-product --product mug --token USDC --chain 2345 +npx goatflow-quickpay pay-mpp --route GET:api:data +``` + +QuickPay session terminal states are `PAYMENT_CONFIRMED`, `EXPIRED`, `FAILED`, +and `CANCELLED`; this is distinct from the Server SDK order model. Session +polling applies `pollTimeoutMs` as a hard cap, retains known transaction hashes +across transient status errors, can adopt a server-confirmed replacement hash +for a fresh payment, and performs five bounded grace polls when a known +transaction is reported `EXPIRED`. A forced reused session does not replace its +local hash with an unrelated prior server hash. Reconcile by session ID and +transaction hash rather than rebroadcasting after an ambiguous failure. + +## 11. GOAT Flow MPP integration endpoints + +[Machine Payments Protocol (MPP)](https://mpp.dev/overview) is an independent +open protocol. This section documents the current GOAT Flow integration profile, +not the standard MPP HTTP wire format. The current client uses dedicated JSON +challenge and verification endpoints instead of retrying the protected resource +with a standard MPP Credential, and its three-segment signed receipt is a GOAT +Flow extension. No official-SDK interoperability result is currently published. + +### GOAT Flow profile challenge + +```http +POST /mpp/v1/challenge +Content-Type: application/json +``` + +```json +{ + "merchant_id": "merchant_123", + "route_canonical": "GET:api:data", + "request_canonical": "GET:api:data", + "payer_addr": "0xUser" +} +``` + +For this GOAT Flow endpoint, HTTP `402` is success. The SDK accepts `expiry` and +legacy `expiry_unix`: + +```json +{ + "challenge_id": "ch_...", + "expiry": 1780000000, + "amount_wei": "1000000", + "chain_id": 4217, + "token_contract": "0xToken", + "recipient": "0xRecipient", + "mac": "...", + "route_pricing_version": 1 +} +``` + +The returned challenge is authoritative for amount, chain, token contract, +recipient, expiry, MAC, and pricing version. The manifest route is discovery +metadata; never construct or override payment terms from it after the challenge +arrives. + +`request_canonical` defaults to `route_canonical` in `MPPClient.pay()`. The +server contract requires it to equal the route or use the route plus a strict +suffix. + +For standalone `MPPClient`, `coreUrl` is the configured Core/API origin for the +target deployment. In QuickPay `pay-mpp`, the adapter intentionally uses the +trusted QuickPay link origin as `coreUrl`, keeping manifest, challenge, and +verify requests same-origin. These origins are deployment choices and need not +be globally identical. + +### GOAT Flow profile transfer + +`payChallenge()`: + +- checks challenge expiry +- checks signer provider chain +- broadcasts `ERC20.transfer(recipient, amountWei)` +- returns `{ txHash, tx }` immediately without local confirmation waiting; + pass `txHash` to verification and use the ethers `TransactionResponse` in + `tx` for matching transaction-replacement recovery + +### GOAT Flow profile verification + +```http +POST /mpp/v1/verify +Content-Type: application/json +``` + +```json +{ + "challenge_id": "ch_...", + "tx_hash": "0x...", + "payer_addr": "0xUser", + "mac": "..." +} +``` + +| Status | SDK behavior | +| --- | --- | +| `200` | Requires and decodes the GOAT Flow profile's `Payment-Receipt` extension | +| `202` | Retry using `Retry-After` | +| `429` | Retry using `Retry-After` | +| other `4xx` | Terminal `MPPError` | +| `5xx` | Bounded exponential backoff | +| fetch rejection | Bounded retry | + +The default verify budget is 16 attempts; individual waits are capped at 30 +seconds. A post-broadcast failure from `pay()` carries `MPPError.recoverable` so +the caller can resume `verifyChallenge()` without paying again. + +Browser Core responses must allow the DApp origin and expose +`Payment-Receipt`; the protected resource must allow that origin and the +`Payment-Receipt` request header. Otherwise use a server-side buyer client. + +## 12. Browser `PaymentHelper` + +`PaymentHelper.pay(order)`: + +1. Creates an `ERC20Token` for `order.tokenContract`. +2. Reads the connected signer's balance. +3. Returns failure if balance is below `order.amountWei`. +4. Sends `transfer(order.payToAddress, amount)`. +5. Waits for a receipt with status `1`. +6. Returns `{ success: true, txHash }` or `{ success: false, error }`. + +It does not validate: + +- wallet chain +- payer address +- order expiry +- backend order status + +Applications must perform those checks. + +The helper converts every `tx.wait()` exception into a failed `PaymentResult`. +Unlike the lower-level `ERC20Token` helpers, it does not classify +`TRANSACTION_REPLACED`; a successful wallet speed-up can therefore be reported +as a failure. Do not submit another transfer solely from that result. Reconcile +the original/replacement transaction and the backend order first. + +## 13. Error model + +TypeScript API failures throw the runtime-exported `GoatFlowError`, so +`instanceof GoatFlowError` is supported. The error preserves optional `code` +and `status`; authenticated-request failures also preserve the raw +`responseBody`. Fetch/network failures may remain native errors. + +Go returns `*APIError` for non-success HTTP responses, preserving status, +optional code, and raw body; transport and JSON errors use wrapped Go errors. +The TypeScript authenticated helper accepts any `2xx` response, while Go +accepts exactly `200`. Both accept `402` only for order creation. + +Browser order payment errors are returned in `PaymentResult`. MPP methods throw +`MPPError` with stable `code`, optional `httpStatus`, original `cause`, and +optional recovery context. + +`onPhase` is application code and executes outside parts of the MPP error +wrapper. If that callback throws, its arbitrary error can replace the expected +`MPPError`, including during the `failed` phase. Keep it non-throwing or wrap it +locally. `bad_request` is a stable terminal MPP code for rejected HTTP `400` +verification parameters. + +Do not retry a payment broadcast solely because an API call timed out. First +determine whether a transaction was already submitted. + +## 14. Runtime capability and version sources + +Do not hardcode a global chain/token matrix. Availability is +deployment- and merchant-specific. Use the merchant/QuickPay response or +operator configuration. + +Current package manifests: + +| Package | Version | Runtime | +| --- | --- | --- | +| `goatflow-sdk` | `0.2.1` | Node >= 18 outside browser | +| `goatflow-sdk-server` | `0.3.0` | Node >= 18 | +| `goatflow-quickpay` | `0.3.0` | Node >= 18 | +| `goatflow-checkout` | `0.1.0` | Node >= 18 for tooling | +| Go server SDK | module source | Go 1.25 | + +Package manifests, exported types, and release notes are the version source of +truth. + +## Appendix A: Operator-provisioned callback compatibility + +This appendix is not part of public DIRECT onboarding. Use it only when the +target merchant and environment have an explicit operator deployment contract. + +Checkout Session compatibility supports either a decimal `price`, or the +legacy fixed-wei combination of `chain_id`, `fixed_amount_wei`, and +`acceptable_tokens`. For these explicitly provisioned sessions, +`checkout_type` is `DELEGATE`. The additional server-SDK field mapping is: + +| Wire field | TypeScript | Go | Use | +| --- | --- | --- | --- | +| `chain_id` | `chainId` | `ChainID` | Legacy fixed-wei source chain | +| `fixed_amount_wei` | `fixedAmountWei` | `FixedAmountWei` | Legacy fixed-wei amount | +| `acceptable_tokens` | `acceptableTokens` | `AcceptableTokens` | JSON-stringified token addresses | +| `callback_calldata` | `callbackCalldata` | `CallbackCalldata` | Optional create-time callback calldata | + +Only create-time `callback_calldata` in the legacy fixed-wei form is +server-authoritative and guarantees exact callback bytes. The decimal-price +form rejects create-time calldata. A +`public_metadata_json.callback_template` value is only a hosted-UI encoding +hint: bind-time calldata is buyer-controlled, may be omitted or replaced, and +is not revalidated against the template. The callback contract must enforce its +own selector, parameter, and permission policy. + +The TypeScript `createDelegateCheckoutSession()` and Go +`CreateDelegateCheckoutSession()` helpers remain deprecated compatibility +wrappers around `createCheckoutSession()` / `CreateCheckoutSession()`. + +An order-create request may include `callback_calldata`. The authoritative +challenge can then use `ERC20_3009` or `ERC20_APPROVE_XFER`, return an +operator-provisioned recipient, and include +`extensions.goatx402.signatureEndpoint` and `calldata_sign_request`. + +The signature request supplies the complete EIP-712 `domain`, `types`, +`primaryType`, and `message`. `primaryType` is `Eip3009CallbackData` or +`Permit2CallbackData`; sign the returned structure without rebuilding or +selectively copying it. Its `domain.chainId` can differ from the transfer +source chain, and the SDK does not switch either chain for the application. + +After the buyer signs, send the signature to the merchant backend. The backend +submits it with `submitCalldataSignature(orderId, signature)` or +`SubmitCalldataSignature(...)`: + +```http +POST /api/v1/orders/{order_id}/calldata-signature +Content-Type: application/json + +{ "signature": "0x..." } +``` + +Keep merchant HMAC credentials on the backend. diff --git a/docs/goat-flow-checkout.md b/docs/goat-flow-checkout.md new file mode 100644 index 0000000..6332e1c --- /dev/null +++ b/docs/goat-flow-checkout.md @@ -0,0 +1,226 @@ +# GOAT Flow Hosted Checkout + +Hosted Checkout is the recommended browser integration when a merchant does not +want to build wallet connection, buyer-transfer UI, session polling, and +completion UX inside its own application. + +The browser package is `goatflow-checkout`. It opens a GOAT Flow-hosted, top-level +checkout page; the server packages create authenticated Checkout Sessions. + +## Choose the right path + +| Use case | Recommended path | Merchant backend required | +| --- | --- | --- | +| Fixed DIRECT catalog item | QuickPay product + `open({ merchant, productKey })` | No | +| Dynamic DIRECT cart/amount | Unified Checkout Session + `open({ checkoutId })` | Yes | +| Donation or buyer-entered amount | `openCustom({ merchant, amount })` | No, but server-side reconciliation is required | +| Fully custom wallet/order UI | `goatflow-sdk` + `goatflow-sdk-server` | Yes | + +Do not use `openCustom` for automatic fulfillment. Its amount originates in the +browser and is not a merchant-authoritative price. + +## Install + +```bash +npm install goatflow-checkout + +# Backend, when creating Checkout Sessions: +npm install goatflow-sdk-server +``` + +The checkout package is framework-free and includes +`dist/checkout.global.js` for self-hosted script-tag delivery through the global +`GoatCheckout` function. The Mainnet QuickPay origin does not currently expose a +public `/sdk/checkout.js`; use the npm import unless your deployment contract +provides a script URL. + +## Fixed DIRECT product, no merchant backend + +The merchant first configures a QuickPay product. The merchant page passes only the +merchant ID and product key: + +```ts +import { GoatCheckout } from 'goatflow-checkout' + +const goat = GoatCheckout({ origin: 'https://flow-quickpay.goat.network' }) + +payButton.addEventListener('click', () => { + goat.open({ + merchant: 'merchant_123', + productKey: 'mug', + display: 'popup', + clientReferenceId: 'cart_9f31', + onSuccess: (result) => { + // Update the UI only. Do not fulfill from this callback. + console.log(result.status, result.tx_hash) + }, + onCancel: () => {}, + onError: (reason) => console.error(reason), + }) +}) +``` + +The hosted page resolves the product's server-side decimal price and the buyer +chooses an eligible chain/token. The browser never supplies the product amount. + +## Create a unified Checkout Session + +`POST /api/v1/checkout/sessions` is HMAC-authenticated. The server SDK signs it +with the merchant API secret and maps the response to: + +```ts +type CheckoutSession = { + checkoutId: string + checkoutType: string // current values: 'DIRECT' | 'DELEGATE' + url: string + expiresAt: number +} +``` + +The merchant is derived from the authenticated API key, not accepted from the +request body. + +### TypeScript: dynamic DIRECT checkout + +```ts +import { GoatFlowClient } from 'goatflow-sdk-server' + +const client = new GoatFlowClient({ + baseUrl: process.env.GOATX402_API_URL!, + apiKey: process.env.GOATX402_API_KEY!, + apiSecret: process.env.GOATX402_API_SECRET!, +}) + +const session = await client.createCheckoutSession({ + checkoutType: 'DIRECT', + price: '19.95', + clientReferenceId: 'cart_9f31', + lineItems: [ + { name: 'Coffee mug', quantity: 1, amount: '19.95' }, + ], + publicMetadata: { campaign: 'summer' }, + privateMetadata: { internal_customer_id: 'cus_42' }, + successUrl: 'https://merchant.example/pay/success', + cancelUrl: 'https://merchant.example/pay/cancel', + expiresIn: 1800, +}) +``` + +DIRECT Checkout Sessions require the authenticated merchant to be DIRECT and to +have QuickPay enabled. + +### Go + +```go +session, err := client.CreateCheckoutSession(ctx, goatflow.CreateCheckoutSessionParams{ + CheckoutType: "DIRECT", + Price: "19.95", + ClientReferenceID: "cart_9f31", + ExpiresIn: 1800, +}) +if err != nil { + return err +} + +// Send session.CheckoutID to the browser, or redirect to session.URL. +``` + +### Operator-provisioned compatibility reference + +The API and SDK retain a compatibility session value for explicitly +operator-provisioned environments. It is not part of public merchant onboarding, +and new integrations use `createCheckoutSession()` with `DIRECT`. Do not infer +availability from SDK types. The complete legacy field mapping, deprecated +wrappers, and callback trust boundary are isolated in the +[API Reference appendix](./goat-flow-api-reference.md#appendix-a-operator-provisioned-callback-compatibility). + +## Open the session in the browser + +Return the opaque `checkoutId` to the browser; never return the API secret. + +```ts +import { GoatCheckout } from 'goatflow-checkout' + +const goat = GoatCheckout({ origin: 'https://flow-quickpay.goat.network' }) + +let checkoutHandle: { close(): void } | undefined +checkoutHandle = goat.open({ + checkoutId, + display: 'tab', // 'popup', 'tab', or 'redirect' + onSuccess: (result) => { + // UX only; await webhook/order verification before fulfillment. + }, + onCancel: () => {}, + onError: (reason) => { + if (reason === 'opener_unavailable') { + // The popup may still be running; close it before redirecting this page. + checkoutHandle?.close() + goat.redirectToCheckout({ checkoutId }) + } + }, +}) +``` + +If session creation requires an asynchronous request after the buyer clicks, either +redirect the current page or synchronously open a blank tab and navigate it after +the response. Calling `window.open` only after an `await` is commonly blocked by +browsers. + +## Lifecycle + +1. The merchant backend creates a server-authoritative Checkout Session. +2. The buyer opens the opaque checkout URL and connects a wallet. +3. The hosted page reads safe session terms from Core. +4. The buyer chooses an eligible token; bind creates the real order. +5. The buyer wallet sends the ERC-20 transfer directly to the merchant receiving + address. +6. GOAT Flow records the resulting session state and may emit the authenticated + completion webhook configured by that deployment. + +Known Checkout Session states include `OPEN`, `BOUND`, `SIGNED` +(operator-provisioned compatibility sessions), `COMPLETED`, `EXPIRED`, and +`CANCELLED`. The linked order has a separate status model. Server SDK order +waiters treat `PAYMENT_CONFIRMED` and `INVOICED` as successful terminal states; +Core can advance a DIRECT order to `INVOICED` before a poller observes +`PAYMENT_CONFIRMED`. + +## API surface + +| Endpoint | Auth | Intended caller | +| --- | --- | --- | +| `POST /api/v1/checkout/sessions` | Merchant HMAC | Server SDK | +| `GET /checkout/v1/sessions/{checkout_id}` | Public opaque handle | Hosted checkout | +| `GET /checkout/v1/sessions/{checkout_id}/status` | Public opaque handle | Hosted checkout | +| `POST /checkout/v1/sessions/{checkout_id}/bind` | Public, rate-limited | Hosted checkout | +| `POST /checkout/v1/sessions/{checkout_id}/signature` | Public, rate-limited | Operator-provisioned hosted compatibility flow | + +Merchant applications normally call only the authenticated create endpoint. The +GOAT Flow-hosted page owns the public read/bind/signature sequence. + +Nested create fields (`acceptableTokens`, `lineItems`, `publicMetadata`, and +`privateMetadata`) are JSON-stringified by the server SDK before HMAC signing +because the current signing format accepts scalar fields. +Do not reproduce that encoding manually when an SDK is available. + +## Fulfillment and security + +- `onSuccess` and `postMessage` are UX signals, not payment proof. +- Fulfill from a trusted backend status check or an authenticated webhook whose + event name, payload, signature, and retry behavior are confirmed for the + deployment. The public SDKs do not define one canonical webhook event name. +- The raw `cs_…` handle is high entropy. Treat it as a bearer capability, avoid + logging it, and do not place secrets in public metadata or line items. +- `privateMetadata` is excluded from the public session view. +- Success/cancel URLs must pass the merchant redirect allowlist. +- Popup/tab messages are accepted only from the exact configured origin, exact + opened window, and matching random channel nonce. +- Hosted checkout must remain a top-level page; do not embed it in an iframe. +- The merchant API key and secret stay exclusively on the backend. + +## Related modules + +- [Browser SDK](../goatx402-checkout/README.md) +- [Server SDK (TypeScript)](../goatx402-sdk-server-ts/src/client.ts) +- [Server SDK (Go)](../goatx402-sdk-server-go/client.go) +- [Demo](../goatx402-demo/README.md) +- [QuickPay payer/agent library](../goatx402-quickpay/README.md) diff --git a/docs/goat-flow-dapp-integration/SKILL.md b/docs/goat-flow-dapp-integration/SKILL.md new file mode 100644 index 0000000..7b44122 --- /dev/null +++ b/docs/goat-flow-dapp-integration/SKILL.md @@ -0,0 +1,517 @@ +--- +name: goat-flow-dapp-integration +description: Implement or review GOAT Flow commerce integrations in an existing Web DApp using authenticated merchant APIs, Hosted Checkout, QuickPay, or MPP. Use for fixed products, server-priced purchases, custom transfer interfaces, buyer or agent purchases, paid API routes, and audits of pricing, fulfillment, retry, receipt, and environment boundaries. +--- + +# GOAT Flow DApp Integration + +## Goal + +Add the smallest GOAT Flow integration that satisfies the application's real +payment and fulfillment requirements. Preserve the application's existing +architecture and business behavior outside the payment gate. + +Target the public DIRECT merchant path by default. Do not add callback signing +or other optional receiving modes unless the user supplies a deployment +contract that explicitly requires them. + +## Establish the Source of Truth + +Before editing, inspect the package source and exports in the version actually +used by the application. Use this evidence order: + +1. Package source, types, tests, and exports for client behavior. +2. Deployed API contract for URLs, status semantics, webhooks, and operator + configuration. +3. Merchant portal observations for environment-specific settings. + +Use the repository documentation for context, but resolve conflicts in favor +of the implementation and deployment contract. Do not infer a production +capability from a testnet observation. + +## Keep Environments Isolated + +Use one environment consistently across API credentials, merchant ID, checkout +origin, QuickPay link, chain, token contract, RPC, and wallet funds. + +| Surface | Testnet3 | Mainnet | +| --- | --- | --- | +| Flow API / standalone MPP Core | `https://flow-api.testnet3.goat.network` | `https://flow-api.goat.network` | +| Hosted Checkout / QuickPay and same-origin public API | `https://flow-quickpay.testnet3.goat.network` | `https://flow-quickpay.goat.network` | + +Treat these as deployment configuration, not library defaults. Verify them +against the active deployment before shipping. + +## Guardrails + +- Keep `GOATX402_API_KEY` and `GOATX402_API_SECRET` in a server runtime only. +- Never put a private key in browser code, source control, argv, logs, or an + agent transcript. +- Treat browser callbacks and wallet receipts as UX signals, not merchant + fulfillment proof. +- Obtain current amount, token, recipient, chain, and expiry from trusted + server terms. Do not hardcode a global payment matrix. +- Use a Product or server-created Checkout Session for automatically fulfilled + purchases. Treat buyer-entered custom amounts as untrusted. +- Do not call a payment method again after a transaction may have broadcast. + Resume status polling or verification with the existing handle. +- Do not invent fees, webhook events, approval steps, or account policy. +- Do not fund wallets or perform mainnet payments without explicit permission. +- Keep the user's source tree and unrelated changes intact. +- Treat Machine Payments Protocol (MPP) as an independent open protocol, not a + GOAT Flow protocol. Call the current MPP endpoints, client, middleware, and + signed receipt a GOAT Flow integration profile or adapter. Do not present its + JSON challenge/verify endpoints or three-segment receipt as the standard MPP + HTTP wire format, and do not claim official-SDK interoperability without a + conformance test. +- In explanatory copy, describe GOAT Flow as commerce and verification software + that observes an on-chain transfer, verifies it, confirms finality, updates + protocol state, and issues a receipt. +- Do not describe GOAT Flow as processing, handling, facilitating, accepting, + collecting, routing, or settling funds, or as a payment processor, gateway, + platform, layer, rail, solution, or infrastructure. +- For DIRECT, state that the buyer wallet sends tokens directly to the merchant + receiving address. Keep service-fee credits and Fee Top-up separate from + buyer-to-merchant funds. + +## Gather Context + +Collect or infer: + +- application path, framework, package manager, and existing backend +- existing cart, order, request, or business reference +- current pricing authority and fulfillment action +- selected GOAT Flow environment and merchant ID +- backend API URL and credentials when merchant APIs are required +- hosted checkout origin when Checkout or QuickPay is required +- wallet provider, supported chain, token contract, RPC, and test funds when a + live payment is required + +If an authenticated path is selected and backend credentials are unavailable, +implement the configuration boundary but stop before a live merchant API call. + +## Inspect the Application + +1. Locate the current purchase or protected action. +2. Locate the server-side pricing and fulfillment code. +3. Reuse existing order IDs and idempotency references. +4. Confirm whether a trusted server runtime exists. +5. Preserve the current framework, package manager, routing, and state model. +6. Identify every point where duplicate clicks, retries, reloads, or wallet + replacement transactions can occur. + +## Choose One Primary Path + +| Requirement | Primary path | Pricing authority | Fulfillment authority | +| --- | --- | --- | --- | +| Fixed catalog item, hosted wallet UI | Hosted Checkout Product | Merchant Product | Trusted session/order status | +| Dynamic cart or invoice, hosted wallet UI | Hosted Checkout Session | Merchant backend | Trusted session/order status | +| Fully custom wallet UI | Authenticated Order API | Merchant backend | Authenticated order status/proof | +| Public buyer/agent automation | QuickPay library or CLI | Manifest preflight plus server session | Terminal server session | +| Paid API route | Current GOAT Flow MPP profile | Core profile challenge | Verified profile `Payment-Receipt` middleware | +| Tip or donation | Hosted custom amount or QuickPay custom amount | Buyer input, reconciled server-side | Observed paid amount | + +Do not combine paths unless the application genuinely needs separate payment +experiences. + +## Hosted Checkout + +Install `goatflow-checkout`. Configure a bare trusted origin: HTTPS in deployed +environments, or HTTP only for loopback development. Reject origins containing +credentials, a path, query, or fragment. + +```ts +import { GoatCheckout } from 'goatflow-checkout' + +const checkout = GoatCheckout({ origin: checkoutOrigin }) +``` + +Call `open()` synchronously from a user gesture. It accepts exactly one +fulfillable price source: + +```ts +checkout.open({ + merchant: merchantId, + productKey, + clientReferenceId, +}) +``` + +or: + +```ts +checkout.open({ + checkoutId, + clientReferenceId, +}) +``` + +Do not combine `checkoutId` with `merchant` or `productKey`. Product opens use +`/quickpay/checkout`; server-created session opens use `/checkout?cs=...`. The +fulfillable URL must not contain an authoritative amount. + +For a dynamic purchase, create the session on the backend: + +```ts +const session = await client.createCheckoutSession({ + checkoutType: 'DIRECT', + price: cartTotal, + clientReferenceId: cartId, + lineItems, + privateMetadata: { cartId }, +}) +``` + +Return only the opaque `session.checkoutId` and non-sensitive display data to +the browser. The current return type is `CheckoutSession`, containing +`checkoutId`, `checkoutType`, `url`, and `expiresAt`. + +Use `display: 'popup'` by default, `tab` for a new tab, and `redirect` when the +opener channel is unavailable. `redirectToCheckout()` and redirect display do +not provide an opener callback after navigation. Treat `onSuccess` as a UX +event only; query trusted server state before fulfillment. + +Use `openCustom({ merchant, amount })` only for tips or donations. Reconcile the +actual paid amount on the server before granting anything of value. + +## Authenticated Order API + +Install `goatflow-sdk-server` on the backend and `goatflow-sdk` in the browser. +Create `GoatFlowClient` only in server code. + +```ts +const order = await client.createOrder({ + dappOrderId, + chainId, + tokenSymbol, + tokenContract, + fromAddress, + amountWei, +}) +``` + +HTTP 402 is the expected successful response only for order creation. The +current server SDKs fail closed on an unexpected 402 from Checkout, status, +proof, signature, or cancellation calls. + +The server and browser `Order` types differ. Map them explicitly; do not pass a +server order to `PaymentHelper` without adding the payer address and converting +`fromChainId` to `chainId`. + +```ts +import type { Order as ServerOrder } from 'goatflow-sdk-server' +import type { Order as BrowserOrder } from 'goatflow-sdk' + +function toBrowserOrder( + order: ServerOrder, + fromAddress: string, +): BrowserOrder { + return { + orderId: order.orderId, + flow: order.flow, + tokenSymbol: order.tokenSymbol, + tokenContract: order.tokenContract, + fromAddress, + payToAddress: order.payToAddress, + chainId: order.fromChainId, + amountWei: order.amountWei, + expiresAt: order.expiresAt, + calldataSignRequest: order.calldataSignRequest, + } +} +``` + +Before payment, verify in application code that the connected wallet matches +`fromAddress`, the wallet network matches `chainId`, and the order has not +expired. `PaymentHelper.pay()` does not perform those checks. + +If a supposedly DIRECT order contains `calldataSignRequest`, stop and require +the explicit operator-provisioned callback contract. That path must sign the +exact EIP-712 request on `domain.chainId`, submit it through the merchant +backend, and return the wallet to the transfer source chain before paying. + +```ts +import { PaymentHelper } from 'goatflow-sdk' + +const result = await new PaymentHelper(signer).pay(order) +if (!result.success || !result.txHash) { + throw new Error(result.error ?? 'Payment failed') +} +``` + +`PaymentHelper.pay()` catches transfer failures and returns +`{ success: false, error }`; it does not throw `PaymentError`. It waits for the +local transaction receipt, but that receipt is not trusted fulfillment proof. +Poll the backend with `getOrderStatus()` or `waitForConfirmation()`. +It also treats any `tx.wait()` exception as failure without classifying +`TRANSACTION_REPLACED`; reconcile wallet speed-ups and backend status before +another transfer. + +Treat `PAYMENT_CONFIRMED` and `INVOICED` as successful Server SDK order +terminals; `FAILED`, `EXPIRED`, and `CANCELLED` are closed outcomes. Core can +advance a DIRECT order to `INVOICED` before a poller observes +`PAYMENT_CONFIRMED`. Cancel only a stale `CHECKOUT_VERIFIED` order. + +The TypeScript waiter performs an immediate read, retries transient failures, +and applies a 30-second per-request deadline bounded by the remaining overall +timeout. The Go waiter starts after one interval and currently retries every +status-read error. Preserve that policy difference when designing portable +polling. + +TypeScript API failures use the runtime-exported `GoatFlowError`; preserve its +`status`, `code`, and authenticated-request `responseBody`, and use +`instanceof GoatFlowError` when needed. + +Treat `getOrderProof()` as a server-issued payment record, not a signed +attestation. Its historical `signature` field is Keccak256 over `order_id`, +`tx_hash`, `log_index`, `from_addr`, `to_addr`, `amount_wei`, and +`from_chain_id`, concatenated without separators in that order; it does not +cover `status`. Verify the transaction hash on-chain for independent proof. + +## QuickPay Library and CLI + +Install `goatflow-quickpay` for public payer or agent automation. Prefer Hosted +Checkout for an interactive browser DApp unless the application already owns a +safe wallet backend. + +Accept only canonical merchant links: + +```text +https:///quickpay/ +https:///quickpay//agent.md +https:///quickpay//manifest.json +``` + +Derive the merchant ID from the trusted URL path and keep manifest, session, +challenge, and verification requests on the same origin. Treat the manifest as +discovery and preflight data. Treat the returned x402 session or MPP challenge +as the current payment instruction. + +Validate generated `agent.md` commands against the installed package metadata. +The current package and CLI binary are both `goatflow-quickpay`. + +```ts +import { QuickPayClient } from 'goatflow-quickpay' + +const quickpay = new QuickPayClient(sharedMerchantLink) +const manifest = await quickpay.loadManifest() +const result = await quickpay.payProduct({ + productKey, + chainId, + tokenContract, + backend, + idempotencyKey: businessReference, +}) +``` + +Inject a `PaymentBackend`; the library does not choose a wallet implicitly. +`EthersPaymentBackend` accepts a private key and RPC resolver, so use it only in +a controlled CLI/server environment with secrets supplied out of band. Never +bundle it with a payer key in browser code. + +For `payProduct()`, use camelCase library fields and let the server price the +product. For `payX402()`, pass a decimal `amount`, `chainId`, a token symbol or +contract, a backend, and an explicit `idempotencyKey` when the payment intent +must survive retries. Raw HTTP bodies use snake_case and atomic `amount_wei`; +do not mix the two interfaces. + +Check `result.ok`, `result.status`, `result.session_id`, and `result.tx_hash`. +The terminal session states are `PAYMENT_CONFIRMED`, `EXPIRED`, `FAILED`, and +`CANCELLED`; this session model is separate from Server SDK order states and +does not include `INVOICED`. A reused session is polled rather than +rebroadcast. Pass literal `force: true` only when it is proven that no transfer +was broadcast. + +Use an explicit idempotency key for durable Product recovery when product, +token, or rail configuration may change. `pollTimeoutMs` is a hard status-poll +cap; sleeps and status requests are bounded by the remaining time. Preserve a +known transaction hash across polling failures. A fresh payment may adopt a +server-confirmed replacement hash, but a forced reused session must not replace +its local hash with an unrelated prior value. When a known transaction is +reported `EXPIRED`, allow the client's five bounded grace polls. If QuickPay +MPP reports a transaction hash without a signed receipt header, do not run the +payment again; reconcile the transaction and resume verification with the +preserved challenge context. + +## Standalone GOAT Flow MPP Adapter + +[MPP](https://mpp.dev/overview) is an independent open protocol. The current +`MPPClient` is GOAT Flow's current adapter and is not generic MPP client code. +Install `goatflow-sdk` and construct it with a trusted Core/API origin without a +trailing slash. This trust model differs from QuickPay MPP, which derives Core +from the shared link origin. + +```ts +import { MPPClient, MPPError } from 'goatflow-sdk' + +const mpp = new MPPClient({ + coreUrl, + signer, +}) + +async function payForRoute() { + try { + return await mpp.pay({ + merchantId, + routeCanonical, + requestCanonical, + onPhase, + }) + } catch (error) { + if (error instanceof MPPError && error.recoverable) { + return mpp.verifyChallenge(error.recoverable) + } + throw error + } +} +``` + +Omit `requestCanonical` to use `routeCanonical`. When provided, it must equal +the route or start with `routeCanonical + ':'`. + +Treat the challenge as authoritative for amount, chain, token contract, +recipient, expiry, MAC, and pricing version. `pay()` checks expiry and chain, +broadcasts without waiting locally, and uses Core verification as the finality +authority. Verification retries network failures, 5xx, 202, and 429; it treats +other 4xx responses as terminal. The default verification budget is 16 +attempts; changing it may also require a matching Core rate-limit change. + +Require all successful MPP results to contain `receiptHeader`, `receiptBody`, +`txHash`, and `challengeId`. Attach the exact signed value to the protected +request: + +```ts +await fetch(resourceUrl, { + headers: { 'Payment-Receipt': result.receiptHeader }, +}) +``` + +This GOAT Flow receipt extension has three dot-separated segments: +`base64url(receipt JSON).base64url(signature).algorithm`. For browser use, the +Core deployment must allow the DApp origin, `POST`, and `Content-Type`, and must +expose `Payment-Receipt` on verify responses. The protected resource must also +allow the DApp origin and the `Payment-Receipt` request header. Otherwise keep +the buyer flow server-side. + +Branch on `MPPError.code`, not message text. Stable current codes include +`network_error`, `parse_error`, `route_not_found`, `invalid_request`, +`chain_mismatch`, `user_rejected`, `payment_failed`, `challenge_expired`, +`challenge_already_consumed`, `challenge_tx_hash_mismatch`, `payer_mismatch`, +`bad_request`, `verify_timeout`, `service_unavailable`, `receipt_missing`, and +`receipt_malformed`. + +Keep `onPhase` non-throwing or catch its errors locally. User callbacks can run +outside parts of the SDK error wrapper and replace the expected `MPPError`. + +After broadcast, call `verifyChallenge(error.recoverable)` and never call +`pay()` again. Keep the replacement-aware recovery handle; the SDK follows +compatible wallet fee-bump transaction hashes while polling. + +## Protect GOAT Flow MPP-Profile Routes + +These middlewares verify the GOAT Flow receipt extension; they do not parse a +generic MPP Credential or Receipt. The TypeScript and Go middleware are +source-only in this repository. Do not +claim registry installation unless the active release process has published +them. Build `goatx402-mpp-middleware-ts` locally, install that directory, and +import framework adapters from their subpaths, not the package root. + +```ts +import { expressMiddleware } from '@goatnetwork/mpp-middleware/express' + +app.get( + '/paid-resource', + expressMiddleware({ + merchantId, + routeCanonical: 'GET:paid-resource', + algorithm: 'ed25519', + ed25519Public, + store: receiptStore, + }), + handler, +) +``` + +For Fastify, import `fastifyPreHandler` or `fastifyPlugin` from +`@goatnetwork/mpp-middleware/fastify`. For Go, bind +`github.com/goatnetwork/goatflow-mpp-middleware-go` to the local source +directory with a `replace` directive before importing it. + +Configure `merchantId`, `routeCanonical`, and either `ed25519Public` for +`ed25519` or `hmacSecret` for `hmac-sha256`. Read the verified receipt from +`req.mppReceipt`. Use a shared atomic receipt store in multi-replica production; +the in-memory store is only suitable for local or single-process use. + +The middleware verifies a signed receipt, audience, route binding, expiry, and +optional single use. It does not issue a challenge or execute payment. Expect +401 for missing, malformed, invalid, wrong-audience, or consumed receipts; 402 +for route mismatch or receipt expiry; and 503 for receipt-store unavailability. +Unexpected verifier exceptions may produce 500 and must fail closed. + +## Fulfillment and State + +Represent only states the selected integration can prove. Keep protected work +blocked until one of these trusted authorities succeeds: + +- authenticated order or session status approved by the deployment contract +- an authenticated webhook event documented by the active deployment +- MPP middleware verification for the current protected request + +Do not hardcode webhook event names. The public SDKs do not define one universal +webhook event contract. + +Separate wallet rejection, wrong network, insufficient token balance, +insufficient native gas, expiry, backend failure, and post-broadcast recovery. +Persist order IDs, checkout IDs, QuickPay session IDs, idempotency keys, +transaction hashes, MPP challenge recovery data, and the application's business +reference as appropriate. Never persist secrets in client storage. + +## Validate + +Run the application's existing checks plus focused tests for the selected path. +Verify all of the following that apply: + +- backend credentials and private keys are absent from browser bundles and logs +- package imports match the installed version and build successfully +- mainnet and testnet configuration cannot be mixed +- fixed Product and Checkout Session URLs contain no amount +- custom amounts cannot unlock fixed-price fulfillment +- server orders are explicitly mapped to browser orders +- `PaymentHelper.pay()` failure results are handled +- unexpected authenticated HTTP 402 results fail closed +- Server SDK order handling treats `INVOICED` as a successful terminal while + QuickPay session handling uses its separate terminal set +- browser callbacks cannot fulfill an order by themselves +- repeated clicks, retries, reloads, and reused sessions cannot double-pay +- QuickPay uses same-origin endpoints and explicit recovery identifiers +- MPP success includes both transaction hash and signed receipt header +- post-broadcast MPP failures resume verification rather than payment +- middleware rejects wrong audience, wrong route, expired, malformed, replayed, + and unverifiable receipts +- a shared receipt store is used when production has multiple replicas +- the original protected action resumes only after trusted confirmation + +When live verification is unavailable, report the exact missing environment, +merchant configuration, wallet, chain, token, RPC, balance, or deployment +contract. Do not describe static review as a successful payment test. + +## Deliver + +Return: + +- selected path and why it fits +- modified files and payment entry points +- environment variables and their server/browser ownership +- pricing, trust, retry, recovery, and fulfillment boundaries +- startup, build, test, and live-verification commands actually run +- verified outcomes and remaining deployment confirmations + +## Repository References + +Consult these files when working in this repository: + +- [Developer Quick Start](../goat-flow-developer-quickstart.md) +- [API Reference](../goat-flow-api-reference.md) +- [Hosted Checkout](../goat-flow-checkout.md) +- [Integration Guide](../goat-flow-integration.md) +- [GOAT Flow MPP Integration](../mpp.md) diff --git a/docs/goat-flow-dapp-integration/openai.yaml b/docs/goat-flow-dapp-integration/openai.yaml new file mode 100644 index 0000000..89334ea --- /dev/null +++ b/docs/goat-flow-dapp-integration/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "GOAT Flow DApp Integration" + short_description: "Integrate GOAT Flow into an existing DApp" + default_prompt: "Use the GOAT Flow DApp integration skill to add GOAT Flow payments to an existing DApp." diff --git a/docs/goat-flow-developer-quickstart.md b/docs/goat-flow-developer-quickstart.md new file mode 100644 index 0000000..f195304 --- /dev/null +++ b/docs/goat-flow-developer-quickstart.md @@ -0,0 +1,399 @@ +# GOAT Flow Developer Quick Start + +Use this guide to complete a first DIRECT payment with the GOAT Flow SDKs. + +For deeper details, see the [Integration Guide](./goat-flow-integration.md) and +[API Reference](./goat-flow-api-reference.md). + +## Choose the shortest path + +| Goal | Start here | +| --- | --- | +| GOAT Flow-hosted wallet/transfer UI | Hosted Checkout | +| Custom merchant wallet/transfer UI | Server SDK + `PaymentHelper` | +| Agent or CLI payment | QuickPay | +| Paid API route | GOAT Flow MPP profile | + +All merchant API credentials stay on your backend. + +## Prerequisites + +- An approved Merchant Account +- Receiving chain/token configuration +- Sufficient merchant fee balance +- API key and secret for authenticated programmatic flows +- A payer wallet with the selected ERC-20 token and native gas + +QuickPay product links and Hosted Checkout do not expose merchant credentials in +the browser. Dynamic Hosted Checkout terms are still created by the merchant +backend. + +The runnable examples below use GOAT Testnet3. Move to Mainnet only after the +same merchant, chain, token, receiving address, and service-fee configuration +have been verified there; switch every origin and chain ID together. + +## Install + +```bash +# Authenticated backend API +npm install goatflow-sdk-server + +# Custom browser wallet flow +npm install goatflow-sdk ethers + +# Hosted payment window +npm install goatflow-checkout + +# Agent / CLI payer +npm install goatflow-quickpay +``` + +The TypeScript packages declare Node.js >= 18 where Node is used. The Go SDK +module currently declares Go 1.25. + +## Path A: Hosted Checkout + +### Fixed product + +```ts +import { GoatCheckout } from 'goatflow-checkout' + +const goat = GoatCheckout({ + origin: 'https://flow-quickpay.testnet3.goat.network', +}) + +payButton.addEventListener('click', () => { + goat.open({ + merchant: 'merchant_123', + productKey: 'mug', + onSuccess(result) { + // UX signal only. Fulfill after a trusted webhook/status verification. + console.log(result) + }, + }) +}) +``` + +### Dynamic price + +Create the session on your backend: + +```ts +const session = await client.createCheckoutSession({ + checkoutType: 'DIRECT', + price: '19.95', + clientReferenceId: 'cart_123', + lineItems: [{ name: 'Mug', amount: '19.95', quantity: 1 }], +}) +``` + +Open the opaque session in the browser: + +```ts +goat.open({ checkoutId: session.checkoutId }) +``` + +This quick start covers the public DIRECT path. Operator-provisioned session +variants are documented as compatibility reference in the +[Hosted Checkout guide](./goat-flow-checkout.md) and +[API Reference](./goat-flow-api-reference.md); do not select them unless the +target merchant and environment have an explicit deployment contract. + +## Path B: Custom order and wallet UI + +### 1. Configure the backend + +```bash +GOATX402_API_URL=https://flow-api.testnet3.goat.network +GOATX402_API_KEY=your_api_key +GOATX402_API_SECRET=your_api_secret +``` + +Never ship these values in a browser bundle. + +### 2. Create and map the order + +The server and browser SDKs intentionally expose different `Order` shapes. +Map the object explicitly before returning it to the frontend. + +```ts +import { + GoatFlowClient, + type Order as ServerOrder, +} from 'goatflow-sdk-server' +import type { Order as ClientOrder } from 'goatflow-sdk' + +const client = new GoatFlowClient({ + baseUrl: process.env.GOATX402_API_URL!, + apiKey: process.env.GOATX402_API_KEY!, + apiSecret: process.env.GOATX402_API_SECRET!, +}) + +function toClientOrder(order: ServerOrder, fromAddress: string): ClientOrder { + return { + orderId: order.orderId, + flow: order.flow, + tokenSymbol: order.tokenSymbol, + tokenContract: order.tokenContract, + fromAddress, + payToAddress: order.payToAddress, + chainId: order.fromChainId, + amountWei: order.amountWei, + expiresAt: order.expiresAt, + calldataSignRequest: order.calldataSignRequest, + } +} + +export async function createOrder( + dappOrderId: string, + fromAddress: string, +): Promise { + const order = await client.createOrder({ + dappOrderId, + chainId: 48816, + tokenSymbol: 'USDC', + fromAddress, + amountWei: '10000000', + }) + + return toClientOrder(order, fromAddress) +} +``` + +Generate `dappOrderId` once for the cart or payment intent, persist it before +the request, and reuse the same value for a retry. Do not derive it from the +current timestamp inside `createOrder()`. + +Under the hood, successful order creation returns HTTP `402 Payment Required`. +The server SDK treats it as success and normalizes the x402 body. + +The server SDK accepts `402` as success only for order creation. An unexpected +`402` from status, proof, checkout, signature, or cancellation fails closed. + +### 3. Validate and pay in the browser + +```ts +import { PaymentHelper, type Order } from 'goatflow-sdk' +import { ethers } from 'ethers' + +export async function payOrder(order: Order): Promise { + const provider = new ethers.BrowserProvider(window.ethereum) + const signer = await provider.getSigner() + + const network = await provider.getNetwork() + if (Number(network.chainId) !== order.chainId) { + throw new Error(`Switch wallet to chain ${order.chainId}`) + } + + const payer = await signer.getAddress() + if (payer.toLowerCase() !== order.fromAddress.toLowerCase()) { + throw new Error('Connected wallet does not match the order payer') + } + + if (Math.floor(Date.now() / 1000) >= order.expiresAt) { + throw new Error('Order expired') + } + + const payment = new PaymentHelper(signer) + + if (order.calldataSignRequest) { + throw new Error( + 'This DIRECT quick start does not handle operator-provisioned callback orders', + ) + } + + const result = await payment.pay(order) + if (!result.success || !result.txHash) { + throw new Error(result.error ?? 'Payment failed') + } + return result.txHash +} +``` + +`PaymentHelper.pay()` does not classify `TRANSACTION_REPLACED`. If a wallet +speed-up is reported as failed, reconcile the original/replacement transaction +and backend order before considering another transfer. + +Callback signing is an operator-provisioned compatibility path, not part of +this DIRECT quick start. If the target deployment explicitly requires it, use +the complete field, callback-chain, and signature-submission contract in the +[Integration Guide](./goat-flow-integration.md#51-typescript-client). + +`PaymentHelper.pay()` checks token balance, submits the ERC-20 transfer, and +waits for a successful receipt. It returns failures in `PaymentResult`; it does +not check chain, payer, or expiration. + +### 4. Confirm on the backend + +Do not fulfill only because the wallet transaction returned successfully. + +```ts +const orderStatus = await client.getOrderStatus(orderId) + +const fulfillable = + orderStatus.status === 'PAYMENT_CONFIRMED' || + orderStatus.status === 'INVOICED' + +if (fulfillable) { + const proof = await client.getOrderProof(orderId) + await fulfillOnce(orderId, proof) +} +``` + +Current SDK status values are: + +- `CHECKOUT_VERIFIED` +- `PAYMENT_CONFIRMED` +- `INVOICED` +- `FAILED` +- `EXPIRED` +- `CANCELLED` + +Server SDK order waiters treat `PAYMENT_CONFIRMED` and `INVOICED` as successful +terminal states. Core can advance a DIRECT order from `PAYMENT_CONFIRMED` to +`INVOICED` in one watcher transaction, so a poller may observe only +`INVOICED`. Before fulfillment, still validate the authenticated order's +merchant context, chain, token, amount, recipient, and transaction identity. + +Cancel an abandoned order only while it remains `CHECKOUT_VERIFIED`: + +```ts +await client.cancelOrder(orderId) +``` + +## Path C: QuickPay / agent + +QuickPay accepts only canonical same-origin links: + +```bash +npx goatflow-quickpay inspect \ + https://flow-quickpay.testnet3.goat.network/quickpay/merchant_123/agent.md + +npx goatflow-quickpay pay-product \ + https://flow-quickpay.testnet3.goat.network/quickpay/merchant_123/agent.md \ + --product mug \ + --token USDC \ + --chain 48816 +``` + +The library derives the manifest and session endpoints from the trusted link +origin; it rejects remote `http` URLs and cross-origin endpoint substitution. + +QuickPay sessions have their own terminal set: `PAYMENT_CONFIRMED`, `EXPIRED`, +`FAILED`, and `CANCELLED`. Polling is bounded by `pollTimeoutMs`, retains a +known transaction hash across transient failures, and performs five bounded +grace polls when a known transaction is reported `EXPIRED`. Reconcile by +session ID and transaction hash instead of rebroadcasting after an ambiguous +post-broadcast failure. + +Library options are camelCase. For example, `payX402()` accepts `amount`, +`chainId`, `tokenSymbol`/`tokenContract`, `memo`, and `idempotencyKey`; it +derives the wire `merchant_id` and `payer_addr`. Do not pass raw API fields such +as `amount_wei`, `merchant_id`, or `payer_addr` to the library methods. + +## Path D: GOAT Flow MPP profile + +[MPP](https://mpp.dev/overview) is an independent open protocol. The example +below uses the current GOAT Flow adapter: deployment-specific JSON +challenge/verify endpoints, a direct ERC-20 transfer, and a GOAT-specific signed +receipt. It is not generic MPP client code, and no interoperability result with +the official MPP SDKs is currently published. + +```ts +import { MPPClient, MPPError } from 'goatflow-sdk' + +const mpp = new MPPClient({ + coreUrl: 'https://flow-api.testnet3.goat.network', // no trailing slash + signer, +}) + +async function payForRoute() { + try { + return await mpp.pay({ + merchantId: 'merchant_123', + routeCanonical: 'GET:api:data', + }) + } catch (error) { + if (error instanceof MPPError && error.recoverable) { + // Resume verification of the already-broadcast transfer. + return mpp.verifyChallenge(error.recoverable) + } + throw error + } +} + +const result = await payForRoute() +await fetch('/api/data', { + headers: { 'Payment-Receipt': result.receiptHeader }, +}) +``` + +This is the standalone GOAT Flow MPP adapter, so `coreUrl` is the Core/API +origin configured for that deployment. QuickPay `pay-mpp` instead derives +`coreUrl` from the trusted QuickPay link origin so discovery, challenge, and +verify remain same-origin. + +For this profile's challenge endpoint, success is HTTP `402`. Verify success is +HTTP `200` with its signed `Payment-Receipt` extension. A browser integration +works only when the Core origin +allows the DApp origin and exposes that response header, and the protected +resource allows the `Payment-Receipt` request header. Otherwise run the buyer +flow server-side. Once returned, the challenge is authoritative for payment +amount, chain, token, recipient, expiry, MAC, and pricing version. + +## Test environment + +| Resource | GOAT Testnet3 | GOAT Mainnet | +| --- | --- | --- | +| Chain ID | `48816` | `2345` | +| RPC | `https://rpc.testnet3.goat.network` | `https://rpc.goat.network` | +| Explorer | `https://explorer.testnet3.goat.network` | `https://explorer.goat.network` | +| Merchant Portal | `https://flow-merchant.testnet3.goat.network` | `https://flow-merchant.goat.network` | +| Admin Portal (operators only) | `https://flow-admin.testnet3.goat.network` | `https://flow-admin.goat.network` | +| Flow API / standalone MPP Core | `https://flow-api.testnet3.goat.network` | `https://flow-api.goat.network` | +| QuickPay / Checkout and same-origin public API | `https://flow-quickpay.testnet3.goat.network` | `https://flow-quickpay.goat.network` | + +GOAT native gas is BTC. Testnet3 gas is available from the +[faucet](https://bridge.testnet3.goat.network/faucet). Token contracts and +enabled transfer capabilities remain deployment/merchant-specific. + +## Troubleshooting + +### Order creation + +- Confirm key, timestamp, nonce, and signature inputs. +- Confirm merchant fee balance. +- Confirm the chain/token is enabled for this merchant. +- Treat HTTP `402` as success only on documented challenge endpoints. +- If status/proof/checkout/signature/cancel returns `402`, the Server SDK fails + closed; treat it as an endpoint or deployment mismatch. + +### Wallet transfer + +- Confirm `chainId`, `fromAddress`, and `expiresAt` before calling `pay()`. +- Confirm the token balance is at least `amountWei`. +- Read `PaymentResult.error`; `pay()` normally does not throw its payment error. + +### Status does not advance + +- Confirm token contract, recipient, amount, chain, and payer match the order. +- Allow for the deployment's confirmation/finality requirement. +- For Server SDK order polling, `INVOICED` is a successful terminal state. +- For QuickPay session polling, use its separate terminal set; it does not + include `INVOICED`. + +### GOAT Flow MPP transfer broadcast but verify failed + +- If `MPPError.recoverable` exists, call `verifyChallenge()` with it. +- Do not call `pay()` again for the same already-broadcast payment. +- Confirm Core CORS allows the DApp origin and exposes `Payment-Receipt`, and + the protected resource allows that origin and request header. + +## Related documents + +- [Integration Guide](./goat-flow-integration.md) +- [API Reference](./goat-flow-api-reference.md) +- [Hosted Checkout](./goat-flow-checkout.md) +- [GOAT Flow MPP Integration](./mpp.md) +- [Merchant Guide](./merchant-guide.md) +- [Onboarding Guide](./goat-flow-onboarding-guide.md) diff --git a/docs/goat-flow-faq.md b/docs/goat-flow-faq.md new file mode 100644 index 0000000..19aded6 --- /dev/null +++ b/docs/goat-flow-faq.md @@ -0,0 +1,438 @@ +# GOAT Flow FAQ + +This FAQ covers public SDK and API behavior and identifies settings that depend +on the active GOAT Flow environment. + +--- + +## Product and Payments + +### What is GOAT Flow? + +GOAT Flow provides commerce and transfer-verification software for merchants, +applications, and agents using the x402 protocol, including: + +- browser and server SDKs +- hosted checkout +- QuickPay buyer/agent tooling +- MPP buyer support and merchant middleware + +### What is DIRECT? + +For `ERC20_DIRECT`, the x402 challenge's `payTo` address is the merchant +receiving address. The browser SDK executes a standard ERC-20 transfer to that +address. + +--- + +## Chains, Tokens, Amounts, and Fees + +### Which chains are supported? + +There is no authoritative global chain list in the public SDK types. + +- For authenticated orders, use the returned x402 `accepts[]` entries. +- For QuickPay, use `rails.x402.tokens` in the merchant manifest. +- For the current GOAT Flow MPP profile, use `rails.mpp.routes`. +- The public `getMerchant(...)` result exposes configured merchant token + entries. + +Do not hardcode a chain table from narrative documentation. + +### Which tokens are supported? + +Token support is merchant and environment configuration. USDC and USDT appear +as examples in types and tests, but applications must use the token contract, +symbol, decimals, and limits returned at runtime. + +### Does QuickPay enforce minimum and maximum amounts? + +The QuickPay manifest includes `min_amount_wei` and optional +`max_amount_wei` for each x402 token entry. The QuickPay client validates those +values and preflights a fresh custom-amount payment against them. + +Products are priced with a positive decimal `price`; the selected token's +decimals determine the base-unit amount. + +### Is there a universal minimum such as USD 0.01? + +No universal minimum is defined. Use the runtime token bounds and the active +service policy. + +### What service fees are configured for a deployment? + +The public packages do not export a stable merchant service-fee schedule or +fee-balance type. The fee balance is separate from buyer-to-merchant transfers; +it must not be described as a balance of customer funds. Fees, top-up +requirements, reservation/refund rules, and insufficient-fee responses must be +confirmed with the deployed portal and API environment. + +Do not encode fixed values or environment-specific HTTP responses unless the +active API documents them. + +### Who pays gas? + +For DIRECT and the current GOAT Flow MPP profile, the buyer wallet broadcasts +the ERC-20 transfer and normally pays the chain's native gas. This is a property +of the GOAT Flow adapter, not MPP generally. A separate AA wallet or Paymaster +may sponsor gas, but that is not implemented by the GOAT Flow client SDK itself. + +--- + +## Orders and HTTP Semantics + +### Why does order creation return HTTP 402? + +HTTP 402 is the expected success path for `POST /api/v1/orders`. The response is +an x402 payment challenge with `x402Version`, `accepts[]`, order metadata, and +GOAT-specific extensions. + +The server SDKs treat HTTP 402 as success only for order creation. An +unexpected 402 from status, proof, checkout, signature, or cancellation fails +closed. + +### What payment fields are authoritative? + +Use the returned payment option: + +- `scheme` +- `network` +- `amount` +- `asset` +- `payTo` +- `maxTimeoutSeconds` + +Do not reconstruct these terms from a symbol-only or chain-only configuration. + +### What order statuses exist in the SDK? + +The public order status union contains: + +- `CHECKOUT_VERIFIED` +- `PAYMENT_CONFIRMED` +- `INVOICED` +- `FAILED` +- `EXPIRED` +- `CANCELLED` + +The server SDK's `waitForConfirmation(...)` returns on successful +`PAYMENT_CONFIRMED` or `INVOICED`, and on `FAILED`, `EXPIRED`, or `CANCELLED`. +Core can move a DIRECT order from `PAYMENT_CONFIRMED` to `INVOICED` in one +watcher transaction, so a poller may observe only `INVOICED`. + +### Can an order be canceled? + +The server SDK exposes cancellation for an order in `CHECKOUT_VERIFIED`. +Whether cancellation restores a fee balance or other reservation is a +server-side policy and should not be promised solely from the client method. + +An already-broadcast on-chain token transfer cannot be canceled by the SDK. + +### How should API errors be handled? + +TypeScript API failures are surfaced as the runtime-exported `GoatFlowError` +with: + +- `message` +- optional `code` +- optional HTTP `status` + +Authenticated-request failures also preserve `responseBody`. +`instanceof GoatFlowError` is supported. Branch on stable status/code fields +when the deployed API documents them; avoid parsing free-form error messages as +a long-term contract. + +--- + +## Proof and Fulfillment + +### How is payment confirmed? + +The public server SDK polls `GET /api/v1/orders/:orderId` and returns the +configured order status, transaction hash, and confirmation time when present. + +The internal listener architecture, RPC failover policy, and exact confirmation +thresholds are not exported by these packages. Treat them as deployment +configuration. + +### Is a browser checkout success callback proof of payment? + +No. Checkout callbacks are explicitly documented as UX signals. A merchant must +use a trusted backend order/session status or an authenticated webhook before +fulfillment. + +### Which webhook event should I use? + +The public SDKs do not export one canonical webhook event union. Confirm the +event name, payload, signature verification, and retry policy against the active +webhook contract. + +Do not build fulfillment around an event name copied only from a narrative +example. + +### Is there an order proof API? + +Yes. `getOrderProof(orderId)` returns a server-issued payment record containing +the order ID, transaction hash, log index, payer, recipient, amount, source +chain, and status. + +Its historical `signature` field is not a signature or attestation. It is +Keccak256 over `order_id`, `tx_hash`, `log_index`, `from_addr`, `to_addr`, +`amount_wei`, and `from_chain_id`, concatenated in that exact order without +separators; it does not cover `status`. Verify the transaction hash on-chain +when independent proof is required. + +--- + +## QuickPay and Products + +### What is the QuickPay discovery surface? + +A buyer or agent starts from one of these same-origin paths: + +- `/quickpay/` +- `/quickpay//agent.md` +- `/quickpay//manifest.json` + +The QuickPay client accepts only those canonical URL shapes, requires HTTPS +except for loopback development, and derives all subsequent endpoints from the +trusted origin. + +### What does the manifest contain? + +Schema `goatx402.quickpay.v1` contains merchant identity plus: + +- `rails.x402.enabled` +- custom-amount and memo flags +- x402 token entries and amount bounds +- optional QuickPay Products +- `rails.mpp.enabled` +- MPP route entries + +The client validates the trusted origin, merchant identity, and selected token, +Product, or MPP route before payment. It does not reject every malformed +enabled-rail container: non-array token/route lists are currently normalized to +empty lists, and the raw custom-amount client does not require the manifest's +`custom_amount` flag. The session/challenge response and deployed service remain +authoritative. + +### How do QuickPay Products work? + +A Product contains: + +- `product_key` +- `name` +- optional description and HTTPS image URL +- token-agnostic decimal `price` + +The buyer chooses a chain/token advertised by the merchant. For a fresh +purchase, the QuickPay client independently converts the price using token +decimals and refuses to broadcast unless the session's x402 terms match the +expected chain, token, amount, and recipient shape. + +### Can a fixed-price Product be opened without a merchant backend? + +Yes. The Checkout SDK supports: + +```ts +goat.open({ merchant, productKey }) +``` + +The browser URL contains the merchant and product key, not the product price. +The product must already exist in the merchant's QuickPay configuration. + +### What is a dynamic Hosted Checkout Session? + +The merchant backend calls `createCheckoutSession(...)` with +`checkoutType: "DIRECT"` and server-authoritative terms. It receives an opaque +`checkoutId`, which the browser opens with: + +```ts +goat.open({ checkoutId }) +``` + +The authenticated API key determines the merchant; the request body does not. + +### What is custom QuickPay? + +Custom QuickPay lets the browser or CLI supply an amount. It is appropriate for +tips, donations, or other cases where the merchant will reconcile the actual +payment. + +It is not appropriate as the sole price authority for automatically fulfilled +goods. + +### How does QuickPay reduce duplicate payments? + +QuickPay supports `idempotency_key`. The client recognizes reused sessions and +does not rebroadcast an unpaid reused session unless the caller explicitly +forces it. For Products, an explicit idempotency key also supports recovery when +the current manifest has changed. + +Status polling applies `pollTimeoutMs` as a hard cap and preserves a known +transaction hash across transient failures. A known transaction reported +`EXPIRED` receives five bounded grace polls for a possible late confirmation. +Reconcile by session ID and transaction hash instead of rebroadcasting after an +ambiguous post-broadcast failure. + +--- + +## Machine Payments Protocol + +### Is MPP a GOAT Flow protocol? + +No. [Machine Payments Protocol (MPP)](https://mpp.dev/overview) is an +independent open protocol with a standard Challenge/Credential/Receipt HTTP +exchange and extensible payment methods. GOAT Flow currently implements a +deployment-specific adapter with JSON challenge/verify endpoints, a direct +ERC-20 transfer, and a signed three-segment receipt extension. + +No interoperability result with official MPP SDKs is currently published. Do +not treat `MPPClient` or the middleware as a generic or official MPP +implementation without an adapter and conformance testing. + +### What does a buyer using the GOAT Flow MPP profile need? + +A buyer using the current GOAT Flow profile needs: + +- a trusted QuickPay URL +- an EVM wallet signer +- an RPC connection for the route's chain +- the exact `route_canonical` advertised in the manifest + +The buyer does not use the merchant API key or secret. + +### What is the current GOAT Flow MPP integration flow? + +1. `POST /mpp/v1/challenge` +2. transfer the challenged ERC-20 amount to the challenged recipient +3. `POST /mpp/v1/verify` +4. receive the signed `Payment-Receipt` header +5. call the protected merchant route with that header + +### What does the GOAT Flow receipt extension protect? + +The middleware verifies: + +- signature and configured algorithm +- merchant audience +- canonical route binding +- expiry +- optional receipt-ID single-use consumption + +A successful receipt is attached to the Express or Fastify request, or to the +Go request context. + +### What are the merchant middleware error semantics? + +The TypeScript middleware returns stable JSON reason codes: + +| Condition | HTTP status | `error` | +| --- | ---: | --- | +| Missing receipt | 401 | `payment_required` | +| Malformed receipt | 401 | `invalid_payment_receipt` | +| Invalid signature | 401 | `invalid_signature` | +| Wrong merchant | 401 | `audience_mismatch` | +| Wrong route | 402 | `route_mismatch` | +| Expired receipt | 402 | `receipt_expired` | +| Already consumed | 401 | `receipt_already_consumed` | +| Receipt store unavailable | 503 | `receipt_store_unavailable` | + +Unexpected verifier throws fail closed with HTTP 500 `internal_error` in the +Express adapter. + +### How does GOAT Flow profile verification polling behave? + +The browser SDK: + +- retries HTTP 202 and 429 using `Retry-After` +- retries network failures and 5xx responses with bounded backoff +- treats other 4xx responses as terminal +- requires a `Payment-Receipt` header on HTTP 200 +- returns stable `MPPError.code` values + +### What if verification fails after the transfer was broadcast? + +Do not call `pay()` again blindly. The SDK attaches a `recoverable` payload to +post-broadcast `MPPError` instances so the caller can run +`verifyChallenge(...)` for the existing transaction. + +The QuickPay CLI also avoids reporting success unless it has both a transaction +hash and the signed receipt header. + +--- + +## Credentials and Account Security + +### Where should merchant API credentials live? + +Only on the merchant backend. The browser SDK must receive normalized order +data, not the API key or secret. + +### How are authenticated SDK requests signed? + +The TypeScript server SDK signs parameters with HMAC-SHA256 and sends: + +- `X-API-Key` +- `X-Timestamp` +- unique `X-Nonce` +- `X-Sign` + +The server's exact timestamp window, nonce retention, and rejection codes are +not exported by the public client package; confirm them with the deployed API +contract. + +### What is the registration and approval workflow? + +Merchant registration, approval, API-key issuance, and rejection policy are +managed through the Merchant Portal. Follow the portal and +[Merchant Guide](./merchant-guide.md) for the target environment. + +### How do password recovery and 2FA work? + +Password changes, account recovery, 2FA enrollment and reset, session lifetime, +and administrator permissions are managed through the active portal. See the +[Merchant Guide](./merchant-guide.md#4-approval-login-and-account-security), and +contact [Support@goat.network](mailto:Support@goat.network) when self-service +recovery is unavailable. + +### Which wallets are supported? + +The browser SDK accepts an `ethers.Signer` and executes standard ERC-20 +operations. No tested vendor-by-vendor wallet compatibility matrix is currently +published. + +WalletConnect, embedded wallets, account-abstraction wallets, and Paymasters may +work when they provide compatible signer/provider behavior, but they are not a +GOAT Flow SDK guarantee. + +--- + +## Operations, Compliance, and Support + +### Where are KYC, AML, tax, and privacy requirements defined? + +These technical documents do not provide a merchant compliance determination. +Merchants must confirm jurisdiction-specific requirements, data handling, +reporting, and sanctions controls separately. + +### What should be confirmed before production? + +- merchant approval and enabled receive type +- receiving addresses +- live chain/token entries and limits +- fee and top-up policy +- API base URL and checkout origin +- webhook contract and signature validation +- MPP receipt algorithm/key and shared replay store for multi-replica services +- fulfillment behavior for every terminal status + +### Where can I get support? + +Contact [Support@goat.network](mailto:Support@goat.network). + +## Related + +- [Documentation hub](./README.md) +- [Developer Quick Start](./goat-flow-developer-quickstart.md) +- [Merchant Guide](./merchant-guide.md) diff --git a/docs/goat-flow-integration.md b/docs/goat-flow-integration.md new file mode 100644 index 0000000..6f42687 --- /dev/null +++ b/docs/goat-flow-integration.md @@ -0,0 +1,803 @@ +# GOAT Flow Integration Guide + +This is the detailed integration guide for the GOAT Flow API and SDKs. +Use the [Developer Quick Start](./goat-flow-developer-quickstart.md) for a first +payment and the [API Reference](./goat-flow-api-reference.md) for field-level wire +contracts. + +## Contents + +1. [System boundaries](#1-system-boundaries) +2. [Packages and versions](#2-packages-and-versions) +3. [Integration surfaces](#3-integration-surfaces) +4. [DIRECT order flow](#4-direct-order-flow) +5. [Server SDK integration](#5-server-sdk-integration) +6. [Browser order integration](#6-browser-order-integration) +7. [Hosted Checkout](#7-hosted-checkout) +8. [QuickPay](#8-quickpay) +9. [MPP](#9-mpp) +10. [Authentication](#10-authentication) +11. [Order lifecycle](#11-order-lifecycle) +12. [Errors and retries](#12-errors-and-retries) +13. [Production checklist](#13-production-checklist) +14. [Known compatibility notes](#14-known-compatibility-notes) + +## 1. System boundaries + +GOAT Flow is the product name. x402 is the payment-challenge protocol used by +the order surfaces. [MPP](https://mpp.dev/overview) is a separate, independent +open protocol. The GOAT Flow MPP components implement the current integration +profile. Public packages use `goatflow-*`; repository directories, protocol +fields, and fixed environment variables may retain `goatx402`. + +```text +Merchant backend + | + | HMAC-authenticated order / checkout / status / proof API + v +GOAT Flow merchant API + | + | x402 payment terms + v +Merchant frontend or hosted page + | + | User-authorized ERC-20 transfer + v +EVM chain + | + | Watcher / verifier observes and validates the transfer + v +GOAT Flow order state / Payment-Receipt +``` + +Security boundaries: + +- Merchant API key and secret exist only on the backend. +- The browser receives payment terms, never merchant credentials. +- A wallet transaction hash is not a fulfillment decision. +- Backend status/proof, an authenticated deployment-defined webhook, or the + GOAT Flow MPP profile's `Payment-Receipt` extension is the authoritative + completion signal for the corresponding surface. + +## 2. Packages and versions + +| Package/module | Role | Current manifest/runtime | +| --- | --- | --- | +| `goatflow-sdk-server` | TypeScript merchant backend | `0.3.0`, Node >= 18 | +| `github.com/goatnetwork/goatflow-sdk-server` | Go merchant backend | Go 1.25, source-only | +| `goatflow-sdk` | Browser wallet, ERC-20, MPP | `0.2.1`, ethers `^6.9.0` | +| `goatflow-checkout` | Hosted Checkout opener | `0.1.0` | +| `goatflow-quickpay` | Public payer/agent library and CLI | `0.3.0`, Node >= 18 | +| `@goatnetwork/mpp-middleware` | Merchant MPP middleware | `0.1.0` | + +Use package manifests and exported types as the version source of truth. Do not +copy version numbers into application compatibility logic. + +## 3. Integration surfaces + +### 3.1 Hosted Checkout + +Use when the application should rely on GOAT Flow-hosted checkout software for +wallet connection and transfer UX. Your backend creates dynamic sessions; +fixed QuickPay products can open directly. + +### 3.2 Custom order flow + +Use when the merchant builds its own wallet and transfer UI. The backend creates +the order and maps it to the browser SDK `Order`; the buyer's browser submits +the transfer. + +### 3.3 QuickPay + +Use for public payer links, products, custom amounts, agents, and CLI payments. +The payer discovers a same-origin manifest and does not need merchant API +credentials. + +### 3.4 MPP + +Use the current GOAT Flow MPP profile for paid API routes. The buyer obtains the +profile's JSON challenge, submits a direct transfer, asks Core to verify the +transaction, and attaches the returned signed `Payment-Receipt` extension to +the protected request. This is not the standard MPP +Challenge/Credential/Receipt HTTP wire exchange. + +## 4. DIRECT order flow + +The DIRECT order flow is: + +1. Backend creates an order. +2. Core returns an x402 challenge with `payTo`. +3. Browser transfers the token to the merchant receiving address. +4. Core observes the transfer and advances order state. + +Flow identifier: `ERC20_DIRECT`. + +## 5. Server SDK integration + +### 5.1 TypeScript client + +```ts +import { GoatFlowClient } from 'goatflow-sdk-server' + +const client = new GoatFlowClient({ + baseUrl: process.env.GOATX402_API_URL ?? 'https://flow-api.goat.network', + apiKey: process.env.GOATX402_API_KEY!, + apiSecret: process.env.GOATX402_API_SECRET!, +}) +``` + +Create an order: + +```ts +// Generate this once per payment intent, persist it, and reuse it on retries. +const persistedPaymentIntentId = 'payment_intent_01J...' + +const order = await client.createOrder({ + dappOrderId: persistedPaymentIntentId, + chainId: 2345, + tokenSymbol: 'USDC', + fromAddress: '0xPayer', + amountWei: '10000000', +}) +``` + +`createOrder()` accepts the successful HTTP `402` response and normalizes the +first x402 payment option. Use `createOrderRaw()` for the literal x402 object. + +Operator-provisioned callback orders are outside public merchant onboarding. +When a deployment contract explicitly enables one, follow the complete fields, +EIP-712 signing, signature-submission endpoint, and chain-switching rules in +the [API Reference appendix](./goat-flow-api-reference.md#appendix-a-operator-provisioned-callback-compatibility). + +Read status and proof: + +```ts +const orderStatus = await client.getOrderStatus(order.orderId) + +const fulfillable = + orderStatus.status === 'PAYMENT_CONFIRMED' || + orderStatus.status === 'INVOICED' + +if (fulfillable) { + const proof = await client.getOrderProof(order.orderId) +} +``` + +Cancel only while pending: + +```ts +const orderStatus = await client.getOrderStatus(order.orderId) + +if (orderStatus.status === 'CHECKOUT_VERIFIED') { + await client.cancelOrder(order.orderId) +} +``` + +### 5.2 TypeScript errors + +```ts +import { GoatFlowError } from 'goatflow-sdk-server' + +try { + await client.createOrder(params) +} catch (error) { + if (error instanceof GoatFlowError) { + console.error(error.status, error.code, error.responseBody) + } +} +``` + +For authenticated HTTP failures, the client parses `error` or `message`, +attaches `status`, optional `code`, and runtime `responseBody`, and names the +runtime-exported error `GoatFlowError`. `instanceof GoatFlowError` is supported. +Fetch failures may remain native errors. + +### 5.3 Go client + +```go +client := goatflow.NewClient(goatflow.Config{ + BaseURL: os.Getenv("GOATX402_API_URL"), + APIKey: os.Getenv("GOATX402_API_KEY"), + APISecret: os.Getenv("GOATX402_API_SECRET"), +}) + +order, err := client.CreateOrder(ctx, goatflow.CreateOrderParams{ + DappOrderID: "order_123", + ChainID: 2345, + TokenSymbol: "USDC", + FromAddress: "0xPayer", + AmountWei: "10000000", +}) +if err != nil { + var apiErr *goatflow.APIError + if errors.As(err, &apiErr) { + log.Printf( + "status=%d code=%s body=%s", + apiErr.Status, + apiErr.Code, + apiErr.ResponseBody, + ) + } + return err +} +``` + +The Go client also exposes: + +- `CreateOrderRaw` +- `CreateCheckoutSession` +- `GetOrderStatus` +- `GetOrderProof` +- `CancelOrder` +- `GetMerchant` +- `WaitForConfirmation` +- `SetHTTPClient` + +Go HTTP failures are returned as `*goatflow.APIError`; use `errors.As`. +Transport and JSON failures are wrapped Go errors. + +### 5.4 Polling and HTTP compatibility differences + +| Behavior | TypeScript | Go | +| --- | --- | --- | +| First status read | Immediate | After the first interval tick | +| Status-read error while polling | Transient errors retried; deterministic 4xx surfaced | Suppressed and retried | +| Status callback | `onStatusChange` | None | +| Cancellation | Overall timeout plus per-request deadline | Timeout or `context.Context` | +| Built-in terminal states | `PAYMENT_CONFIRMED`, `INVOICED`, `FAILED`, `EXPIRED`, `CANCELLED` | Same | +| First-party request deadline | 30 seconds, bounded by remaining wait timeout | 30-second default HTTP client; replaceable | +| Authenticated HTTP success | Any `2xx`; `402` only for order creation | Exactly `200`; `402` only for order creation | + +TypeScript retries request timeouts, network failures, `408`, `429`, and server +errors within the overall wait deadline; other deterministic 4xx errors are +surfaced immediately. Go currently retries all status-read errors. + +## 6. Browser order integration + +### 6.1 Do not pass the server order unchanged + +The server SDK `Order` has: + +- `fromChainId` +- `payToChainId` +- no `fromAddress` + +The browser SDK `Order` requires: + +- `chainId` +- `fromAddress` + +Map a minimal object: + +```ts +import type { Order as ServerOrder } from 'goatflow-sdk-server' +import type { Order as ClientOrder } from 'goatflow-sdk' + +export function toClientOrder( + order: ServerOrder, + fromAddress: string, +): ClientOrder { + return { + orderId: order.orderId, + flow: order.flow, + tokenSymbol: order.tokenSymbol, + tokenContract: order.tokenContract, + fromAddress, + payToAddress: order.payToAddress, + chainId: order.fromChainId, + amountWei: order.amountWei, + expiresAt: order.expiresAt, + calldataSignRequest: order.calldataSignRequest, + } +} +``` + +Avoid `{ ...serverOrder, fromAddress, chainId }`; an explicit allowlist makes the +boundary reviewable and prevents internal/raw fields from leaking. + +### 6.2 Validate before payment + +`PaymentHelper.pay()` does not validate chain, payer, or expiry. + +```ts +async function validateOrder( + order: ClientOrder, + provider: ethers.BrowserProvider, + signer: ethers.Signer, +): Promise { + const network = await provider.getNetwork() + if (Number(network.chainId) !== order.chainId) { + throw new Error(`Wallet is on ${network.chainId}; order requires ${order.chainId}`) + } + + const payer = await signer.getAddress() + if (payer.toLowerCase() !== order.fromAddress.toLowerCase()) { + throw new Error('Connected wallet does not match the order payer') + } + + if (Math.floor(Date.now() / 1000) >= order.expiresAt) { + throw new Error('Order expired') + } +} +``` + +### 6.3 Pay + +```ts +import { PaymentHelper } from 'goatflow-sdk' +import { ethers } from 'ethers' + +const provider = new ethers.BrowserProvider(window.ethereum) +const signer = await provider.getSigner() +await validateOrder(order, provider, signer) + +const payment = new PaymentHelper(signer) + +const result = await payment.pay(order) +if (!result.success) { + throw new Error(result.error ?? 'Payment failed') +} +``` + +Actual `pay()` behavior: + +1. Read signer address. +2. Read token balance. +3. Return failure if balance is insufficient. +4. Submit `transfer(payToAddress, amountWei)`. +5. Wait for a receipt. +6. Require receipt status `1`. +7. Return `txHash`. + +The method catches errors and normally returns `{ success: false, error }` +instead of throwing them. + +`PaymentHelper.pay()` treats every `tx.wait()` exception as failure and does not +classify `TRANSACTION_REPLACED`. A successful wallet speed-up can therefore +look failed. Reconcile the original/replacement hash and backend order before +considering another transfer. + +### 6.4 ERC-20 approval helpers + +Order payment itself uses `transfer()` and does not require an allowance. +Approval helpers are for other integration needs. + +```ts +const exactTx = await payment.approveToken(token, spender, amount) +const unlimitedTx = await payment.approveToken( + token, + spender, + amount, + { unlimited: true }, +) +``` + +The helper: + +- validates `bigint` and uint256 bounds before any transaction +- skips writes when the target allowance is already set +- uses a direct-write `eth_call` probe +- falls back to confirmed `approve(0)` only when required +- follows matching fee-bump replacements + +Use `ERC20Token.setApproval()` when the reset transaction hash is also needed. + +## 7. Hosted Checkout + +### 7.1 Fixed product + +```ts +import { GoatCheckout } from 'goatflow-checkout' + +const goat = GoatCheckout({ origin: 'https://flow-quickpay.goat.network' }) + +goat.open({ + merchant: 'merchant_123', + productKey: 'mug', + clientReferenceId: 'cart_123', + onSuccess(result) { + // UX only + console.log(result) + }, +}) +``` + +### 7.2 Dynamic DIRECT session + +```ts +const session = await client.createCheckoutSession({ + checkoutType: 'DIRECT', + price: '19.95', + clientReferenceId: 'cart_123', + lineItems: [{ name: 'Mug', amount: '19.95', quantity: 1 }], + publicMetadata: { campaign: 'summer' }, + privateMetadata: { internalCartId: 'db-42' }, +}) + +// Browser: +goat.open({ checkoutId: session.checkoutId }) +``` + +The SDK serializes nested values into signed JSON strings. + +The response exposes `checkoutType` as `string`; handle unknown future values +explicitly. Public integrations create `DIRECT` sessions. Operator-provisioned +compatibility fields, deprecated wrappers, signature submission, and the +`callback_template` trust boundary are documented in the +[API Reference appendix](./goat-flow-api-reference.md#appendix-a-operator-provisioned-callback-compatibility). + +### 7.3 Hosted security model + +- `origin` must be a bare HTTPS origin; HTTP is allowed only for loopback. +- The opener accepts messages only from the configured origin, exact popup + source, and matching nonce. +- A server-created checkout ID is an opaque bearer capability. +- Product and checkout-session prices are server-authoritative. +- Browser callbacks are non-sensitive UX events, not proof of payment. + +## 8. QuickPay + +The canonical public link shape is: + +```text +https://flow-quickpay.goat.network/quickpay/{merchant_id}/agent.md +``` + +The package: + +1. Validates the URL scheme and canonical path. +2. Takes the merchant ID from the trusted URL path. +3. Derives the same-origin manifest URL. +4. Validates the manifest merchant ID and rail data. +5. Derives session/MPP endpoints from the same origin. + +```ts +import { QuickPayClient, EthersPaymentBackend } from 'goatflow-quickpay' + +const quickpay = new QuickPayClient( + 'https://flow-quickpay.goat.network/quickpay/merchant_123/agent.md', +) + +const manifest = await quickpay.loadManifest() +const inspected = await quickpay.inspect() +``` + +The raw session API uses snake_case: + +```json +{ + "merchant_id": "merchant_123", + "payer_addr": "0xPayer", + "chain_id": 2345, + "token_contract": "0xToken", + "amount_wei": "10000000", + "memo": "invoice-123", + "idempotency_key": "invoice-123:payer" +} +``` + +`QuickPayClient` methods do not accept that object. `payX402()` uses +`amount`, `chainId`, `tokenSymbol`/`tokenContract`, `memo`, and +`idempotencyKey`; `payProduct()` uses `productKey`, chain/token selection, and +optional `idempotencyKey`. The client derives `merchant_id` from the trusted URL +and `payer_addr` from the payment backend. There is currently no +`clientReferenceId` library option. + +CLI: + +```bash +npx goatflow-quickpay inspect +npx goatflow-quickpay pay-x402 --amount 10 --token USDC --chain 2345 +npx goatflow-quickpay pay-product --product mug --token USDC --chain 2345 +npx goatflow-quickpay pay-mpp --route GET:api:data +``` + +Product mode uses the manifest's decimal `price` and the chosen token decimals. +Custom amount mode is untrusted for automatic fulfillment unless the backend +reconciles the actual paid amount. + +QuickPay session terminal states are `PAYMENT_CONFIRMED`, `EXPIRED`, `FAILED`, +and `CANCELLED`; they are separate from Server SDK order states. Status polling +uses `pollTimeoutMs` as a hard cap, retains known transaction hashes across +transient failures, and performs five bounded grace polls when a known +transaction is reported `EXPIRED`. Reconcile by session ID and transaction hash +instead of rebroadcasting after an ambiguous failure. + +## 9. MPP + +MPP itself is an independent, payment-method-agnostic open protocol. This +section documents the current GOAT Flow adapter, not a generic or official +MPP SDK. Its `/mpp/v1/challenge` and `/mpp/v1/verify` JSON endpoints and signed +three-segment receipt are deployment-specific contracts. + +### 9.1 High-level flow + +```ts +import { MPPClient } from 'goatflow-sdk' + +const mpp = new MPPClient({ + coreUrl: 'https://flow-api.goat.network', // no trailing slash + signer, +}) + +const result = await mpp.pay({ + merchantId: 'merchant_123', + routeCanonical: 'GET:api:data', + requestCanonical: 'GET:api:data:user-42', + onPhase: (phase, detail) => console.log(phase, detail), +}) +``` + +`requestCanonical` defaults to `routeCanonical`. When present, it must preserve +the route prefix accepted by Core. + +For the standalone GOAT Flow `MPPClient` adapter, `coreUrl` is the configured +Core/API origin for the deployment. The QuickPay `pay-mpp` adapter instead sets +`coreUrl` to the trusted +QuickPay link origin so manifest, challenge, and verify stay same-origin. Do not +assume those two deployment origins are universally the same. + +### 9.2 Challenge + +The GOAT Flow profile's `POST /mpp/v1/challenge` returns HTTP `402` on success. + +The client decodes: + +- `challenge_id` +- `expiry` (and legacy `expiry_unix`) +- `amount_wei` +- `chain_id` +- `token_contract` +- `recipient` +- `mac` +- `route_pricing_version` + +These challenge fields are authoritative for the payment. Route data in a +manifest is discovery metadata; after challenge issuance, do not override the +amount, chain, token, recipient, expiry, MAC, or pricing version. + +### 9.3 Broadcast + +Before broadcasting, `payChallenge()` checks: + +- signer has a provider +- challenge has not expired +- provider chain matches challenge chain + +It submits the buyer-authorized ERC-20 transfer and returns `{ txHash, tx }` +immediately. Pass `txHash` to `verifyChallenge()`; use the ethers +`TransactionResponse` in `tx` to observe a matching fee-bump replacement. The +method does not wait for a local receipt because +`/mpp/v1/verify` confirms on-chain finality for this integration profile and +issues its signed receipt. It does not hold or release the transferred tokens. + +### 9.4 Verify and retry + +`POST /mpp/v1/verify` behavior: + +| Response | Behavior | +| --- | --- | +| `200` + `Payment-Receipt` | Decode and return receipt | +| `202` | Retry using `Retry-After` | +| `429` | Retry using `Retry-After` | +| other `4xx` | Terminal `MPPError` | +| `5xx` | Bounded exponential backoff | +| network rejection | Bounded exponential backoff | + +Default maximum attempts: 16. Maximum per-wait delay: 30 seconds. + +The transfer replacement watcher follows only a replacement with the same +destination and calldata. A user cancellation or unrelated same-nonce +transaction is not treated as the challenged transfer. + +### 9.5 Recovery after broadcast + +```ts +import { MPPError } from 'goatflow-sdk' + +try { + await mpp.pay(params) +} catch (error) { + if (error instanceof MPPError && error.recoverable) { + const recovered = await mpp.verifyChallenge(error.recoverable) + console.log(recovered.receiptHeader) + return + } + throw error +} +``` + +Once a transfer has been broadcast, do not call `pay()` again merely because +verification failed. Preserve and resume the challenge/transaction context. + +Browser Core CORS must allow the DApp origin and expose the `Payment-Receipt` +response header. The protected resource must allow that origin and the +`Payment-Receipt` request header. + +## 10. Authentication + +The server SDKs produce: + +```text +X-API-Key: +X-Timestamp: +X-Nonce: +X-Sign: +``` + +The signed parameter set contains: + +- every non-null top-level body field converted to string +- `api_key` +- `timestamp` +- `nonce` + +Keys are sorted and joined as `key=value&...`; empty strings and `sign` are +excluded. + +Hosted Checkout nested fields are JSON-stringified because this signing scheme +does not canonicalize nested JSON. + +Operational rules: + +- Generate a new nonce for every request. +- Keep system time synchronized. +- Never log the API secret or full credential set. +- Avoid custom signing unless cross-validated against both SDK tests. + +## 11. Order lifecycle + +Current TypeScript status values include: + +```ts +type OrderStatus = + | 'CHECKOUT_VERIFIED' + | 'PAYMENT_CONFIRMED' + | 'INVOICED' + | 'FAILED' + | 'EXPIRED' + | 'CANCELLED' +``` + +Recommended application classification: + +| Class | Status | +| --- | --- | +| Pending | `CHECKOUT_VERIFIED` | +| Confirmed success | `PAYMENT_CONFIRMED`, `INVOICED` | +| Failure/closed | `FAILED`, `EXPIRED`, `CANCELLED` | + +Server SDK order waiters treat `INVOICED` as a successful terminal state. Core +can advance a DIRECT order from `PAYMENT_CONFIRMED` to `INVOICED` inside one +watcher transaction, so polling code must not require observing both states. +Do not assume every deployment exposes each transition. + +`cancelOrder()` is documented for `CHECKOUT_VERIFIED`. Reservation restoration, +fee refunds, and automatic-expiration effects are not part of the public SDK +contract; confirm them with the active API before relying on them. + +## 12. Errors and retries + +### 12.1 Merchant API + +| HTTP | Treatment | +| --- | --- | +| `400` | Validation/business error; inspect body | +| `401` | HMAC/timestamp/nonce failure | +| `402` | Success only for documented challenge endpoints | +| `403` | Ownership/authorization failure | +| `404` | Resource not found | +| `5xx` | Caller-managed bounded retry for idempotent operations | + +Do not blindly retry order creation without a stable merchant +`dappOrderId`/idempotency strategy. + +Both server SDKs fail closed on unexpected authenticated `402` responses. +Individual merchant API calls do not retry automatically. +`waitForConfirmation()` retries eligible status-read failures within its +overall deadline; Go `WaitForConfirmation` currently retries all status-read +errors. This differs from the MPP verifier, which has its own bounded retry +policy. + +### 12.2 Browser order payment + +`PaymentHelper.pay()` returns payment failures: + +```ts +const result = await payment.pay(order) +if (!result.success) { + console.error(result.error) +} +``` + +Wallet user rejection, RPC errors, transfer reverts, and failed receipts are +converted to the `error` string. + +### 12.3 MPP + +MPP methods throw `MPPError`. Branch on `code`, not message. Important codes +include: + +- `network_error` +- `parse_error` +- `bad_request` +- `invalid_request` +- `route_not_found` +- `chain_mismatch` +- `user_rejected` +- `payment_failed` +- `challenge_expired` +- `challenge_already_consumed` +- `challenge_tx_hash_mismatch` +- `payer_mismatch` +- `verify_timeout` +- `service_unavailable` +- `receipt_missing` +- `receipt_malformed` + +Application `onPhase` callbacks can throw outside the SDK's error wrapper and +replace the expected `MPPError`, including during the `failed` phase. Keep +phase callbacks non-throwing or catch their errors locally. + +## 13. Production checklist + +1. Keep merchant credentials in a backend secret store. +2. Use separate credentials and origins per environment. +3. Derive amount/token/chain from server-side product/cart data. +4. Map server orders to browser orders with an explicit allowlist. +5. Validate chain, payer, and expiry before `PaymentHelper.pay()`. +6. Treat authenticated `PAYMENT_CONFIRMED` and `INVOICED` order states as + successful terminals, while validating all expected order fields. +7. Fulfill idempotently from trusted server evidence. +8. Cancel abandoned `CHECKOUT_VERIFIED` orders. +9. Monitor merchant fee-balance errors. +10. Treat checkout IDs and QuickPay session IDs as capabilities. +11. For the browser MPP adapter, allow the DApp origin on Core and the protected resource, + expose the response `Payment-Receipt`, allow that request header, and retain + recoverable verify context after broadcast. + +## 14. Known compatibility notes + +### 14.1 Polling differences + +Both polling helpers stop on `PAYMENT_CONFIRMED`, `INVOICED`, `FAILED`, +`EXPIRED`, or `CANCELLED`. TypeScript performs the first read immediately and +selectively retries transient failures; Go waits one interval and retries every +status-read error until timeout/context cancellation. Use explicit polling when +an application needs one cross-language retry policy. + +### 14.2 Merchant token-list field + +The TypeScript `getMerchant()` implementation reads `wallets[]` and maps it to +`supportedTokens`. The Go `MerchantInfo` type expects `supported_tokens`. +Verify the target deployment response before using the Go field. + +### 14.3 Browser compatibility + +No tested minimum-version browser matrix is currently published. The browser path +requires an EIP-1193 wallet/provider and modern features used by ethers and the +SDK (`BigInt`, `fetch`, `URL`, Promises, and Web Crypto/browser primitives). + +### 14.4 Runtime capability + +Supported chains, tokens, fee configuration, redirect allowlists, and merchant +payment capabilities are deployment-specific. Discover them from trusted +runtime responses or operator configuration rather than a static documentation +table. + +### 14.5 Generated declaration examples + +The current generated Checkout declaration contains an outdated example +origin: `goatx402-checkout/dist/types.d.ts` mentions `pay.goat.network`. It is +not an active deployment origin and must not be copied into integrations. The +current origins are listed in the [documentation hub](./README.md#service-origins). + +### 14.6 MPP interoperability + +The current `MPPClient` and middleware do not implement the standard MPP +HTTP Challenge/Credential/Receipt exchange. They use GOAT Flow JSON +challenge/verify endpoints, a direct ERC-20 transfer, and a signed +three-segment receipt extension. No conformance or interoperability result with +official MPP SDKs is currently published. Treat these components as a +deployment-specific adapter unless standards compatibility is explicitly +documented and tested. diff --git a/docs/goat-flow-onboarding-guide.md b/docs/goat-flow-onboarding-guide.md new file mode 100644 index 0000000..450752b --- /dev/null +++ b/docs/goat-flow-onboarding-guide.md @@ -0,0 +1,109 @@ +# GOAT Flow Onboarding Guide + +This is the launch map for merchants, developers, and operations teams. Detailed +portal procedures and screenshots live in the +[Merchant Guide](./merchant-guide.md); technical implementation lives in the +[Developer Quick Start](./goat-flow-developer-quickstart.md) and +[Integration Guide](./goat-flow-integration.md). + +The public GOAT Flow path is **DIRECT**: the buyer transfers the selected ERC-20 +token directly to the merchant receiving address returned by the payment terms. + +## Environment Separation + +Treat Production and Testnet3 as separate deployments. + +| Resource | Production | Testnet3 | +| --- | --- | --- | +| Merchant portal | `https://flow-merchant.goat.network` | `https://flow-merchant.testnet3.goat.network` | +| Admin portal (operators only) | `https://flow-admin.goat.network` | `https://flow-admin.testnet3.goat.network` | +| Flow API / standalone MPP Core | `https://flow-api.goat.network` | `https://flow-api.testnet3.goat.network` | +| QuickPay / checkout and same-origin public API | `https://flow-quickpay.goat.network` | `https://flow-quickpay.testnet3.goat.network` | + +The QuickPay library derives its API paths from the trusted QuickPay link +origin. Use `flow-api` for authenticated merchant APIs and for standalone MPP +only when that Core origin is explicitly configured. + +Keep separate merchant records, users, credentials, secret-manager entries, +receiving wallets, webhook endpoints/secrets, fee balances, products, MPP +routes, logs, alerts, and runbooks. + +Do not derive production configuration from Testnet3 examples or screenshots. +Read chain IDs, token contracts, decimals, limits, fees, RPCs, explorers, and +enabled capabilities from the active environment. Order IDs, nonces, +idempotency keys, and wallet addresses are also environment-specific. + +Before launch, create a reviewed production configuration record from the live +portal and API. + +## Five-Step Path + +| Step | Action | Detailed home | +| --- | --- | --- | +| **1. Register** | Create the merchant account and owner user; wait for approval | [Merchant Guide §3-4](./merchant-guide.md#3-register-a-merchant-account) | +| **2. Configure receiving** | Add one valid receiving address for every accepted chain/token pair | [Merchant Guide §6](./merchant-guide.md#6-configure-receiving-addresses) | +| **3. Prepare access** | Configure QuickPay/Products and create backend API credentials only when required | [Merchant Guide §8](./merchant-guide.md#8-api-keys-management) and [§12](./merchant-guide.md#12-quickpay-and-products) | +| **4. Integrate** | Have a test buyer submit the first DIRECT transfer using the selected integration surface | [Developer Quick Start](./goat-flow-developer-quickstart.md) | +| **5. Test and launch** | Validate transfer verification, fulfillment, reconciliation, and operations in Testnet3 before production | [Integration Guide §13](./goat-flow-integration.md#13-production-checklist) | + +## Completion Checks + +| Step | Done when | +| --- | --- | +| Register | Merchant approved and enabled; owner can sign in | +| Configure receiving | Export or reviewed record of every enabled chain, token, and receiving address | +| Prepare access | QuickPay links/products are available, or the one-time API secret is stored server-side | +| Integrate | Application creates an order/session, presents runtime payment terms, and keeps merchant secrets out of the browser | +| Test and launch | Confirmed/invoiced test transfer, trusted fulfillment result, reconciliation record, and sufficient launch fee balance | + +Keep completion records tied to their environment. Testnet3 screenshots and +transactions confirm testing only; they are not Mainnet configuration. + +## Go-Live Checklist + +**Environment** + +- [ ] Production URLs, merchant ID, credentials, wallets, chain/token registry, + webhooks, QuickPay/MPP configuration, and fee policy were verified from + the live deployment. +- [ ] No Testnet3 credential, token contract, wallet, order ID, or callback URL + is present in production configuration. + +**Merchant** + +- [ ] Merchant is approved and enabled. +- [ ] Receiving addresses are correct for every accepted chain/token pair. +- [ ] Merchant users reviewed password, 2FA, recovery, and role ownership. +- [ ] Fee balance covers expected launch traffic and top-up ownership is clear. + +**Integration** + +- [ ] Merchant API secrets remain backend-only. +- [ ] Pricing and payment terms are server/runtime-authoritative. +- [ ] Wallet chain, payer, recipient, amount, token, and expiry are validated. +- [ ] HTTP `402` challenge responses are handled as documented. +- [ ] Confirmed and failed/cancelled/expired states are handled. +- [ ] Status/proof or the verified GOAT Flow MPP-profile receipt gates fulfillment. +- [ ] Browser checkout callbacks do not unlock fulfillment by themselves. +- [ ] Webhook authentication and retry behavior are verified when webhooks are + used. + +**Operational Validation** + +- [ ] A Testnet3 buyer transfer completed end to end. +- [ ] The transfer appears in Order Reconciliation with matching transaction, + amount, token, chain, and status. +- [ ] Error messaging, retry limits, abandoned-order handling, monitoring, + escalation, and support ownership were exercised. +- [ ] The production configuration and launch record were reviewed by + merchant, engineering, and operations owners. + +Related references: + +- [API Reference](./goat-flow-api-reference.md) +- [Hosted Checkout](./goat-flow-checkout.md) +- [GOAT Flow MPP Integration](./mpp.md) +- [DApp Integration Skill](./goat-flow-dapp-integration/SKILL.md) +- [GOAT Flow FAQ](./goat-flow-faq.md) + +Support: [Support@goat.network](mailto:Support@goat.network) diff --git a/docs/images/01-apply.png b/docs/images/01-apply.png deleted file mode 100644 index 2126214..0000000 Binary files a/docs/images/01-apply.png and /dev/null differ diff --git a/docs/images/02-apply-filled.png b/docs/images/02-apply-filled.png deleted file mode 100644 index 2dbd6a6..0000000 Binary files a/docs/images/02-apply-filled.png and /dev/null differ diff --git a/docs/images/03-invite-code.png b/docs/images/03-invite-code.png deleted file mode 100644 index eaa5191..0000000 Binary files a/docs/images/03-invite-code.png and /dev/null differ diff --git a/docs/images/04-invite-codes-list.png b/docs/images/04-invite-codes-list.png deleted file mode 100644 index 18a1208..0000000 Binary files a/docs/images/04-invite-codes-list.png and /dev/null differ diff --git a/docs/images/05-receiving-addresses-empty.png b/docs/images/05-receiving-addresses-empty.png deleted file mode 100644 index a8c73f5..0000000 Binary files a/docs/images/05-receiving-addresses-empty.png and /dev/null differ diff --git a/docs/images/06-add-receiving-address.png b/docs/images/06-add-receiving-address.png deleted file mode 100644 index c294589..0000000 Binary files a/docs/images/06-add-receiving-address.png and /dev/null differ diff --git a/docs/images/07-receiving-addresses-list.png b/docs/images/07-receiving-addresses-list.png deleted file mode 100644 index 1f4748e..0000000 Binary files a/docs/images/07-receiving-addresses-list.png and /dev/null differ diff --git a/docs/images/08-apply-form-detail.png b/docs/images/08-apply-form-detail.png deleted file mode 100644 index ed7fd4a..0000000 Binary files a/docs/images/08-apply-form-detail.png and /dev/null differ diff --git a/docs/images/09-login.png b/docs/images/09-login.png deleted file mode 100644 index ad924a5..0000000 Binary files a/docs/images/09-login.png and /dev/null differ diff --git a/docs/images/10-dashboard.png b/docs/images/10-dashboard.png deleted file mode 100644 index 7ea57fa..0000000 Binary files a/docs/images/10-dashboard.png and /dev/null differ diff --git a/docs/images/11-orders-empty.png b/docs/images/11-orders-empty.png deleted file mode 100644 index 60e5685..0000000 Binary files a/docs/images/11-orders-empty.png and /dev/null differ diff --git a/docs/images/12-balance-fees.png b/docs/images/12-balance-fees.png deleted file mode 100644 index b262f1f..0000000 Binary files a/docs/images/12-balance-fees.png and /dev/null differ diff --git a/docs/images/13-settings-profile.png b/docs/images/13-settings-profile.png deleted file mode 100644 index 57c2341..0000000 Binary files a/docs/images/13-settings-profile.png and /dev/null differ diff --git a/docs/images/14-developer-apikeys-webhook.png b/docs/images/14-developer-apikeys-webhook.png deleted file mode 100644 index 8b1ff12..0000000 Binary files a/docs/images/14-developer-apikeys-webhook.png and /dev/null differ diff --git a/docs/images/15-developer-webhook-created.png b/docs/images/15-developer-webhook-created.png deleted file mode 100644 index b95426b..0000000 Binary files a/docs/images/15-developer-webhook-created.png and /dev/null differ diff --git a/docs/images/16-team-invite-codes.png b/docs/images/16-team-invite-codes.png deleted file mode 100644 index d2b2fec..0000000 Binary files a/docs/images/16-team-invite-codes.png and /dev/null differ diff --git a/docs/images/17-team-create-invite.png b/docs/images/17-team-create-invite.png deleted file mode 100644 index 10bfcbf..0000000 Binary files a/docs/images/17-team-create-invite.png and /dev/null differ diff --git a/docs/images/18-team-invite-created.png b/docs/images/18-team-invite-created.png deleted file mode 100644 index aaae1d6..0000000 Binary files a/docs/images/18-team-invite-created.png and /dev/null differ diff --git a/docs/images/19-invite-register.png b/docs/images/19-invite-register.png deleted file mode 100644 index 8d017c5..0000000 Binary files a/docs/images/19-invite-register.png and /dev/null differ diff --git a/docs/images/20-member-dashboard.png b/docs/images/20-member-dashboard.png deleted file mode 100644 index 53108a0..0000000 Binary files a/docs/images/20-member-dashboard.png and /dev/null differ diff --git a/docs/images/21-developer-apikeys.png b/docs/images/21-developer-apikeys.png deleted file mode 100644 index 2379a27..0000000 Binary files a/docs/images/21-developer-apikeys.png and /dev/null differ diff --git a/docs/images/22-apikeys-rotated.png b/docs/images/22-apikeys-rotated.png deleted file mode 100644 index 0fbfb6e..0000000 Binary files a/docs/images/22-apikeys-rotated.png and /dev/null differ diff --git a/docs/images/24-order-reconciliation.png b/docs/images/24-order-reconciliation.png new file mode 100644 index 0000000..f4e8951 Binary files /dev/null and b/docs/images/24-order-reconciliation.png differ diff --git a/docs/images/35-order-detail.png b/docs/images/35-order-detail.png new file mode 100644 index 0000000..25578eb Binary files /dev/null and b/docs/images/35-order-detail.png differ diff --git a/docs/images/37-add-address-current.png b/docs/images/37-add-address-current.png new file mode 100644 index 0000000..7517adf Binary files /dev/null and b/docs/images/37-add-address-current.png differ diff --git a/docs/images/40-add-webhook-current.png b/docs/images/40-add-webhook-current.png new file mode 100644 index 0000000..ab88f48 Binary files /dev/null and b/docs/images/40-add-webhook-current.png differ diff --git a/docs/images/42-create-invite-current.png b/docs/images/42-create-invite-current.png new file mode 100644 index 0000000..2226ffb Binary files /dev/null and b/docs/images/42-create-invite-current.png differ diff --git a/docs/images/43-security-current.png b/docs/images/43-security-current.png new file mode 100644 index 0000000..3f1ab2e Binary files /dev/null and b/docs/images/43-security-current.png differ diff --git a/docs/images/44-change-password.png b/docs/images/44-change-password.png new file mode 100644 index 0000000..7dfc8fa Binary files /dev/null and b/docs/images/44-change-password.png differ diff --git a/docs/images/48-new-merchant-fee-balance.png b/docs/images/48-new-merchant-fee-balance.png new file mode 100644 index 0000000..9e5ea52 Binary files /dev/null and b/docs/images/48-new-merchant-fee-balance.png differ diff --git a/docs/images/49-new-merchant-dashboard.png b/docs/images/49-new-merchant-dashboard.png new file mode 100644 index 0000000..b26bb2c Binary files /dev/null and b/docs/images/49-new-merchant-dashboard.png differ diff --git a/docs/images/50-portal-onboarding.png b/docs/images/50-portal-onboarding.png new file mode 100644 index 0000000..46c2343 Binary files /dev/null and b/docs/images/50-portal-onboarding.png differ diff --git a/docs/images/51-onboarding-receiving.png b/docs/images/51-onboarding-receiving.png new file mode 100644 index 0000000..96b2334 Binary files /dev/null and b/docs/images/51-onboarding-receiving.png differ diff --git a/docs/images/52-onboarding-self-test.png b/docs/images/52-onboarding-self-test.png new file mode 100644 index 0000000..8d367dd Binary files /dev/null and b/docs/images/52-onboarding-self-test.png differ diff --git a/docs/images/53-new-merchant-receiving.png b/docs/images/53-new-merchant-receiving.png new file mode 100644 index 0000000..da3af25 Binary files /dev/null and b/docs/images/53-new-merchant-receiving.png differ diff --git a/docs/images/55-new-merchant-quickpay.png b/docs/images/55-new-merchant-quickpay.png new file mode 100644 index 0000000..963f3fb Binary files /dev/null and b/docs/images/55-new-merchant-quickpay.png differ diff --git a/docs/images/57-new-merchant-product.png b/docs/images/57-new-merchant-product.png new file mode 100644 index 0000000..19e97ec Binary files /dev/null and b/docs/images/57-new-merchant-product.png differ diff --git a/docs/images/58-new-merchant-public-checkout.png b/docs/images/58-new-merchant-public-checkout.png new file mode 100644 index 0000000..c7c29ff Binary files /dev/null and b/docs/images/58-new-merchant-public-checkout.png differ diff --git a/docs/images/60-new-merchant-mpp-route.png b/docs/images/60-new-merchant-mpp-route.png new file mode 100644 index 0000000..e95858d Binary files /dev/null and b/docs/images/60-new-merchant-mpp-route.png differ diff --git a/docs/images/61-new-merchant-profile.png b/docs/images/61-new-merchant-profile.png new file mode 100644 index 0000000..81f1906 Binary files /dev/null and b/docs/images/61-new-merchant-profile.png differ diff --git a/docs/images/62-member-dashboard-current.png b/docs/images/62-member-dashboard-current.png new file mode 100644 index 0000000..7400023 Binary files /dev/null and b/docs/images/62-member-dashboard-current.png differ diff --git a/docs/images/63-team-invite-used.png b/docs/images/63-team-invite-used.png new file mode 100644 index 0000000..b5d74b0 Binary files /dev/null and b/docs/images/63-team-invite-used.png differ diff --git a/docs/images/64-new-merchant-orders.png b/docs/images/64-new-merchant-orders.png new file mode 100644 index 0000000..b1d1597 Binary files /dev/null and b/docs/images/64-new-merchant-orders.png differ diff --git a/docs/images/66-new-merchant-topup.png b/docs/images/66-new-merchant-topup.png new file mode 100644 index 0000000..b00bb3d Binary files /dev/null and b/docs/images/66-new-merchant-topup.png differ diff --git a/docs/images/67-use-invite-empty.png b/docs/images/67-use-invite-empty.png new file mode 100644 index 0000000..83089fe Binary files /dev/null and b/docs/images/67-use-invite-empty.png differ diff --git a/docs/images/68-mainnet-register-empty.png b/docs/images/68-mainnet-register-empty.png new file mode 100644 index 0000000..e3fb1d7 Binary files /dev/null and b/docs/images/68-mainnet-register-empty.png differ diff --git a/docs/images/69-mainnet-register-filled.png b/docs/images/69-mainnet-register-filled.png new file mode 100644 index 0000000..89a5d31 Binary files /dev/null and b/docs/images/69-mainnet-register-filled.png differ diff --git a/docs/images/70-mainnet-login.png b/docs/images/70-mainnet-login.png new file mode 100644 index 0000000..19eaa1e Binary files /dev/null and b/docs/images/70-mainnet-login.png differ diff --git a/docs/images/71-mainnet-registration-pending.png b/docs/images/71-mainnet-registration-pending.png new file mode 100644 index 0000000..8a3c3b0 Binary files /dev/null and b/docs/images/71-mainnet-registration-pending.png differ diff --git a/docs/images/goat-x402-payment-flow.png b/docs/images/goat-x402-payment-flow.png deleted file mode 100644 index 21ee320..0000000 --- a/docs/images/goat-x402-payment-flow.png +++ /dev/null @@ -1,95 +0,0 @@ - - - - - - - - - GOAT X402 Payment Flow - - - Client - Merchant Server - GOAT X402 Core / Facilitator - Settlement Chain - - - - - - - - - GET /api/resource - 1 - - - - 402 Payment Required + PAYMENT-REQUIRED header - 2 - - - - 3) Client selects method, - creates/signs payment payload - - - - Retry request + PAYMENT-SIGNATURE - 4 - - - - POST /verify (payload + requirement) - 5 - - - - Verification result - 6 - - - - 7) Server prepares fulfillment - - - - POST /settle - 8 - - - - Submit settlement tx - 9 - - - - Tx confirmed - 10 - - - - Settled response - 11 - - - - 200 OK + resource + PAYMENT-RESPONSE - 12 - - Notes: - • Merchant may choose sync wait or async mode for settlement confirmation. - • Multi-chain behavior depends on GOAT X402 Core config (chain/token/fee/callback). - \ No newline at end of file diff --git a/docs/merchant-guide.md b/docs/merchant-guide.md index dfc141d..a60f5fe 100644 --- a/docs/merchant-guide.md +++ b/docs/merchant-guide.md @@ -1,107 +1,113 @@ -# GOAT Flow Merchant Onboarding Guide +# GOAT Flow Merchant Guide -> This guide walks merchants through registering, configuring, and operating a GOAT Flow payment integration. +Use this guide to register a merchant, configure receiving addresses and +developer access, manage orders and team members, and publish QuickPay products +or paid API routes. --- ## Table of Contents 1. [Overview](#1-overview) -2. [Payment Modes and Supported Chains](#2-payment-modes-and-supported-chains) +2. [Payment Mode and Available Assets](#2-payment-mode-and-available-assets) 3. [Register a Merchant Account](#3-register-a-merchant-account) 4. [Approval, Login, and Account Security](#4-approval-login-and-account-security) 5. [Dashboard Overview](#5-dashboard-overview) -6. [Configure Receiving Addresses and Callback Contracts](#6-configure-receiving-addresses-and-callback-contracts) +6. [Configure Receiving Addresses](#6-configure-receiving-addresses) 7. [Merchant Settings](#7-merchant-settings) 8. [API Keys Management](#8-api-keys-management) 9. [Webhook Configuration](#9-webhook-configuration) 10. [Team Management and Invite Codes](#10-team-management-and-invite-codes) 11. [Order Management](#11-order-management) 12. [QuickPay and Products](#12-quickpay-and-products) -13. [Balance, Fees, and Topup](#13-balance-fees-and-topup) +13. [Balance, Fees, and Top-up](#13-balance-fees-and-top-up) 14. [Audit Logs](#14-audit-logs) --- ## 1. Overview -The GOAT Flow Merchant Portal is your management dashboard for: +The GOAT Flow Merchant Portal is your management dashboard for registering your +merchant identity, configuring receiving addresses, managing the team, keys, and +webhooks, viewing orders and balances, and publishing QuickPay and agent (MPP) +commerce surfaces. -- Registering and managing your merchant identity -- Configuring receiving addresses, callback contracts, and supported chains/tokens -- Managing team members, account security, API keys, and webhook callbacks -- Viewing orders, balances, reconciliation, and audit history -- Publishing QuickPay links, products, and agent-native payment surfaces +### Portal navigation -**Access URLs:** +The portal sidebar is grouped as: -- Merchant Portal: `https://x402-merchant.goat.network` -- Production API: `https://flow-api.goat.network` +| Group | Pages | +| --- | --- | +| **Overview** | Dashboard | +| **Payments** | Orders · Order Reconciliation | +| **Payment Setup** | Receiving Tokens & Addresses · Hosted Checkout (QuickPay) · QuickPay Products · Paid API Routes (MPP) | +| **Billing** | Fee Balance · Fee Top-up | +| **Developer** | Programmatic API & Webhooks | +| **Account & Security** | Profile · Team · Security · Audit Logs | + +Use the service origins for the target environment: + +- Merchant Portal: `https://flow-merchant.goat.network` (Testnet3: + `https://flow-merchant.testnet3.goat.network`) +- Admin Portal (authorized operators only): `https://flow-admin.goat.network` + (Testnet3: `https://flow-admin.testnet3.goat.network`) +- Flow API / standalone MPP Core: `https://flow-api.goat.network` (Testnet3: + `https://flow-api.testnet3.goat.network`) +- QuickPay / Checkout and same-origin public API: `https://flow-quickpay.goat.network` (Testnet3: + `https://flow-quickpay.testnet3.goat.network`) + +The QuickPay client derives public session and MPP paths from the +trusted QuickPay link origin. Use `flow-api` for authenticated merchant APIs +and for standalone MPP only when that Core origin is explicitly configured. + +### Before you begin + +- Keep Mainnet and Testnet3 merchant IDs, credentials, wallets, webhook secrets, + receiving addresses, and configuration separate. +- Use the active portal, manifest, challenge, or API response as the source of + current configuration. +- Never share API secrets, webhook secrets, private keys, TOTP setup data, + recovery codes, active invite codes, or unredacted personal and wallet data. --- -## 2. Payment Modes and Supported Chains - -You choose a receive mode during registration. The receive mode is not a routine merchant self-service setting: changing it later requires admin action and is blocked while receiving addresses or callback contracts are configured. - -### DIRECT Mode +## 2. Payment Mode and Available Assets -User or agent payments go directly to your receiving wallet on the same chain as the order. +The current buyer and server SDKs implement DIRECT transfer workflows in which +the payer sends ERC-20 tokens to the receiving address returned for the order. +The current GOAT Flow MPP profile also uses a direct-transfer and +receipt-verification flow; MPP itself is not limited to this payment method. - Fund flow: User wallet -> Merchant wallet - Mechanism: ERC-20 `transfer` -- Best for: Tips, donations, simple checkout, QuickPay links, agent payments -- Requirements: A receiving address for each chain/token you accept -- Contract callback: Not used -- Payment record: Confirmed orders expose transaction details for audit and reconciliation; verify the transaction on-chain for independent proof +- Best for: Tips, donations, simple checkout, QuickPay links, and agent (MPP) purchases +- Requirements: A receiving address for each supported chain/token pair +- Records: Confirmed orders can expose a server-issued payment record for + operations and reconciliation -**Example:** A content platform has a GOAT mainnet USDC receiving address. A buyer pays USDC on GOAT mainnet to that same-chain merchant address. The watcher matches the transfer to the order; no TSS settlement or callback contract is involved. +**Example:** A content service configures a GOAT Mainnet USDC receiving address. +A buyer sends USDC to that same-chain merchant address, and GOAT Flow observes +and matches the transfer to the order. -### DELEGATE Mode +This guide covers DIRECT, the current public merchant mode. -DELEGATE is a TSS-assisted EVM settlement path for flows that need -payment-triggered contract execution. The merchant configures one callback/ -settlement chain; an eligible buyer source chain may differ. +### Find available chains and tokens -- Fund flow: User payment on an eligible source chain -> TSS-controlled settlement path -> Merchant callback contract / settlement chain -- Mechanism: EIP-3009 or Permit2 `SignatureTransfer`, TSS co-signing, SubmitMonitor submission, and merchant callback -- Best for: In-game purchases, per-call API billing, NFT minting, and other post-payment execution -- Requirements: One EVM callback chain per merchant, receiving token configuration on that chain, and an admin-approved callback contract on the same chain -- Cross-chain checkout: decimal-price Hosted Checkout may offer source-chain/token candidates derived from live TSS and token configuration -- Payment record: Confirmed orders expose transaction details and an unsigned checksum over a subset of the record's fields; verify the transaction on-chain for independent proof +Do not treat a static chain matrix as current availability. +Supported chains, tokens, decimals, contracts, minimums, maximums, and available +transfer and receipt capabilities are deployment- and merchant-specific. -**Example:** A game operates on GOAT mainnet. The player authorizes a USDC payment on GOAT mainnet; Core verifies the order and the TSS-backed settlement path calls the game's approved GOAT callback contract. The player receives the item without any bridge or chain switch. +Use these runtime sources: -### Mode Comparison +- **Portal:** Receiving Tokens & Addresses, QuickPay accepted tokens, and MPP route + selectors. +- **Public integration:** the x402 challenge, QuickPay `manifest.json`, or public + merchant lookup. +- **Authenticated integration:** order and checkout responses returned by the + active API deployment. -| Feature | DIRECT | DELEGATE | -| --- | --- | --- | -| Fund flow | User -> Merchant | Eligible source-chain payment -> TSS settlement -> callback/merchant chain | -| Chain scope | Selected payment/receiving chain | One merchant callback chain; eligible source chain may differ | -| Contract callback | No | Yes, via admin-approved callback contract | -| Gas sponsorship for callback/settlement | No callback path | TSS-submitted settlement/callback path | -| Payment record | Yes, after confirmation | Yes, after confirmation | -| Public QuickPay product/link | Yes | No | -| Server-created Hosted Checkout | Yes | Yes | -| Integration difficulty | Simplest | Requires callback contract review | -| Best for | Simple payments and public payment links | Contract execution after payment | - -### Supported Mainnet Matrix - -| Chain | Chain ID | DIRECT | DELEGATE | Explorer | -| --- | ---: | --- | --- | --- | -| Ethereum | `1` | Yes | Yes | `etherscan.io` | -| Polygon | `137` | Yes | Yes | `polygonscan.com` | -| BSC | `56` | Yes | Yes | `bscscan.com` | -| Arbitrum | `42161` | Yes | Yes | `arbiscan.io` | -| Optimism | `10` | Yes | Yes | `optimistic.etherscan.io` | -| Avalanche | `43114` | Yes | Yes | `snowtrace.io` | -| Base | `8453` | Yes | Yes | `basescan.org` | -| Berachain | `80094` | Yes | Yes | `berascan.com` | -| X Layer | `196` | Yes | Yes | `web3.okx.com/explorer/x-layer/evm` | -| GOAT | `2345` | Yes | Yes | `explorer.goat.network` | -| Metis | `1088` | Yes | No | `andromeda-explorer.metis.io` | -| Tempo | `4217` | Yes | No | `explore.tempo.xyz` | +Testnet3 configuration does not imply that the same chains, token contracts, +limits, fees, or merchant capabilities are enabled on Mainnet. --- @@ -109,27 +115,37 @@ settlement chain; an eligible buyer source chain may differ. ### 3.1 Open the Registration Page -Visit the Merchant Portal and click the **Apply** tab to access the registration form. +Visit the Merchant Portal and select **Apply** to open the registration form. -![Registration Page](./images/01-apply.png) +![Mainnet Registration Page](./images/68-mainnet-register-empty.png) ### 3.2 Fill in Registration Details -![Registration Form](./images/08-apply-form-detail.png) +![Completed Mainnet Registration Form](./images/69-mainnet-register-filled.png) + +The registration form includes: | Field | Description | Format Requirements | | --- | --- | --- | | **Merchant ID** | Unique merchant identifier; cannot be changed after registration | Letters, numbers, hyphens, and underscores only. Reserved IDs and `topup-` / `topup_` prefixes are not available. | -| **Merchant Name** | Display name; can be changed later | Any merchant display text | -| **Receive Type** | Payment mode | Select `DIRECT` or `DELEGATE` after confirming the chain/mode matrix above | -| **Email** | Owner login email | Valid email address | -| **Password** | Owner login password | 8 to 72 characters | +| **Business name** | Display name; can be changed later | Any merchant display text | +| **Work email** | Owner login email | Valid email address | +| **Password** | Owner login password | 8 to 72 characters. Set up 2FA after signing in. | ### 3.3 Submit Application -Click **Submit Application** when done. +Click **Submit application** when done. + +After submission, the portal displays: -After submission, the system displays a pending-approval state. Self-registration creates the merchant and owner user, but does not issue access tokens until an admin approves the merchant. +> Registration submitted. Your account will be available after admin approval. +> No access token or session is issued until then. + +![Mainnet Registration Pending Approval](./images/71-mainnet-registration-pending.png) + +The applicant is not signed in and no access or refresh session is issued until +the application is approved. The GOAT Flow operations team performs the approval +and activates the owner account. --- @@ -137,168 +153,215 @@ After submission, the system displays a pending-approval state. Self-registratio ### 4.1 Admin Approval -After registration, a GOAT Flow administrator reviews the application. Approval enables the merchant; rejection records a reason for the applicant. +After registration, a GOAT Flow administrator reviews the application. Approval +enables the merchant account. QuickPay and MPP are separate capabilities; +inspect **Profile** or the relevant setup page to see whether they are enabled +for the account. ### 4.2 Login -Once approved, open the Merchant Portal and switch to the **Login** tab. +The portal provides **Sign in**, **Apply**, and **Use invite** tabs. -![Login Page](./images/09-login.png) +![Mainnet Login Page](./images/70-mainnet-login.png) -Enter the email and password used during registration. If 2FA is enabled for the user, complete the TOTP or recovery-code challenge before entering the dashboard. +Once approved, enter the work email and password used during registration. If +2FA is enabled, complete the TOTP or recovery-code challenge before entering the +dashboard. -Login intentionally uses generic failures for pending, disabled, locked, or invalid accounts so that attackers cannot infer account state. +If login fails, the generic `invalid email or password` response does not +distinguish an invalid credential from an activation or account-state problem. +If the credentials are correct but access still fails, contact +[Support@goat.network](mailto:Support@goat.network) to confirm the account state +or request a reset. -### 4.3 Password Change and Forced Reset +### 4.3 Change Password -Every authenticated merchant user can change their own password: +Every authenticated owner or member can open **Account & Security → Security** +and select **Change password**. The form requires: -```text -POST /merchant/v1/account/change-password -``` +- current password +- new password +- confirmation of the new password -Request fields: +The new password must be 8 to 72 characters and must differ from the current +password. -| Field | Description | -| --- | --- | -| `current_password` | Current password, or the one-time temporary password from an admin reset | -| `new_password` | New password; 8 to 72 characters, different from the current password, and not equal to the user email | +![Change Password](./images/44-change-password.png) + +Lockout thresholds and administrator reset policies can vary. Contact support if +self-service recovery is unavailable. -When an admin resets a merchant user's password, the account is marked `must_change_password=true`. The user can log in with the temporary password, but the session is confined to `POST /merchant/v1/account/change-password` until a new password is set. Changing the password clears the forced-change flag and bumps `mfa_epoch`, evicting other refresh tokens. +### 4.4 Self-Service 2FA -### 4.4 Lockout Rules +2FA is **per user and not owner-gated**. Enroll, confirm, or disable TOTP on the +**Account & Security → Security** page. The account is password-only until 2FA +is enabled; after that, every sign-in requires a 6-digit authenticator code. -| Surface | Failed attempts | Lock duration | Response behavior | -| --- | ---: | --- | --- | -| Login password | 5 | 15 minutes | Generic `401` | -| Current password check on change-password | 5 | 15 minutes | `400` until locked, then `429` | -| TOTP or recovery-code verification | 5 | 15 minutes | `403` until locked, then `429` | +![Account Security and 2FA](./images/43-security-current.png) -### 4.5 Self-Service 2FA +Store recovery codes securely. They are meant for account recovery if the authenticator is unavailable. -2FA is per user. Use the Settings page to enroll, confirm, or disable TOTP. +Do not publish screenshots of the enrollment QR code, shared secret, or recovery +codes. -| Operation | API path | Notes | -| --- | --- | --- | -| Start enrollment | `POST /merchant/v1/totp/enroll` | Returns the TOTP setup data and QR payload | -| Confirm enrollment | `POST /merchant/v1/totp/confirm` | Enables TOTP and returns one-time recovery codes | -| Disable 2FA | `POST /merchant/v1/totp/disable` | Requires a valid TOTP code or unused recovery code | +### 4.5 Lost Password or Authenticator -Store recovery codes securely. They are meant for account recovery if the authenticator is unavailable. +If you lose your password or 2FA authenticator and +cannot recover with a one-time recovery code, contact the GOAT Flow operations +team at [Support@goat.network](mailto:Support@goat.network). -### 4.6 Lost Password or Authenticator +### 4.6 Sessions -If you lose your password or 2FA authenticator and cannot recover with a one-time recovery code, contact the GOAT Flow platform team to request a reset. A platform administrator can issue a one-time temporary password (which forces a password change on your next login) or reset your 2FA. This is an admin-assisted operation; the administrator procedure lives in the operator runbook (`ONBOARDING.md`). +Portal sessions use short-lived access tokens with refresh sessions. After a +password or 2FA change, sign out other sessions where possible and sign in again. +Do not rely on a fixed session-expiry or invalidation delay. --- ## 5. Dashboard Overview -After login, you'll land on the Dashboard page. +After login, the owner lands on the Dashboard page. + +![New Merchant Dashboard](./images/49-new-merchant-dashboard.png) + +### 5.1 Setup progress + +Until the required DIRECT setup is complete, the Testnet3 Dashboard shows a +setup card with four checks: + +- Account enabled +- DIRECT mode confirmed +- Receiving address configured +- API keys reviewed as an optional step + +Select **Resume setup** to open **Payment Setup → Onboarding**. + +![Post-Approval Onboarding](./images/50-portal-onboarding.png) + +The current DIRECT onboarding flow has four steps: + +1. Confirm the read-only receive mode. +2. Add one or more receiving rows. +3. Optionally create API keys for programmatic DIRECT transfer workflows. +4. Review the optional self-test. + +![Onboarding Receiving Selection](./images/51-onboarding-receiving.png) -![Dashboard](./images/10-dashboard.png) +The self-test is guidance, not an approval gate. A merchant can return to the +Dashboard without broadcasting a payment. -The Dashboard displays: +![Optional Self-Test](./images/52-onboarding-self-test.png) + +### 5.2 Dashboard statistics + +**Dashboard Stats** show revenue and order totals as cards: | Card | Description | | --- | --- | -| **Fee Balance** | Current prepaid fee balance | +| **Total Revenue** | Cumulative order revenue and order count | | **Today** | Today's order count and volume | | **This Week** | This week's order count and volume | | **This Month** | This month's order count and volume | -Below the cards: - -- **Order Statistics** - Total order count -- **Recent Orders** - Latest orders list +**Order Statistics** counts every order by state: Total, Checkout verified, Payment +confirmed, Invoiced, Failed, Expired, Canceled. A **Recent orders** table lists the +latest orders (Order ID, Dapp Order, Flow, Chain, Token, Amount, Status, Created). New merchants show zero counts until orders are created and paid. --- -## 6. Configure Receiving Addresses and Callback Contracts +## 6. Configure Receiving Addresses -Go to the **Settings** page and find the **Receiving Addresses** section. +Open **Payment Setup → Receiving Tokens & Addresses** in the sidebar. + +In DIRECT, the buyer wallet sends tokens to the merchant +wallet address returned for the order on the selected EVM chain. ### 6.1 Add a Receiving Address -Click **Add Address** in the top right to open the form. +Click **Add address** in the top right to open the form. Token options come from +the supported token list, filtered to EVM and already-configured pairs. -![Add Receiving Address](./images/06-add-receiving-address.png) +![Add Receiving Address](./images/37-add-address-current.png) -| Field | Description | +The **DIRECT receiving addresses** table lists one row per accepted pair: + +| Column | Description | | --- | --- | -| **Chain** | Select a supported mainnet chain, such as GOAT (`2345`), Base (`8453`), X Layer (`196`), or Metis (`1088`) | -| **Token** | Select a configured token, such as USDC or USDT | +| **Chain** | One of the EVM chains offered by the active deployment | +| **Symbol** | Token symbol, such as USDC or USDT | +| **Token contract** | The token's ERC-20 contract address | | **Address** | Your EVM receiving address (`0x` + 40 hex characters) | -Important rules: +The form enforces these rules: - Each Chain + Token combination can only have one address. - Address and token contract must be valid EVM addresses. -- Available chains/tokens come from platform configuration. -- For `DELEGATE`, all receiving addresses must be on a single EVM chain. -- For `DELEGATE`, the receiving-address chain must match the callback-contract chain. - -![Receiving Addresses List](./images/07-receiving-addresses-list.png) - -### 6.2 Callback Contracts for DELEGATE +- Available chains/tokens come from deployment configuration. +- The onboarding picker initially selects all available test rows; review the + selection before confirming so that one address is not unintentionally assigned + to every chain and token. -DELEGATE merchants must submit a callback contract for admin review before -callback execution can be used. The contract and merchant receiving-token -configuration remain locked to the same settlement chain. - -Merchant self-service endpoints: - -| Action | API path | Fields | -| --- | --- | --- | -| List active and pending/rejected submissions | `GET /merchant/v1/callback-contracts` | None | -| Submit for review | `POST /merchant/v1/callback-contracts` | `chain_id`, `spent_address`, optional `spent_permit2_func_abi`, optional `spent_erc3009_func_abi`, optional `eip712_name`, optional `eip712_version` | -| Cancel pending submission | `DELETE /merchant/v1/callback-contracts/submissions/:submission_id` | Path parameter | -| Remove active contract | `DELETE /merchant/v1/callback-contracts/:chain_id` | Path parameter; blocked while in-flight orders exist on that chain | +To configure several pairs at once, use the **EVM setup draft** panel: add rows, +then select **Submit**. Each row is submitted independently and reports its own +success or error state; review every row before continuing. -After you submit a callback contract it enters review and becomes active only after a platform administrator approves it. Contact the GOAT Flow team to request review. (The administrator review procedure lives in the operator runbook, `ONBOARDING.md`.) - -The callback contract must be on the same chain as the DELEGATE merchant's receiving addresses. Metis and Tempo are DIRECT-only in the matrix above. +![Receiving Addresses List](./images/53-new-merchant-receiving.png) --- ## 7. Merchant Settings -Go to the **Settings** page to view and edit merchant information. +Open **Account & Security → Profile** to view your merchant identity and edit +business details. Receive type and merchant ID are read-only. + +![Merchant Profile](./images/61-new-merchant-profile.png) -![Settings Page](./images/13-settings-profile.png) +### Identity and capability flags -### Profile Information +The account card shows fixed identity and capability fields: | Field | Editable | Description | | --- | --- | --- | | Merchant ID | No | Set during registration | -| Receive Type | No | `DIRECT` or `DELEGATE`; admin-only change, blocked while addresses/contracts exist | +| Receive type | No | `DIRECT`; fixed at registration | | Status | No | Enabled / Disabled | +| Hosted Checkout (QuickPay) | No | Whether QuickPay is enabled for this merchant | +| MPP enabled | No | Whether Paid API Routes (MPP) are enabled | | Role | No | Owner or Member | -| Name | Yes | Merchant display name | -| Logo URL | Yes | Merchant logo image URL | -Click **Save Changes** after modifications. +### Business details + +| Field | Editable | Description | +| --- | --- | --- | +| Business name | Yes | Shown to buyers on your hosted checkout and on receipts | +| Logo URL | Yes | Optional; must be an `https://` image URL without embedded credentials | + +Click **Save changes** after modifications. -### Limits +### Per-chain payment limits -The Settings and QuickPay pages may display limits configured by the platform, such as maximum frozen amount, pending orders, maximum open QuickPay sessions, or per-merchant daily session caps. These are operational controls and may be admin-configured. +The **Per-chain payment limits** panel is **read-only**. +These are merchant-level per-chain limits set by GOAT Flow risk controls and +shown for reference (for example `max_frozen_amount_usd`, +`max_pending_orders`). --- ## 8. API Keys Management -Go to the **Developer** page to view and manage API keys. +API keys and webhooks share **Developer → Programmatic API & Webhooks**. API keys +are **optional** and are only needed for programmatic DIRECT transfer workflows. +Hosted QuickPay works without API keys. -![Developer Page - API Keys](./images/21-developer-apikeys.png) +New Testnet3 merchants show `API key (not set)` / `API secret (not set)` until an owner +creates keys. The existing API secret is never returned after creation. -### After Rotation +### Create or rotate API keys -After clicking **Rotate API Keys**, the system generates a new API Key and API Secret. The secret is shown only once; save it immediately. - -![API Keys Rotated](./images/22-apikeys-rotated.png) +After selecting **Rotate API Keys**, GOAT Flow generates a new API Key and API +Secret. The secret is shown only once; save it immediately. | Field | Description | | --- | --- | @@ -306,6 +369,10 @@ After clicking **Rotate API Keys**, the system generates a new API Key and API S | **API Secret** | Private HMAC key; shown only once when generated | | **Last Updated** | Last rotation timestamp | +After the one-time display closes, the Developer page still shows the API Key but +only reports that the Secret is configured. Do not include either credential in +screenshots, tickets, or public documentation. + Use API keys only from your backend: ```bash @@ -314,7 +381,7 @@ GOATX402_API_KEY=your_API_Key GOATX402_API_SECRET=your_API_Secret ``` -Server-side calls to `/api/v1/*` are HMAC protected and require: +The TypeScript and Go server SDKs sign authenticated `/api/v1/*` requests with: | Header | Required | | --- | --- | @@ -323,133 +390,284 @@ Server-side calls to `/api/v1/*` are HMAC protected and require: | `X-Nonce` | Yes | | `X-Sign` | Yes | -Do not expose the API Secret in frontend code or public repositories. If you suspect a key leak, rotate immediately; old keys are invalidated. +Do not expose the API Secret in frontend code or public repositories. If you +suspect a key leak, rotate it immediately and confirm the previous credentials +no longer work. --- ## 9. Webhook Configuration -Webhooks notify you when an order reaches the merchant-facing invoiced state. The current webhook event is `order.invoiced`; use order polling or reconciliation views for other status changes. +Webhooks are configured on the same **Programmatic API & Webhooks** page, below +API keys. The current portal allows up to **3 webhooks**. ### 9.1 Add a Webhook -On the Developer page, click **Add Webhook**. +On the Developer page, click **Add webhook**. -![Add Webhook](./images/14-developer-apikeys-webhook.png) +![Add Webhook](./images/40-add-webhook-current.png) | Field | Description | | --- | --- | | **URL** | Your callback URL. Must be HTTPS. Example: `https://your-app.com/api/x402/callback` | -| **Events** | Events to subscribe to. Current merchant-facing event: `order.invoiced` | +| **Events** | Events exposed by the active deployment | -Restrictions: +The form enforces: - URL must be HTTPS. - `localhost` URLs are not allowed. - Maximum 3 webhooks per merchant. -### 9.2 Save the Webhook Secret +The Testnet3 portal currently shows +`order.invoiced`, `quickpay.payment.confirmed`, and +`quickpay.checkout.completed`. Available events can vary by environment and are +not defined by the public SDK packages. + +> **Before relying on a webhook:** confirm the event is emitted in the target +> environment and verify its payload schema, signature input and headers, +> timestamp and replay rules, retry schedule, and delivery source. + +Before enabling fulfillment: + +1. Deploy a publicly reachable HTTPS endpoint with a valid certificate. + `localhost`, private-only addresses, browser cookies, and interactive login + are not valid delivery dependencies. +2. Preserve the raw request bytes until signature verification is complete if the + deployment signs the raw body. +3. Authenticate the request according to the active webhook specification, + durably enqueue it, return a success response promptly, and process it + idempotently. +4. Test a real Testnet3 event through the deployed receiver, including duplicate + delivery and retry handling. +5. Repeat the delivery test on Mainnet before enabling production fulfillment. -After creation, the system displays the **Webhook Secret**. +### 9.2 Save the Webhook Secret -![Webhook Created](./images/15-developer-webhook-created.png) +After creation, the portal displays the **Webhook Secret** once. -The Webhook Secret is shown only once. Save it immediately and use it to verify incoming webhook requests. +The Webhook Secret is shown only once. Save it immediately and use it to verify +incoming webhook requests according to the active signing contract. Never share +or expose the one-time secret. --- ## 10. Team Management and Invite Codes -Merchant Owners can invite others to join the team. Invited members have read-only operational access and can manage their own account security. +Open **Account & Security → Team**. Merchant owners create and revoke +**single-use member invite codes** here. Invited members can access operational +and billing views plus their own Security page, but owner-only setup pages are +hidden. + +> The invitee accepts the code on the public portal's **Use invite** tab, which is +> labeled **Join workspace** — the same merchant, joined as a member. -### 10.1 Role Permission Comparison +### 10.1 Role permissions + +The current portal exposes these role permissions. Confirm role enforcement in +your target environment before relying on it for sensitive operations. | Feature | Owner | Member | | --- | --- | --- | | View Dashboard | Yes | Yes | | View Orders | Yes | Yes | -| View Balance | Yes | Yes | +| View Order Reconciliation | Yes | Yes | +| Access Fee Balance / Fee Top-up | Yes | Yes | | Manage own password and 2FA | Yes | Yes | | Modify profile and receiving settings | Yes | No | -| Manage callback contract submissions | Yes | No | | Manage API keys | Yes | No | | Manage webhooks | Yes | No | | Manage QuickPay and Products | Yes | No | +| Manage MPP routes | Yes | No | | Manage Team / Invite Codes | Yes | No | -| View Audit Logs | Yes | Yes | +| View Audit Logs | Yes | No | ### 10.2 Create an Invite Code -Go to the **Team** page and click **Create Invite Code**. +Go to the **Team** page and click **Create invite**. -![Create Invite Code](./images/17-team-create-invite.png) +![Create Invite Code](./images/42-create-invite-current.png) - Owner-created invite codes create `member` users. - Expiration defaults to 72 hours. +- Expiration may be set up to 720 hours. - Each invite code is single-use. -![Invite Code Created](./images/18-team-invite-created.png) +The full invite code is shown once. Do not publish it while it is active. ### 10.3 Invitee Registration -The invitee opens the Merchant Portal and switches to the **Invite** tab. +The invitee opens the Merchant Portal and uses the **Use invite** tab (Join workspace). -![Invite Registration](./images/19-invite-register.png) +![Invite Registration](./images/67-use-invite-empty.png) Fill in: -- **Invite Code** - The code provided by the Owner +- **Invite code** - The single-use code provided by the workspace owner (starts with `inv_`) - **Email** - The invitee's email - **Password** - A password following the 8 to 72 character rule -Invite-code registration does not require new merchant approval because the merchant is already approved. +The invitee joins as a member. Invite-code registration does not require new merchant approval because the merchant is already approved. + +After registration, the member is signed in to the same merchant workspace. +Direct navigation to an owner-only page redirects the member back to +**Security**. + +![Member Dashboard](./images/62-member-dashboard-current.png) + +The Team table marks a redeemed code as **Used** and records the member email. + +![Used Invite Code](./images/63-team-invite-used.png) --- ## 11. Order Management -Go to the **Orders** page to view all orders. +Open **Orders** to view all orders. -![Orders List](./images/11-orders-empty.png) +![Orders List](./images/64-new-merchant-orders.png) ### Filters | Filter | Options | | --- | --- | | **Status** | All / order statuses | -| **Flow** | All / DIRECT / DELEGATE | +| **Payment method** | All EVM flows / Direct transfer / EIP-3009 / Permit2 | +| **Chain** | All chains / configured deployment chains | +| **From / To** | Full timestamp range | ### Order List Fields | Column | Description | | --- | --- | -| ID | System order ID | +| Order ID | System order ID | | Dapp Order | DApp-side order number | -| Flow | DIRECT or DELEGATE | +| Payment method | Direct transfer, EIP-3009, or Permit2 | +| Type | Order type, such as x402 payment | | Token | Payment token, such as USDC | | Amount | Payment amount | | User | Payer address | | Status | Order status | | Created | Creation time | +| View | Opens the order detail dialog | + +Click **View** to see order and transfer data. The dialog includes +chains, token contract and decimals, receiving address, payer, transaction hash, +expiry, memo, timestamps, confirmation details, and compatibility fields related +to the selected method. A `Payout Tx` field in the portal does not mean GOAT Flow +pays out, releases, or settles customer funds to a DIRECT merchant; the buyer's +tokens move directly to the merchant receiving address. + +![Order Detail](./images/35-order-detail.png) + +The example shows a Testnet3 DIRECT transfer displayed as `INVOICED`, the +successful terminal state commonly visible after Core records the direct +transfer and invoice. + +### 11.1 Order status and fulfillment + +Current SDK order status values include: + +- `CHECKOUT_VERIFIED` +- `PAYMENT_CONFIRMED` +- `INVOICED` +- `FAILED` +- `EXPIRED` +- `CANCELLED` + +The TypeScript `waitForConfirmation()` and Go `WaitForConfirmation` helpers +return on successful `PAYMENT_CONFIRMED` or `INVOICED`, and on `FAILED`, +`EXPIRED`, or `CANCELLED`. Core can advance a DIRECT order from +`PAYMENT_CONFIRMED` to `INVOICED` in one watcher transaction, so a poller may +observe only `INVOICED`. -Click **View** to see order details, including payment and payout transaction data. For confirmed orders, use proof retrieval for audit, reconciliation, and downstream fulfillment evidence. The response's `signature` field is an unsigned checksum over a subset of the payload fields, not a cryptographic attestation; verify its `payload.tx_hash` on-chain when independent proof is required. +A terminal status is only one fulfillment input. Read it through an +authenticated backend call or verified webhook, then verify the merchant and +order identifiers, expected chain, token, amount, receiving address, and +transaction identity. Make fulfillment idempotent so a webhook retry or +repeated status poll cannot ship twice. + +`getOrderProof()` / `GetOrderProof()` returns a server-issued payment record. +Its historical `signature` field is an unsigned Keccak256 digest of +`order_id`, `tx_hash`, `log_index`, `from_addr`, `to_addr`, `amount_wei`, and +`from_chain_id`, concatenated without separators in that order. It does not +cover `status`; verify the transaction hash on-chain when independent proof is +required. + +### 11.2 Order Reconciliation + +**Payments → Order Reconciliation** provides +accounting-oriented reports. Filter by +**Chain** and a **From/To** date range, then **Apply**. Summary cards show Total +Orders, Matched Payments (confirmed), Unpaid Orders (waiting or expired before +payment), and Late Payments (received after expiry). + +![Order Reconciliation](./images/24-order-reconciliation.png) + +Three report tabs — **Matched**, **Unpaid Orders**, and **Late Payments** — each list +rows with Order ID, Dapp Order, From/To Chain, Token, Amount, Status, Payment Tx, +Payout Tx, and Confirmed At. **Export CSV** exports the active tab under the current +filters. Amounts use the token's normal display units; no cross-token totals are shown. + +`Payment Tx` and `Payout Tx` are portal column labels. For DIRECT transfers, +their presence does not imply that GOAT Flow holds or later disburses buyer funds. --- ## 12. QuickPay and Products -QuickPay is the public payment surface for human payers and AI agents. It requires: +QuickPay is the public hosted checkout and agent-commerce surface for human +buyers and AI agents. + +The portal requires: - Merchant is enabled -- Receive type is `DIRECT` - QuickPay is enabled -- At least one payable EVM token is configured with fee configuration - -DELEGATE merchants cannot publish QuickPay sessions. +- At least one eligible receiving token is configured +- The active deployment exposes the required fee and limit configuration ### 12.1 Configure QuickPay -QuickPay must first be enabled for your merchant account by a platform administrator — contact the GOAT Flow team to enable it. Once enabled, you manage the configuration yourself: +QuickPay must be enabled for the merchant. After approval, inspect **Profile** +or **Hosted Checkout (QuickPay)**: + +- If the account state is **Live**, configure it directly. +- If it is unavailable, contact + [Support@goat.network](mailto:Support@goat.network). + +Manage the live configuration under **Payment Setup → Hosted Checkout +(QuickPay)**: + +![Hosted Checkout (QuickPay) Configuration](./images/55-new-merchant-quickpay.png) + +Configure and verify QuickPay in this order: + +1. Confirm **Account state** is live, the merchant is enabled and in `DIRECT` + mode, and the current user is the owner. +2. Confirm every token you intend to expose has a receiving-address row. Compare + the displayed chain ID, token contract, decimals, and min/max amount with the + active environment record. +3. In **Hosted link configuration**, set the buyer-facing Display name, + Description, and optional HTTPS Logo URL. +4. Decide whether to enable hosted custom-amount checkout. If it is enabled, + decide whether a payer memo is required. +5. Set limits for unpaid checkouts per payer, daily sessions, merchant-wide open + sessions, and maximum checkout amount. Start conservatively on Mainnet. +6. Save the configuration and reload the page. Confirm the values persisted and + **Account state** remains live. +7. Copy and open the **Payment page**, **`agent.md`**, and **`manifest.json`** + links in a signed-out browser. They must resolve on the expected environment + origin and must not expose merchant API credentials. +8. Check **Accepted EVM tokens for QuickPay**. Do not launch if a contract, + decimals value, receiving address, or limit differs from the approved + Mainnet configuration. + +QuickPay links are served from a dedicated origin (for example, +`flow-quickpay.goat.network`; Testnet3: +`flow-quickpay.testnet3.goat.network`). Copy the links from the portal instead of +constructing them by hostname substitution. + +The merchant API also exposes these owner-only configuration operations. They +are service endpoints rather than SDK convenience methods: | Action | API path | Notes | | --- | --- | --- | @@ -458,13 +676,17 @@ QuickPay must first be enabled for your merchant account by a platform administr Config fields include `quickpay_enabled`, `display_name`, `description`, `logo_url`, `memo_required`, `max_amount_wei`, `max_open_sessions`, `daily_session_limit`, and `max_open_sessions_merchant_wide`. -The response also distinguishes MPP configuration from live availability: `mpp_enabled` is the persisted toggle gated by whether the MPP runtime is mounted, while `mpp_effective` is true only when the merchant is currently enabled and DIRECT and has at least one eligible route. Use `mpp_effective` for “payable now” UI and automation. - ### 12.2 QuickPay Products -Products are predefined fixed-price items for QuickPay. A product is token-agnostic: it stores one decimal `price`; the buyer or agent chooses a supported chain and token at checkout, and the server computes the on-chain amount as `price * 10^token_decimals`. +QuickPay Products are predefined fixed-price items. A product carries a +token-agnostic decimal `price`; the buyer or agent chooses an +eligible chain and token, and the QuickPay client independently converts the +price using the selected token decimals and refuses to broadcast if the session +amount does not match. -You manage your own products with the merchant endpoints below. (Platform admins have equivalent endpoints for support; see the operator runbook.) +![QuickPay Products](./images/57-new-merchant-product.png) + +The merchant API exposes these product operations: | Action | Merchant API path | | --- | --- | @@ -473,23 +695,55 @@ You manage your own products with the merchant endpoints below. (Platform admins | Update product | `PUT /merchant/v1/quickpay/products/:product_key` | | Delete product | `DELETE /merchant/v1/quickpay/products/:product_key` | -Product fields: +The QuickPay public type and manifest validator cover these published product +fields: | Field | Notes | | --- | --- | -| `product_key` | Immutable identifier; must match `^[A-Za-z0-9._:~-]{1,64}$` | +| `product_key` | Identifier; must match `^[A-Za-z0-9._:~-]{1,64}$` | | `name` | Required display name | | `description` | Optional | | `image_url` | Optional HTTPS URL | | `price` | Required positive decimal, token-agnostic | -| `enabled` | Optional; defaults to true | -| `sort_order` | Optional; defaults to 0 | -Product-bound sessions use `product_key` plus the buyer-selected `chain_id` and `token_contract`. The server pins the authoritative amount and memo (`product:`). +The portal also exposes `enabled` and `sort_order`, +and treats the product key as the update/delete path identifier. Those fields, +defaults, and immutability rules are not exported or validated by the current +QuickPay client, and the public manifest does not currently publish them. + +Product-bound sessions use `product_key` plus the +buyer-selected `chain_id` and `token_contract`. The server response pins the +authoritative amount and memo (`product:`), and the client verifies +the amount before broadcast. -### 12.3 Public Agent Surfaces +The public payment page presents live products and allows custom amount checkout +when that mode is enabled. -Public QuickPay surfaces never expose merchant API secrets. +![Public QuickPay Checkout](./images/58-new-merchant-public-checkout.png) + +For each product: + +1. Choose a stable `product_key`; treat it as an integration identifier, not a + display label. +2. Enter the buyer-facing name, optional description/image, decimal price, + enabled state, and sort order. +3. Save it, then confirm the catalog row is **Enabled** and **Live**. +4. Open the public payment page and verify the name, description, price, and + eligible tokens. +5. Create one small environment-appropriate checkout. Confirm it appears under + **Orders** and **Order Reconciliation** before connecting fulfillment. + +Custom-amount checkout is suitable for donations, tips, and other +buyer-controlled amounts. Use a Product or an authenticated Checkout Session +when the merchant must control the price. Never fulfill solely from a browser +success callback; use a trusted webhook or backend order/session status. + +### 12.3 Public agent and CLI access + +The QuickPay library uses the shared QuickPay +link origin as its trust anchor, derives every endpoint from that origin, requires +HTTPS except for loopback development, validates the manifest, and does not use +merchant API credentials on the buyer side. | Surface | API path | | --- | --- | @@ -504,120 +758,264 @@ For custom amounts, `POST /quickpay/v1/x402/sessions` accepts `merchant_id`, `pa For product sessions, send `product_key` with `merchant_id`, `payer_addr`, `chain_id`, `token_contract`, and optional `idempotency_key`. Browser merchants can open these fixed-price products with -`goatflow-checkout` and no merchant secret in the page. Dynamic DIRECT carts and -all DELEGATE hosted checkout use an HMAC-created Checkout Session instead; see -[Hosted Checkout](x402-checkout.md). +`goatflow-checkout` and no merchant secret in the page. Dynamic DIRECT carts use +an HMAC-created Checkout Session instead; see +[Hosted Checkout](goat-flow-checkout.md). Agent/CLI entry points: ```bash -npx goatflow-quickpay inspect https://flow-api.goat.network/quickpay//agent.md -npx goatflow-quickpay pay-x402 https://flow-api.goat.network/quickpay//agent.md \ - --amount --token-contract --chain -npx goatflow-quickpay pay-product https://flow-api.goat.network/quickpay//agent.md \ - --product --token-contract --chain +npx goatflow-quickpay inspect \ + https://flow-quickpay.goat.network/quickpay//agent.md --json + +npx goatflow-quickpay pay-x402 https://flow-quickpay.goat.network/quickpay//agent.md \ + --amount --token-contract --chain \ + --idempotency-key + +npx goatflow-quickpay pay-product https://flow-quickpay.goat.network/quickpay//agent.md \ + --product --token-contract --chain \ + --idempotency-key + +npx goatflow-quickpay pay-mpp https://flow-quickpay.goat.network/quickpay//agent.md \ + --route GET:api:data ``` -Agents should treat `agent.md` as the canonical skills-style instruction file for QuickPay payments and use `manifest.json` for machine-readable capabilities. +Operational rules: + +- Read the chain, token contract, decimals, limits, products, and MPP routes from + `manifest.json`; do not substitute values from a screenshot. +- Supply the payer key through `QUICKPAY_PRIVATE_KEY` or a permission-restricted + `--wallet-file`. Passing it with `--wallet` can leak through process listings, + shell history, logs, and agent transcripts. +- Reuse one idempotency key for retries of the same QuickPay intent. A reused + session is resumed rather than automatically paid again. +- Do not use `--force` unless you have independently established that no transfer + was broadcast for the reused session. +- Retain the returned session/order ID and transaction hash, then reconcile the + trusted backend status before fulfillment. +- QuickPay session terminal states are `PAYMENT_CONFIRMED`, `EXPIRED`, `FAILED`, + and `CANCELLED`; this is separate from the Server SDK order model, which also + treats `INVOICED` as a successful terminal state. +- Polling is bounded by a hard overall timeout. A known transaction hash is + retained across poll failures, and `EXPIRED` with a known hash receives five + bounded grace polls for a possible late confirmation. Never rebroadcast solely + because status polling or receipt verification failed. + +Agents should use `manifest.json` as the machine-readable capability and pricing +surface and validate every command shown by `agent.md` against the installed +`goatflow-quickpay` package. + +### 12.4 Paid API Routes (MPP) + +**Payment Setup → Paid API Routes (MPP)** +configures **fixed-price protected API endpoints** that agents pay for through +the Machine Payments Protocol (MPP). + +**About MPP.** [MPP](https://mpp.dev/overview) is an independent open +protocol, not a GOAT Flow protocol. This portal configures GOAT Flow's current +MPP integration profile. Its JSON endpoints, direct ERC-20 transfer, and signed +three-segment receipt are GOAT-specific implementation contracts, not the +generic MPP wire format. + +MPP availability and supported chains are runtime configuration. Do not +hardcode the page's descriptive copy or a single chain ID. +Read the active chain selector, supported tokens, receiving rows, and public +manifest. + +Route management requires: + +- the **Owner** role +- an enabled merchant in **DIRECT** mode +- Paid API Routes enabled for the merchant +- a supported chain/token pair +- a matching receiving address + +Configure and operate an MPP route in this order: + +1. Define the protected HTTP method and path in application code first. +2. Convert it to the portal's canonical colon-delimited identifier, for example + `GET /api/docs/protected` → `GET:api:docs:protected`. +3. Add and verify the route's intended receiving chain/token under **Receiving + Tokens & Addresses**. +4. Open **Paid API Routes (MPP)** and confirm the status card says routes are + manageable. +5. Select **Add Route**, enter the canonical route, version, active + network/token, and fixed decimal amount, then save. +6. Confirm the new row is active and shows the expected token contract, + receiving address, and amount. +7. Open the public QuickPay `manifest.json` and confirm the MPP rail contains the + exact route and current pricing version. +8. Run a Testnet3 buyer flow: request the challenge, broadcast exactly the + instructed ERC-20 amount, verify the transaction, and call the protected + endpoint with the returned `Payment-Receipt`. +9. Verify the resource server rejects missing, malformed, expired, + cross-merchant, wrong-route, and replayed receipts according to the intended + receipt policy. + +Each route row carries a paid resource path, version, network/token, payment +token contract address, the receiving address for that token, amount, and status. +A matching receiving address must already exist for the token before a route can +be added. + +Route identifiers are colon-delimited, for example +`GET:api:docs:protected`; slashes and spaces are not accepted by the current +portal form. + +> Testnet3 may show stale helper text that mentions "Tempo only". Use the active +> chain selector and API response to determine available routes. + +![Active MPP Route](./images/60-new-merchant-mpp-route.png) + +The current GOAT Flow MPP adapter: + +1. Treats `POST /mpp/v1/challenge` returning HTTP `402` as a successful payment + instruction. +2. Transfers exactly the challenged token amount to the challenged recipient. +3. Calls `POST /mpp/v1/verify`; HTTP `200` must include a valid + `Payment-Receipt` response header, while `202` and `429` are retried according + to `Retry-After`. +4. Preserves the challenge and transaction hash in recoverable post-broadcast + errors so verification can resume without paying again. + +The merchant resource server must verify the signed receipt against the exact +merchant, route/request canonicalization, expiry, signature, and replay policy. +The current receipt does not carry `route_pricing_version`; Core binds pricing +to the challenge/order, so middleware cannot independently compare that version +without a future receipt extension. For browser use, Core must allow the DApp +origin and expose `Payment-Receipt`, while the protected resource must allow the +same origin and the `Payment-Receipt` request header. Multi-replica deployments +need a shared atomic consumption store when receipts are single-use. + +For the protocol boundary and this profile's buyer/agent flow, see +[GOAT Flow MPP Integration](./mpp.md). --- -## 13. Balance, Fees, and Topup + + +## 13. Balance, Fees, and Top-up -Go to the **Balance** page to view fee-related information. +> **Environment note:** screenshots in this section show Testnet3. Mainnet fee +> amounts, supported chains and assets, limits, and top-up behavior may differ. +> Confirm the live Mainnet **Billing** pages before launch, and do not perform a +> Mainnet top-up without authorization. -![Balance & Fees](./images/12-balance-fees.png) +Open **Billing → Fee Balance** to view service-fee credits; use **Billing → Fee +Top-up** to add service-fee credits. + +![Testnet3 New Merchant Fee Balance](./images/48-new-merchant-fee-balance.png) ### 13.1 Fee Balance -Fee Balance is your prepaid platform fee balance. Creating x402 orders or QuickPay sessions can fail if the fee balance is insufficient. Successful orders consume fees; expired or canceled orders refund reserved fees according to platform rules. +**Fee Balance** is a prepaid service-fee credit ledger, separate from +buyer-to-merchant transfers. It is not a balance of customer funds. +The Testnet3 UI states that new order or session services can stop when the +credit balance is depleted. The active per-chain fee is displayed in **Platform +fee configuration**; do not assume one global price or reuse Testnet3 fees on +Mainnet. -### 13.2 Self-Service Topup +### 13.2 Self-Service Top-up -Use the **Topup** page after approval to create and track fee-balance top-ups. The Topup service creates an x402 order for the requested top-up, tracks the payment, and credits your merchant fee balance through an internal verified callback after the top-up order is invoiced. +The Testnet3 **Fee Top-up** page requires: -Top-ups accept only the service's configured USD-pegged stablecoins because conversion assumes `1 token = 1 USD` and does not use a price oracle. `amount_usd` may have at most six fractional digits and may not be finer than the selected token's decimals. If a symbol maps to multiple token contracts on the selected chain, include `token_contract` to disambiguate it (the portal does this automatically). +- a supported top-up destination chain +- an amount in USD +- a connected EVM wallet +- a supported USDT or USDC top-up asset -| Action | API path | -| --- | --- | -| Create top-up | `POST /api/topup` | -| List top-ups | `GET /api/topup/records` | -| Get one top-up | `GET /api/topup/records/:id` | +The page displays USDT and USDC as 1:1 USD for this Testnet3 top-up flow. +Supported chains, top-up assets, receiving wallets, limits, confirmation policy, +and crediting behavior can differ by environment. + +![Testnet3 Fee Top-up](./images/66-new-merchant-topup.png) + +Top-up history can be filtered by `PENDING`, `COMPLETED`, `EXPIRED`, or `FAILED`. +Confirm the final status and corresponding Fee Balance credit before relying on +a top-up. Do not infer refund or retry behavior from an empty form. ### 13.3 Balance Cards | Card | Description | | --- | --- | -| **Current Balance** | Current fee balance | -| **Total Charged** | Cumulative fees charged | -| **Total Refunded** | Cumulative refunded fees | +| **Current fee balance** | Current prepaid service-fee credit balance | +| **Total charged** | Cumulative fees charged | +| **Total refunded** | Cumulative refunded fees | ### 13.4 Fee Configuration -Fees are chain/admin configured. The default documentation baseline is: - -| Mode | Default baseline | -| --- | ---: | -| DIRECT | `$0.10` per order | -| DELEGATE | `$0.20` per order where supported | +The **Platform fee configuration** table is read-only: merchants can view +configured fee rows but cannot edit pricing. -DELEGATE fees are higher because they include authorization processing, TSS -submission, payout, and callback execution overhead. Check the Balance or fee -configuration view for the active chain-specific values before launch. +Check the Mainnet table or API for active fees before launch. Testnet3 values are +examples only and must not be quoted as Mainnet pricing. ### 13.5 Transaction History +The transaction history table exposes amount, chain ID, order, description, and +date columns: + | Type | Description | | --- | --- | -| `TOPUP` | Fee-balance top-up credit | -| `CHARGE` | Order or session fee deduction | -| `REFUND` | Returned fee reservation | +| **Fee charged** | Order or session fee deduction (e.g. order creation fee) | +| **Fee refunded** | A returned fee recorded by the active deployment | +| **Fee funds added** | Service-fee credit added to Fee Balance (e.g. a top-up) | + +Do not assume that every cancellation or expiry refunds a fee, that every top-up +is reversible, or that fee events are posted at +a particular lifecycle state unless the active Mainnet policy documents that +behavior. --- ## 14. Audit Logs -Go to the **Audit Logs** page to view operation records. +Open **Audit Logs** to view operation records. + +The page is read-only and shows events newest first. The current portal shows 30 +rows per page; filter and export availability may vary. -The system records operations such as: +Typical audit events include: - Profile changes - Receiving address additions and removals -- Callback contract submissions and changes -- Webhook creation, editing, and deletion -- API key rotations -- QuickPay and product changes +- Merchant approval and operator capability changes +- API key rotation +- Webhook configuration changes +- QuickPay configuration and product changes +- MPP route changes - Invite code creation and revocation -- Account security changes -- Login and logout +- Other deployment-defined account actions Each record contains: | Field | Description | | --- | --- | -| Action | Operation type | -| Old Value | Value before change | -| New Value | Value after change | -| Actor | Who performed the action | -| IP | Actor's IP address | | Time | When the action occurred | +| Action | Operation type | +| Description | Human-readable summary | +| Details | Structured action details and changed values | +| IP | Source IP address | + +Audit rows may contain addresses, account identifiers, and source IPs. Do not +share or publish unredacted audit data. --- ## Appendix: Quick Start Checklist -Complete the following steps to start accepting payments: +Complete the following steps to start receiving direct buyer-to-merchant +transfers: -- [ ] 1. Register a merchant account and choose `DIRECT` or `DELEGATE` +- [ ] 1. Register a merchant account - [ ] 2. Wait for admin approval - [ ] 3. Log in and optionally enable 2FA - [ ] 4. Add receiving addresses for each accepted Chain + Token -- [ ] 5. For DELEGATE, submit a callback contract on the merchant settlement chain for review -- [ ] 6. Generate API keys and save the API Secret -- [ ] 7. Configure webhook callbacks -- [ ] 8. Top up the fee balance from the Topup page -- [ ] 9. If using QuickPay, enable QuickPay and optionally create Products -- [ ] 10. Integrate the x402 SDK or QuickPay public agent surfaces +- [ ] 5. Choose the integration path: QuickPay/Products or authenticated programmatic APIs +- [ ] 6. If using programmatic APIs, generate API keys and save the API Secret server-side +- [ ] 7. If using webhooks, configure the callback and save its one-time secret +- [ ] 8. Verify Mainnet fees independently; use Testnet3 top-up only with Testnet3 assets and wallets +- [ ] 9. If using QuickPay, publish the hosted link and optionally create Products or MPP routes +- [ ] 10. Complete a test buyer transfer, confirm it in Orders and Order Reconciliation, and exercise the deployment's fulfillment-state contract ```bash # Install SDKs @@ -631,8 +1029,4 @@ GOATX402_API_SECRET=your_API_Secret --- -> For questions, please contact the GOAT Network team. - ---- - -Contact email: x402support@goat.network +Support: [Support@goat.network](mailto:Support@goat.network) diff --git a/docs/mpp.md b/docs/mpp.md new file mode 100644 index 0000000..d0b585e --- /dev/null +++ b/docs/mpp.md @@ -0,0 +1,473 @@ +# MPP Integration in GOAT Flow + +[Machine Payments Protocol (MPP)](https://mpp.dev/overview) is an independent, +open protocol for machine-to-machine payments, designed by Tempo and Stripe and +described by an open specification proposed to the IETF. MPP is not owned or +defined by GOAT Flow. + +At the protocol level, MPP is payment-method and currency agnostic. Its standard +HTTP exchange is **Challenge -> Credential -> Receipt**: a protected resource +returns an HTTP `402` challenge, the client retries the resource request with a +payment credential, and the resource returns its response with an optional +receipt. See the official [protocol overview](https://mpp.dev/protocol/), +[HTTP transport](https://mpp.dev/protocol/transports/http), and +[receipt specification](https://mpp.dev/protocol/receipts). + +This guide documents the **current GOAT Flow MPP integration profile** +implemented by the GOAT Flow SDK and Core. It follows the challenge, payment +evidence, and receipt lifecycle, but its current wire contract is GOAT-specific: + +- JSON `POST /mpp/v1/challenge` and `POST /mpp/v1/verify` endpoints replace the + standard protected-resource Challenge/Credential retry exchange; +- the buyer wallet submits a direct ERC-20 transfer to the merchant recipient; +- Core returns a signed, three-segment `Payment-Receipt` extension. + +These endpoints, fields, and the signed receipt encoding are GOAT Flow +implementation contracts, not the generic MPP HTTP wire format or Receipt +schema. No interoperability result with the official MPP SDKs is currently +published. Do not describe `MPPClient` or the middleware as official MPP SDKs, +or assume an arbitrary standards-based MPP client or server can interoperate +without an adapter and conformance testing. + +In the current GOAT Flow profile, the buyer transfers tokens **directly to the +merchant's recipient address**. The transfer does not pass through GOAT Flow or +an intermediary contract, and there is no merchant API key on the buyer side. + +> **Note on naming:** MPP means **Machine Payments Protocol**. The Go middleware +> package comment that expands it as "Merchant Payment Protocol" is a stale +> source label, not the protocol name. + +> **Terminology:** some API and SDK identifiers use `settled` or `settlement` +> for an observed transfer event or block. In this integration, those terms mean +> verified on-chain finality. Buyer funds still move directly to the merchant +> recipient; GOAT Flow observes finality and issues the signed receipt. + +--- + +## 1. GOAT Flow profile: actors and authentication + +| Actor | Role | Credentials | +| --- | --- | --- | +| **Buyer / agent** | Requests a challenge, pays on-chain, verifies, then calls the protected route with the receipt | A wallet signer + chain RPC. **No merchant API key.** | +| **GOAT Flow Core** | Implements this profile's challenge and verification endpoints, observes on-chain finality, and issues the GOAT-specific signed receipt | — | +| **Merchant resource server** | Protects its route and verifies the receipt on each request | A receipt-verification key (ed25519 public key or HMAC secret) | + +For this profile, the buyer calls `POST /mpp/v1/challenge` and +`POST /mpp/v1/verify` with only a +`Content-Type: application/json` header — these endpoints are public. The merchant +never shares an API key with buyers; trust is carried by the cryptographically +signed receipt. + +Here, **public** means no merchant HMAC credential is required; it does not +guarantee arbitrary browser CORS access. Browser MPP requires an explicitly +allowed Core origin that permits `POST` and `Content-Type` and exposes +`Payment-Receipt`; otherwise use a server-side buyer client. The merchant +resource must separately allow the DApp origin and the `Payment-Receipt` +request header. + +--- + +## 2. GOAT Flow discovery extension + +> **Merchant side:** merchants publish paid MPP routes in the portal under +> **Payment Setup → Paid API Routes (MPP)**. Supported chains and tokens are +> deployment- and merchant-specific; read the active portal selector and public +> manifest instead of hardcoding a single network. See the +> [Merchant Guide §12.4](./merchant-guide.md#124-paid-api-routes-mpp). + +A buyer using this profile discovers a merchant's paid routes from its trusted +**QuickPay manifest**, loaded from the merchant's public agent surface. This is +a GOAT Flow discovery extension, not the generic MPP discovery document: + +```text +GET https://flow-quickpay.goat.network/quickpay//agent.md +GET https://flow-quickpay.goat.network/quickpay//manifest.json +``` + +The manifest exposes an MPP rail: + +```jsonc +{ + "rails": { + "mpp": { + "enabled": true, + "challenge_endpoint": "…", // deployment metadata + "verify_endpoint": "…", // deployment metadata + "routes": [ + { "route_canonical": "GET:api:data", "...": "..." } + ] + } + } +} +``` + +Rules: + +- Require `rails.mpp.enabled === true`. +- Select an exact `route_canonical` from `rails.mpp.routes` (e.g. `GET:api:data`). +- In QuickPay-driven MPP, **every endpoint is derived from the QuickPay link + origin**, even when endpoint fields are present. Absolute URLs embedded in + the manifest are not trusted, so a + tampered manifest cannot redirect the buyer's transfer instruction to another + host. The origin must be `https://` except for loopback development. +- Standalone `MPPClient` does not require a QuickPay manifest. Its `coreUrl` is + the Core/API origin configured for that deployment. A deployment may serve + standalone Core and QuickPay from different origins. + +--- + +## 3. GOAT Flow challenge endpoint — `POST /mpp/v1/challenge` + +Request body: + +```json +{ + "merchant_id": "acme", + "route_canonical": "GET:api:data", + "request_canonical": "GET:api:data", + "payer_addr": "0xBuyerWallet" +} +``` + +For this endpoint, **success is `HTTP 402 Payment Required`** and the JSON body +is the transfer instruction. This is not the standard MPP +`WWW-Authenticate` challenge representation. Any other status is an error. The +response decodes to: + +| Field | Type | Meaning | +| --- | --- | --- | +| `challenge_id` | string | Opaque challenge identifier | +| `expiry` | number (unix seconds) | Do not broadcast after this time | +| `amount_wei` | string | Exact ERC-20 amount to transfer | +| `chain_id` | number | Chain the transfer must happen on | +| `token_contract` | string | ERC-20 token address | +| `recipient` | string | Address to pay (the merchant's recipient) | +| `mac` | string | Challenge MAC, echoed back on verify | +| `route_pricing_version` | number | Pricing version bound into the challenge | + +The challenge is authoritative for the transfer: use its exact amount, chain, +token contract, recipient, expiry, MAC, and pricing version. The manifest route +is discovery metadata, not permission to reconstruct or override those fields. + +--- + +## 4. GOAT Flow transfer and verification + +### 4.1 Pay — one on-chain transfer + +Transfer **exactly `amount_wei`** of `token_contract` to `recipient` on +`chain_id`. Do **not** wait for local confirmation before verifying — a slow RPC +could push the wait past `expiry`, after which Core rejects the verification +request as `challenge_expired` even though you broadcast in time. Broadcast, +capture the `tx_hash`, and go straight to verify. + +The SDK pre-checks expiry and chain before opening the wallet, so a mismatch fails +fast without a wallet popup (`challenge_expired`, `chain_mismatch`). + +### 4.2 Verify — `POST /mpp/v1/verify` + +```json +{ + "challenge_id": "…", + "tx_hash": "0x…", + "payer_addr": "0xBuyerWallet", + "mac": "…" +} +``` + +Response handling: + +| Status | Meaning | Action | +| --- | --- | --- | +| `200` | Transfer verified; receipt issued | Read the `Payment-Receipt` response header (required). Done. | +| `202` | Tx pending finality | Back off for `Retry-After`, then poll again | +| `429` | Verify rate-limited | Back off for `Retry-After`, then poll again | +| `4xx` (400/401/404/413) | Terminal | Do not retry; the challenge/tx is rejected | +| `5xx` | Transient | Exponential backoff, then retry | + +`Retry-After` is honored but **capped at 30 seconds** so a misbehaving server +cannot stall the client. The SDK's default is **16 verify attempts**, pinned +under Core's per-`(tx_hash, order_id)` budget (18) so the buyer never out-polls +the server. For chains with slow finality (e.g. Ethereum mainnet), operators must +raise Core's `mpp.rate_limit.tx_order_budget` **and** callers must pass an +explicit higher attempt count. + +--- + +## 5. GOAT Flow signed receipt extension + +On `200`, the GOAT Flow verify endpoint returns this `Payment-Receipt` header: + +```text +.. +``` + +This three-segment value is specific to the current GOAT Flow profile. It is not +the generic MPP Receipt encoding described by mpp.dev. Both base64url segments +are unpadded. The final segment is plain ASCII: +`ed25519` or `hmac-sha256`. This is visually JWT-like, but it is not a JWT and +does not contain a separate JOSE header. + +The decoded receipt JSON contains: + +- `receipt_id`, `challenge_id`, `order_id`, `merchant_id`, `payer_addr` +- `chain_id`, `token_contract`, `recipient`, `amount_wei` +- `request_canonical`, `tx_hash`, `log_index`, `block_number` +- `block_timestamp`, `receipt_issued_at`, `receipt_expires_at` + +An alternative JSON envelope is +`{ "receipt": {...}, "signature": "", "algorithm": "ed25519" }`. +Merchant HTTP middleware consumes the header form. Send it to the protected +route: + +```text +GET /api/data +Payment-Receipt: +``` + +The merchant middleware validates the receipt and serves the resource. + +--- + +## 6. GOAT Flow buyer adapter (`goatflow-sdk`) + +`MPPClient` wraps the whole `challenge → transfer → verify` sequence. + +```ts +import { MPPClient, MPPError } from 'goatflow-sdk' +import { ethers } from 'ethers' + +const provider = new ethers.BrowserProvider(window.ethereum) +const signer = await provider.getSigner() + +const mpp = new MPPClient({ + coreUrl: 'https://flow-api.goat.network', // no trailing slash + signer, +}) + +const result = await mpp.pay({ + merchantId: 'acme', + routeCanonical: 'GET:api:data', + onPhase: (phase) => console.log(phase), // requesting_challenge, sending_transaction, verifying, verified, ... +}) + +// Unlock the resource +const res = await fetch('https://acme.example/api/data', { + headers: { 'Payment-Receipt': result.receiptHeader }, +}) +``` + +This example uses the standalone GOAT Flow MPP adapter: `coreUrl` is the +deployment's Core/API origin. +The QuickPay CLI/library instead derives that value from the trusted QuickPay +link origin. In a browser, run this only from an origin allowed by Core CORS; +the default public deployment is not evidence that any DApp origin is allowed. + +Lower-level methods are available when you need to drive the steps yourself: +`requestChallenge(...)`, `payChallenge(challenge)`, and +`verifyChallenge(...)`. `payChallenge()` returns `{ txHash, tx }` immediately; +pass its `txHash` to `verifyChallenge()`. The `tx` value is the ethers +`TransactionResponse` used to observe a matching fee-bump replacement without +blocking verification. + +### Error model + +Every method throws an `MPPError` with a **stable `code`** — branch on the code, +not the message. Common codes: `challenge_expired`, `chain_mismatch`, +`user_rejected`, `payment_failed`, `bad_request`, `verify_timeout`, +`receipt_missing`, `receipt_malformed`, `network_error`. + +An application `onPhase` callback can throw outside parts of the SDK error +wrapper and replace the expected `MPPError`. Keep phase callbacks non-throwing +or catch their errors locally. + +--- + +## 7. Recovery — never pay twice + +Once the transfer is broadcast, it exists on-chain even if verification later +fails (network blip, timeout). **Do not start a new payment on a verify failure** +— that would issue a fresh challenge and pay again. + +When `pay()` fails after broadcast, the thrown `MPPError` carries a `recoverable` +payload — `{ challenge, txHash, payerAddr, tx }`. Resume by calling +`verifyChallenge` with the preserved challenge and tx hash: + +```ts +try { + await mpp.pay({ merchantId, routeCanonical }) +} catch (err) { + if (err instanceof MPPError && err.recoverable) { + const { challenge, txHash, payerAddr } = err.recoverable + const result = await mpp.verifyChallenge({ challenge, txHash, payerAddr }) + // use result.receiptHeader + } else { + throw err + } +} +``` + +There is currently **no resume-verification CLI command**. Recovery is a +library/manual operation — do not re-run `pay-mpp` to recover, as it pays again. + +--- + +## 8. QuickPay CLI (`goatflow-quickpay`) + +For agents and scripts, the QuickPay CLI coordinates this profile's discovery, the +buyer-authorized direct transfer, and receipt verification: + +```bash +# 1. Inspect a merchant's payment capabilities (machine-readable JSON) +npx goatflow-quickpay inspect https://flow-quickpay.goat.network/quickpay/acme/agent.md --json + +# 2. Pay a fixed MPP route +npx goatflow-quickpay pay-mpp https://flow-quickpay.goat.network/quickpay/acme/agent.md \ + --route GET:api:data +``` + +Notes: + +- Provide the payer wallet key via the environment / config, never inline on the + command line (shell history leaks). Configure the chain RPC the same way. +- Every endpoint the CLI calls (session create/status, MPP challenge/verify) is + **derived from the origin**; absolute URLs in the manifest are never trusted. +- Post-broadcast MPP failures preserve recoverable challenge and transaction + context in structured output. There is no resume-verification CLI command, so + do not rerun payment; reconcile the transaction and use + `MPPClient.verifyChallenge()` with the preserved context. +- The on-chain step uses `goatflow-sdk` (ethers v6) as an optional dependency. + +--- + +## 9. GOAT Flow middleware — verify the profile receipt + +Protect a route using this integration profile by verifying its +`Payment-Receipt` header on every request. This middleware does not parse a +generic MPP Credential or generic MPP Receipt. + +### TypeScript (Express) + +The middleware packages are distributed as source modules. +`@goatnetwork/mpp-middleware` is not currently available from npm, and the Go +module path is not currently available from the public Go proxy. Build and +consume the source directories locally; do not present the names as +registry-installable packages. + +For a sibling TypeScript application: + +```bash +cd ../goatx402-mpp-middleware-ts +npm install +npm run build +cd ../your-application +npm install ../goatx402-mpp-middleware-ts express +``` + +```ts +import express from 'express' +import { + expressMiddleware, +} from '@goatnetwork/mpp-middleware/express' + +const app = express() + +app.get( + '/api/data', + expressMiddleware({ + merchantId: 'acme', + routeCanonical: 'GET:api:data', + algorithm: 'ed25519', + ed25519Public: receiptVerificationPublicKey, // 32-byte Uint8Array + store: receiptStore, + }), + (req, res) => { + res.json({ + data: '…', + receiptId: req.mppReceipt?.receipt_id, + }) + }, +) +``` + +Fastify uses the separate subpath: + +```ts +import { + fastifyPlugin, + fastifyPreHandler, +} from '@goatnetwork/mpp-middleware/fastify' +``` + +The package root exports `verifyReceipt`, `InMemoryReceiptIDStore`, +`decodeHeader`, `decodeEnvelope`, `signingBytes`, and receipt/config types. + +### Go + +For a sibling Go application, bind the module path to the local source before +resolving it: + +```bash +go mod edit -require=github.com/goatnetwork/goatflow-mpp-middleware-go@v0.0.0 +go mod edit -replace=github.com/goatnetwork/goatflow-mpp-middleware-go=../x402/goatx402-mpp-middleware-go +``` + +```go +import ( + "crypto/ed25519" + "net/http" + + mppmiddleware "github.com/goatnetwork/goatflow-mpp-middleware-go" + receiptspec "github.com/goatnetwork/goatflow-mpp-middleware-go/receiptspec" +) + +middleware := mppmiddleware.Middleware(mppmiddleware.Config{ + MerchantID: "acme", + RouteCanonical: "GET:api:data", + Algorithm: receiptspec.AlgEd25519, + Ed25519Public: ed25519.PublicKey(receiptVerificationPublicKey), + ReceiptIDStore: receiptStore, +}) + +handler := middleware(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + receipt, ok := mppmiddleware.FromContext(r.Context()) + if !ok { + http.Error(w, "missing verified receipt", http.StatusInternalServerError) + return + } + _, _ = w.Write([]byte(receipt.ReceiptID)) +})) +``` + +After adding the imports, run `go mod tidy`. + +Both middlewares run these checks in order and reject on the **first** failure: + +1. `Payment-Receipt` header present and well-formed. +2. Signature valid under the configured ed25519 public key or HMAC secret. +3. `merchant_id` matches the configured merchant (audience binding — blocks cross-merchant replay). +4. `request_canonical` bound to the configured route, exactly or as a `:<…>` prefix (route binding — blocks cross-resource replay). +5. Receipt not expired (`now < receipt_expires_at`). +6. If a receipt-ID store is configured, `receipt_id` has not been consumed (double-spend defense). + +### Rejection contract + +| Condition | HTTP status | +| --- | --- | +| Missing / malformed / invalid-signature / audience / replay failure | `401` | +| Route mismatch or expired receipt | `402` | +| Receipt-store outage (cannot check double-spend) | `503` | + +**Production note:** running more than one merchant replica requires a **shared, +atomic receipt-ID store** so a receipt cannot be redeemed twice across replicas. + +--- + +## Related + +- [Hosted Checkout](./goat-flow-checkout.md) — browser checkout for DIRECT products and sessions +- [API Reference](./goat-flow-api-reference.md) — HMAC-authenticated merchant API +- [DApp Integration Skill](./goat-flow-dapp-integration/SKILL.md) — coding-agent integration workflow +- [QuickPay payer/agent CLI](../goatx402-quickpay/README.md) +- [TypeScript middleware](../goatx402-mpp-middleware-ts/README.md) +- [Go middleware](../goatx402-mpp-middleware-go/README.md) diff --git a/docs/what-is-goat-flow.md b/docs/what-is-goat-flow.md new file mode 100644 index 0000000..e5b0330 --- /dev/null +++ b/docs/what-is-goat-flow.md @@ -0,0 +1,195 @@ +# What is GOAT Flow? + +GOAT Flow is the GOAT Network implementation of x402 commerce and payment- +verification flows for merchants, applications, and agents. It turns protocol +requirements, buyer-authorized on-chain transfers, verification, and +fulfillment into a consistent integration flow. + +**GOAT Flow** is the product. **x402** is the HTTP payment protocol used by the +order and checkout surfaces. + +--- + +## The Core x402 Flow + +The authenticated order API follows this sequence: + +1. A merchant backend creates an order. +2. The API returns an HTTP 402 payment challenge. +3. The challenge contains one or more payment options in `accepts[]`, including + network, token contract, amount, and `payTo`. +4. The buyer wallet sends the required ERC-20 transfer directly to the merchant + receiving address. +5. The merchant backend polls the order status or receives a deployment-defined + webhook. +6. After confirmation, the merchant can request the server-issued payment + record and independently verify its transaction hash on-chain. + +The server SDK treats HTTP 402 as the expected create-order response and +normalizes the challenge into an `Order` for the browser SDK. + +--- + +## Payment Mode: DIRECT + +`ERC20_DIRECT` sends the selected ERC-20 token to the merchant receiving +address. The browser SDK executes a standard token `transfer` to the challenge's +`payTo` address and waits for the transaction receipt. + +DIRECT is the clearest fit for: + +- checkout and commerce payments +- API and content monetization +- tips and donations +- QuickPay custom amounts and products +- MPP-protected API routes + +## Hosted Checkout + +The Checkout SDK opens the GOAT Flow-hosted checkout in a top-level popup, tab, +or redirect. + +It supports two server-authoritative purchase forms: + +- **QuickPay Product:** the browser supplies `merchant` and `productKey`; the + product manifest supplies the decimal price, while the buyer chooses an + offered chain/token. +- **Checkout Session:** the merchant backend creates an opaque `checkoutId` + with `createCheckoutSession(...)`; the browser opens that ID and never sends + the price. + +A separate custom-amount method exists for tips and donations. Because that +amount comes from the browser, it must not be used as the sole authority for +automatic fulfillment. + +Browser `onSuccess` is a UX signal only. The merchant must confirm payment from +a trusted backend status source or an authenticated deployment-defined webhook. + +--- + +## QuickPay + +QuickPay is the public buyer and agent surface. A trusted QuickPay URL resolves +to a same-origin manifest using schema `goatx402.quickpay.v1`. + +The manifest can advertise: + +- x402 custom-amount availability +- offered chain/token entries and per-token amount bounds +- fixed-price Products identified by `product_key` +- MPP routes with chain, token, and amount metadata + +The `goatflow-quickpay` library and CLI validate the manifest, derive all API +endpoints from the trusted URL origin, and verify fresh session payment terms +before the configured payer backend submits a direct transfer. + +Supported commands are: + +- `inspect` +- `pay-x402` +- `pay-product` +- `pay-mpp` + +--- + +## Machine Payments Protocol + +[Machine Payments Protocol (MPP)](https://mpp.dev/overview) is an independent, +open, payment-method-agnostic protocol; it is not defined by GOAT Flow. GOAT +Flow currently provides an MPP-labeled integration profile for paid resources. +That profile works as follows: + +1. The buyer requests a challenge for a merchant and canonical route. +2. The challenge binds the amount, chain, token, recipient, expiry, and pricing + version. +3. The buyer sends an ERC-20 transfer to the challenge recipient. +4. The buyer submits the transaction hash for verification. +5. A successful verification returns a signed `Payment-Receipt` header. +6. The buyer presents that header to the merchant's protected route. + +The TypeScript and Go middleware verify the receipt signature, merchant +audience, route binding, expiry, and optionally single-use receipt consumption. + +Buyers using this GOAT Flow profile do not use the merchant's API key or secret. +They need a wallet signer and an RPC connection for the route's advertised +chain. The profile's JSON challenge/verify endpoints and three-segment signed +receipt are GOAT-specific extensions, not the generic MPP HTTP wire format. + +--- + +## Chains, Tokens, and Amount Limits + +Chain and token availability is runtime configuration, not a universal static +matrix. + +- The x402 challenge is authoritative for an authenticated order. +- The QuickPay manifest is authoritative for public QuickPay and GOAT Flow + MPP-profile discovery. +- `getMerchant(...)` exposes the merchant's configured receive type and token + entries. + +USDC and USDT appear as examples in the SDK types and tests, but those examples +are not a promise that every deployment or merchant supports both tokens. + +--- + +## Security Boundaries + +The public implementation establishes several clear boundaries: + +- Merchant API keys and secrets stay on the backend. +- The server SDK signs authenticated requests with HMAC-SHA256 and includes a + timestamp and unique `X-Nonce`. +- Hosted Checkout only accepts messages from the configured origin, the exact + popup window, and the matching random channel nonce. +- QuickPay derives endpoints from the trusted link origin instead of trusting + arbitrary manifest URLs. +- Before a fresh QuickPay transfer, the client verifies the session scheme, + chain, token, and amount, and requires a non-empty `payTo` from the trusted + origin before broadcasting. +- GOAT Flow MPP-profile middleware validates its signed receipt extension and + fails closed on invalid, expired, mismatched, replayed, or unverifiable + receipts. + +Registration approval, 2FA, password recovery, fee policy, and webhook signing +are operated-service concerns. Their current procedure must be taken from the +deployed portal and environment documentation, not inferred from SDK types. + +--- + +## Where GOAT Flow Fits + +GOAT Flow is a good fit when a product needs: + +- pay-per-request APIs +- fixed-price or server-priced checkout +- on-chain payment status and proof +- public agent payment discovery +- receipt-protected machine-to-machine APIs + +It is less direct for fiat-only audiences, traditional recurring billing, or +products that do not benefit from on-chain payment. + +--- + +## Getting Started + +1. Complete merchant setup in the deployed portal. +2. Configure receiving addresses and runtime payment capabilities. +3. Keep API credentials on the backend. +4. Choose the appropriate integration surface: + - order API + - QuickPay Product + - Hosted Checkout Session + - custom QuickPay + - MPP +5. Test the exact environment configuration before production fulfillment. + +Continue with: + +- [Developer Quick Start](./goat-flow-developer-quickstart.md) +- [Hosted Checkout](./goat-flow-checkout.md) +- [GOAT Flow MPP Integration](./mpp.md) +- [GOAT Flow FAQ](./goat-flow-faq.md) + +Support: [Support@goat.network](mailto:Support@goat.network) diff --git a/docs/what-is-x402.md b/docs/what-is-x402.md deleted file mode 100644 index a0bcb03..0000000 --- a/docs/what-is-x402.md +++ /dev/null @@ -1,249 +0,0 @@ -# What is x402? - -> An introductory document for merchants, developers, product teams, and operations teams. -> This document explains what x402 is, what problems it solves, which scenarios it fits, and what role it plays within GOAT Network. - ---- - -## One-Line Definition - -**x402 is a crypto payment protocol built around HTTP 402 “Payment Required”, designed to let apps, APIs, content, and agents request, complete, and verify payments natively within an internet request flow.** - -In simple terms: - -> **x402 turns payment into part of the request flow itself, instead of an external checkout process.** - ---- - -## What Problem Does x402 Solve? - -In the traditional internet stack, charging for a service usually depends on: - -- user accounts and registration -- subscription systems -- credit cards or third-party payment gateways -- manual billing systems -- custom reconciliation and access control logic - -In Web3, it may seem easier to use wallets, but practical problems still remain: - -- payment flows are not standardized, so each project builds its own version -- supporting multiple chains, wallets, and tokens creates high complexity -- small payments are often too expensive or too cumbersome to justify on-chain -- after payment, teams still need to build status verification, callbacks, fulfillment, and reconciliation themselves -- there is no broadly adopted standard for pay-per-call APIs, pay-per-use content, or agent-driven payments - -x402 is designed to standardize this entire problem space. - ---- - -## The Core Idea Behind x402 - -The core idea of x402 comes from a long-reserved HTTP status code: - -- **HTTP 402 — Payment Required** - -This status code existed for many years, but was never widely activated in real-world products. - -x402 applies it to programmable money: - -1. A user or agent requests a resource -2. If payment is required, the server returns **HTTP 402** -3. The response contains the payment requirements -4. The user completes payment -5. The system verifies payment and continues service delivery - -That means payment is no longer an external step detached from the service flow — it becomes part of the request protocol itself. - ---- - -## What Does x402 Mean in Practice? - -With x402, many previously awkward payment scenarios become much more natural: - -### 1. Pay-Per-Request APIs -Developers do not always need accounts, card setup, or subscriptions. They can pay directly for a single API request. - -### 2. Pay-Per-Use Content -Users do not always need a monthly subscription. They can pay for a single article, download, or service interaction. - -### 3. AI / Agent Payments -Agents can complete payments automatically during execution and continue calling external services. - -### 4. Payment-Triggered Business Logic -Payment is not just about receiving funds. It can also be connected to downstream actions, such as: - -- unlocking content -- triggering contract logic -- minting NFTs -- executing on-chain actions - ---- - -## How Is x402 Different from Ordinary Payment Methods? - -### Compared with Traditional Payment Gateways -Traditional payments rely on: - -- account systems -- card networks and banking rails -- monthly billing or centralized settlement systems - -x402 is better suited for: - -- on-chain payments -- programmable payments -- micropayments -- API, agent, and Web3-native monetization - -### Compared with a Simple Wallet Transfer -A normal wallet transfer only solves the problem of sending funds. It does not automatically solve: - -- how requests are standardized -- how the server knows what should be paid -- how service delivery is tied to payment success -- how payment is bound to a specific order -- how to support callbacks and proof - -x402 solves a **payment protocol layer**, not just a token transfer. - ---- - -## What Role Does x402 Play in GOAT Network? - -Within GOAT Network, x402 is more than a payment button. It acts as a broader: - -> **payment + settlement + reconciliation layer** - -In the GOAT implementation, x402 helps merchants and developers: - -- support EVM mainnet payments across Ethereum, Polygon, BSC, Arbitrum, Optimism, Avalanche, Base, Berachain, X Layer, GOAT, Metis, and Tempo -- integrate through a unified SDK -- obtain a payment record after payment completes and verify its transaction on-chain -- support callback / execution in more advanced scenarios -- provide more standardized payment capability for merchants, apps, and agents - -This makes x402 well-suited to serve as: - -- a payment capability layer for builders -- a settlement integration layer for merchants -- automated payment infrastructure for agents - ---- - -## The Two Main GOAT Flow Modes - -The current GOAT Flow implementation mainly supports two modes: - -### 1. DIRECT -The user pays directly to the merchant address. - -DIRECT is the payment-only mode. The payer sends an ERC-20 transfer on the selected EVM chain to the merchant's receiving address, and the watcher matches that transfer to the order. DIRECT is available across the supported EVM mainnets, including Metis and Tempo. - -Suitable for: -- simple payments -- paid content -- API monetization -- lightweight checkout scenarios - -### 2. DELEGATE -Payment is not just about receiving funds. It also supports more advanced settlement and post-payment execution logic. - -DELEGATE is the callback-enabled mode. It uses EIP-3009 or Permit2-style -authorization, an approved merchant callback contract, and TSS-assisted -submission. The merchant callback/settlement configuration is locked to one EVM -chain, while eligible Hosted Checkout source payments may come from another -chain. Metis and Tempo are DIRECT-only as merchant settlement modes in the -current matrix. - -Suitable for: -- callback-enabled flows -- NFT minting -- in-game on-chain actions -- agent-driven execution -- any scenario where payment success should trigger business logic automatically - -A simple way to remember it: - -- **DIRECT = get paid** -- **DELEGATE = get paid + execute logic** - ---- - -## Why Is x402 a Good Fit for Agent and AI Scenarios? - -x402 is especially useful in agent-driven workflows because it naturally supports: - -- machine-readable payment flows -- protocol-level coupling between requests and payments -- automated invocation -- standardized post-payment verification - -For agents, this means: - -- they do not need a human to step out of the workflow and pay manually -- they can complete payments during execution -- accessing a paid API can become just another programmable step in a task pipeline - -This is why x402 is often seen as an important piece of infrastructure for the emerging **agent economy**. - ---- - -## Which Scenarios Fit x402 Best? - -x402 is especially well suited for: - -- API monetization -- AI / ML inference payments -- paid content or download access -- in-game purchases -- agent / bot payments -- payment-triggered on-chain execution flows - ---- - -## Which Scenarios Are Less Suitable for x402? - -x402 may not be the best fit for: - -- fixed monthly subscription services -- large one-time payments -- fiat-only audiences with no wallet usage -- ordinary websites with no blockchain or programmable payment relevance - -In those cases, traditional payment systems may be more direct. - ---- - -## How Do You Get Started with x402? - -A typical onboarding path looks like this: - -1. Register a Merchant Account -2. Configure Receiving Address -3. Generate API Credentials -4. Choose an integration path (SDK, direct API integration, or QuickPay for agent-facing payments) -5. Test and go live - -Recommended companion documents: - -- **x402 Onboarding Guide** -- **Merchant Guide** -- **Developer Quick Start** -- **DIRECT vs DELEGATE** -- **QuickPay agent surface:** `GET /quickpay/:merchant_id/agent.md`, `GET /quickpay/:merchant_id/manifest.json`, and the `goatflow-quickpay` CLI -- **x402 FAQ** - ---- - -## One-Line Summary - -**The value of x402 is not just that it enables payment, but that it makes payment part of the internet request and service delivery flow itself.** - -For merchants, it provides a more standardized monetization layer. -For developers, it provides a more integrable payment protocol. -For agents, it provides programmable payment infrastructure. - ---- - -Contact email: x402support@goat.network diff --git a/docs/why-goat-flow.md b/docs/why-goat-flow.md new file mode 100644 index 0000000..285e003 --- /dev/null +++ b/docs/why-goat-flow.md @@ -0,0 +1,200 @@ +# Why GOAT Flow? + +GOAT Flow packages the recurring parts of an on-chain commerce integration into +reusable order, checkout, QuickPay, and MPP-adapter interfaces. Its value is not a +promise to remove every wallet or blockchain concern; it is a narrower and more +useful promise to give applications a consistent way to obtain transfer terms, +have a buyer wallet submit a direct transfer, track status, and verify the +result. + +--- + +## 1. The Integration Problem + +A production payment flow needs more than an ERC-20 `transfer`: + +- the server must define the expected chain, token, recipient, and amount +- the payment must be correlated with an application order +- the frontend must handle wallet interaction and transaction failure +- the backend must distinguish a pending payment from a confirmed one +- fulfillment must use a trusted result, not a browser-only callback +- agent payments need machine-readable discovery and authorization artifacts + +Without a shared contract, every application builds these pieces independently. + +--- + +## 2. What GOAT Flow Provides + +| Surface | Implemented responsibility | +| --- | --- | +| Server SDK | Authenticated orders/sessions, status, cancellation, proof, and merchant lookup | +| Browser SDK | Buyer-authorized ERC-20 transfer submission, balance checks, and GOAT Flow MPP-adapter support | +| Checkout SDK | Hosted checkout, trusted purchase intents, and validated popup messaging | +| QuickPay | Same-origin discovery, custom-amount transfers, Products, recovery, and MPP | +| MPP-profile middleware | GOAT Flow receipt-extension signature, audience, route, expiry, and optional single use | + +This division keeps merchant credentials and price authority on trusted server +surfaces while leaving wallet confirmation with the buyer. + +--- + +## 3. Why DIRECT Is Useful + +For `ERC20_DIRECT`, the challenge's `payTo` address is the merchant receiving +address. The buyer sends the ERC-20 transfer there directly. + +That gives the merchant: + +- an on-chain transfer to its configured address +- an order ID and status lifecycle for application reconciliation +- a server-issued payment-record endpoint after confirmation; its historical + `signature` field is an unsigned digest, so independent proof still requires + on-chain transaction verification +- a common SDK shape across runtime-configured EVM chains and tokens + +DIRECT does not make the transaction gasless. The buyer wallet still broadcasts +and pays gas for the ERC-20 transfer unless a separate wallet-layer mechanism +sponsors it. + +--- + +## 4. Why Hosted Checkout Helps + +Hosted Checkout separates the purchase intent from the buyer-controlled browser. + +For fulfillable purchases: + +- a QuickPay Product carries a merchant-defined decimal price +- a Checkout Session carries a backend-created, server-authoritative price +- the browser URL carries only a `productKey` or opaque `checkoutId`, not an + authoritative amount +- the buyer chooses only from payment options offered by the server + +The Checkout SDK also validates the configured origin and checks the popup +origin, source window, and random nonce before accepting UX messages. + +The boundary is deliberate: `onSuccess` can update the interface, but the +merchant must verify payment on a trusted backend before fulfillment. + +--- + +## 5. Why QuickPay Helps Public Buyers and Agents + +A QuickPay link exposes a machine-readable manifest without giving the buyer +merchant credentials. + +The manifest can describe: + +- available x402 tokens and amount bounds +- whether custom amounts are enabled +- fixed-price Products +- paid MPP routes + +The QuickPay client derives session and MPP endpoints from the trusted URL +origin, rejects malformed manifests, and checks fresh payment terms before the +payer backend submits tokens directly to the instructed recipient. Products are +independently converted from decimal price into the selected token's base units +before the transfer is allowed. + +Explicit idempotency keys support durable session recovery. Reused unpaid +sessions are not rebroadcast by default, which reduces accidental double +payment. + +--- + +## 6. Why MPP Helps Paid APIs + +[Machine Payments Protocol (MPP)](https://mpp.dev/overview) is an independent +open protocol, not a GOAT Flow protocol. Its standard HTTP model uses a +Challenge, Credential, and Receipt and supports multiple payment methods. GOAT +Flow's current adapter uses dedicated JSON challenge/verify endpoints, a direct +ERC-20 transfer, and a signed three-segment receipt extension. + +That current adapter gives an agent a payment artifact that can be presented to +a protected HTTP route. + +The buyer flow is: + +`challenge -> ERC-20 transfer -> verify -> Payment-Receipt` + +The merchant middleware then validates: + +- receipt signature algorithm and signature +- merchant audience +- canonical route binding +- receipt expiry +- optional single-use consumption + +This makes authorization explicit at the protected resource boundary. A +transaction hash alone is not enough; success requires the signed receipt +header. + +The SDK also preserves a recovery handle when verification fails after the +payment has already been broadcast, allowing the caller to retry verification +without sending a second payment. + +--- + +## 7. Choosing the Right Surface + +| Requirement | Use | +| --- | --- | +| Merchant backend creates a payment order | `goatflow-sdk-server` order API | +| Fixed-price public catalog item | QuickPay Product + `goatflow-checkout` | +| Dynamic cart or backend-priced purchase | `createCheckoutSession(...)` + `open({ checkoutId })` | +| Tip or donation with a buyer-entered amount | QuickPay custom amount | +| Agent pays to access an API route | GOAT Flow MPP adapter + profile middleware | + +--- + +## 8. What Must Remain Runtime-Defined + +The public SDKs intentionally do not define a universal commercial or +operational policy. + +Confirm these with the deployed environment: + +- enabled chains and token contracts +- token decimals and amount bounds +- merchant fees and fee-balance behavior +- merchant registration and approval workflow +- password, 2FA, and account-recovery policy +- webhook event names, payloads, signatures, and retry behavior + +Applications should read payment terms from the x402 challenge, public merchant +configuration, or QuickPay manifest rather than copying a static matrix from a +document. + +--- + +## 9. Practical Tradeoffs + +GOAT Flow still depends on: + +- an operated API and checkout service for new orders and status +- chain RPC availability and confirmation time +- a compatible EVM signer and sufficient token/native-gas balances +- merchant-side fulfillment and reconciliation logic +- correct deployment configuration + +It is most valuable where standardized on-chain payment, public agent +discovery, or receipt-protected APIs justify those dependencies. It is less +useful for fiat-only products or conventional recurring billing. + +--- + +## Summary + +GOAT Flow reduces on-chain commerce integration work by giving developers a +consistent contract for transfer requirements, server-authoritative checkout, +public QuickPay discovery, and its current MPP receipt extension. Its strongest +guarantees come from enforced pricing boundaries, explicit status and proof +handling, same-origin discovery, replay-aware session behavior, and fail-closed +receipt verification. + +## Related + +- [Documentation hub](./README.md) +- [What is GOAT Flow](./what-is-goat-flow.md) +- [Integration Guide](./goat-flow-integration.md) diff --git a/docs/why-x402.md b/docs/why-x402.md deleted file mode 100644 index aff16fd..0000000 --- a/docs/why-x402.md +++ /dev/null @@ -1,267 +0,0 @@ -# Why x402? - -## The Value Proposition of HTTP-Native Web3 Payments - -This document explains why x402 matters, what problems it solves for developers and users, how it compares with common alternatives, and where it fits best in product and infrastructure design. - ---- - -## 1. The Problem with Payments Today - -### For Developers - -Every time you want to accept crypto payments, you run into the same set of challenges: - -> “I just want to charge for my API. Why is this so hard?” - -- No standard protocol — every integration becomes custom development -- Multi-chain support means multiplying engineering effort -- Gas estimation, nonce management, and transaction monitoring all fall on the app team -- Wallet UX varies significantly across providers -- There is no strong micropayment infrastructure, so low-value transactions are hard to justify -- If you need callbacks or business logic after payment, you often have to build the settlement layer yourself - -### For Users - -Users often face a broken payment experience for even small purchases: - -> “Why do I need to sign multiple transactions just to buy something worth $2?” - -Typical flow: - -1. Approve token spending (signature + gas) -2. Wait for confirmation -3. Execute payment (signature + gas) -4. Wait for confirmation again -5. Hope the callback or fulfillment logic completes correctly - -That can mean: - -- 2 signatures -- 2 gas payments -- 2+ minutes of waiting -- all for a very small purchase - -### The Numbers - -| Metric | Traditional Web3 Payment Flow | Impact | -| --- | --- | --- | -| Checkout steps | 4–6 steps | 60–80% abandonment | -| Gas overhead | $2–10 per transaction | micropayments become impractical | -| Integration time | 2–4 weeks | slower time to market | -| Chain support | separate work per chain | 3–5x engineering cost | - ---- - -## 2. What x402 Changes - -### HTTP 402 Finally Becomes Useful - -HTTP 402 **Payment Required** has existed in the HTTP specification family for decades. x402 gives it a practical implementation for programmable money. - -Instead of inventing a custom payment pattern for every product, x402 turns payment into a protocol-native flow. - -### What This Means in Practice - -| Dimension | Before x402 | After x402 | -| --- | --- | --- | -| Payment model | subscription or free access | pay per request | -| Integration model | custom per provider | standardized protocol | -| User action | multiple signatures for Web3 payment | simpler signing flow | -| Settlement | manual reconciliation | automated on-chain settlement | -| Callback support | custom-built | protocol-native | - -x402 changes payment from an app-specific patchwork into a reusable infrastructure layer. - ---- - -## 3. x402 vs Common Alternatives - -### x402 vs API Keys - -| Dimension | API Keys | x402 | -| --- | --- | --- | -| Signup friction | high (account, KYC, card) | low (wallet only) | -| Payment granularity | monthly billing | per-request payment | -| Overage handling | complex billing logic | built into the flow | -| Global accessibility | dependent on card networks | permissionless | -| Developer burden | auth + billing system | one payment SDK | - -### x402 vs Subscription Models - -| Dimension | Subscription | x402 | -| --- | --- | --- | -| User commitment | pay upfront | pay as you use | -| Revenue pattern | predictable but rigid | usage-based | -| Churn reason | “I didn’t use it enough” | less relevant because payment maps to use | -| Pricing optimization | manual tier design | usage-driven | -| Free-tier abuse | common problem | much less applicable | - -### x402 vs Wallet Popup Payment UX - -| Dimension | Wallet Popup Flow | x402 | -| --- | --- | --- | -| UX style | interruptive | smoother, more protocol-native | -| Gas cost | user pays every time | can be abstracted | -| Transaction count | often 1–2 per action | can be streamlined | -| Mobile support | often inconsistent | better mobile-native support | -| Programmable callback | usually none | supported | - ---- - -## 4. Developer Experience - -### Integration in 3 Steps - -A typical x402 integration is designed to look like this: - -1. Install the SDK -2. Create an order on the backend -3. Handle payment on the frontend - -The exact code can vary by implementation, but the key point is that the developer interacts with a single payment flow instead of building the full stack manually. - -### What You Don’t Need to Build Yourself - -| Concern | Without x402 | With x402 | -| --- | --- | --- | -| Multi-chain support | build separately for each chain | handled | -| Gas estimation | you build it | handled | -| Transaction monitoring | you build it | handled | -| Nonce management | you build it | handled | -| Callback verification | you build it | handled | -| Balance management | you build it | handled | -| Replay protection | you build it | handled | - -For developers, x402 is valuable not only because it enables payment, but because it removes repeated infrastructure work. - ---- - -## 5. Why Choose This x402 Implementation - -### Architectural Advantages - -This implementation is differentiated by practical builder-focused features rather than just protocol compatibility. - -### Key Differentiators - -| Feature | Description | Why It Matters | -| --- | --- | --- | -| Unified SDK | same integration path across 12 supported EVM mainnets | integrate once and reuse | -| TSS security | threshold signing with no single point of failure | stronger operational security | -| Native callbacks | callback-contract execution bound with EIP-712 on DELEGATE-capable chains | trust-minimized business logic | -| Fee abstraction | predictable USD-denominated pricing | easier budgeting | -| Fast settlement | sub-minute confirmation targets on supported chains | faster fulfillment | - -### Supported EVM Mainnets - -| Chain | Chain ID | DIRECT | DELEGATE | Explorer | -| --- | --- | --- | --- | --- | -| Ethereum | `1` | Yes | Yes | `etherscan.io` | -| Polygon | `137` | Yes | Yes | `polygonscan.com` | -| BSC | `56` | Yes | Yes | `bscscan.com` | -| Arbitrum | `42161` | Yes | Yes | `arbiscan.io` | -| Optimism | `10` | Yes | Yes | `optimistic.etherscan.io` | -| Avalanche | `43114` | Yes | Yes | `snowtrace.io` | -| Base | `8453` | Yes | Yes | `basescan.org` | -| Berachain | `80094` | Yes | Yes | `berascan.com` | -| X Layer | `196` | Yes | Yes | `web3.okx.com/explorer/x-layer/evm` | -| GOAT | `2345` | Yes | Yes | `explorer.goat.network` | -| Metis | `1088` | Yes | No | `andromeda-explorer.metis.io` | -| Tempo | `4217` | Yes | No | `explore.tempo.xyz` | - -DELEGATE requires EIP-3009 or Permit2 plus an approved merchant callback contract -on the merchant's single settlement chain. Metis and Tempo are DIRECT-only as -merchant settlement modes in this matrix; eligible cross-chain payment sources are -derived from live configuration. - -### Protocol Guarantees - -#### Security Guarantees - -- EIP-712 signatures bind payment to exact calldata -- nonce tracking prevents replay attacks -- threshold signing protects key material -- confirmation thresholds reduce reorg risk -- circuit breakers reduce cascading failure risk - -#### Developer Guarantees - -- payment confirmation time depends on the target chain -- callback execution can be atomic with payment handling -- duplicate `dapp_order_id` values are rejected for normal order creation; QuickPay sessions support `idempotency_key` -- expired flows can be refunded automatically -- merchant webhooks currently emit `order.invoiced`; other state changes should be tracked through polling or reconciliation - ---- - -## 6. When to Use x402 - -### Ideal Use Cases - -| Use Case | Why x402 Fits | -| --- | --- | -| API monetization | charge per request instead of forcing subscriptions | -| AI / ML inference | charge per generation or call | -| Data subscriptions | support micropayments for real-time data | -| Gaming items | enable in-context purchases | -| Content access | pay per article or asset without registration friction | -| Bot / agent payments | support programmable machine-driven payments | - -### When x402 Is Not the Best Fit - -| Scenario | Better Alternative | -| --- | --- | -| fixed monthly service | traditional subscriptions | -| large one-time payments | direct wallet transfer | -| fiat-only audience | traditional payment processor | -| no blockchain relevance | Stripe / PayPal | - -x402 works best when programmability, lower friction, and on-chain settlement add real value. - ---- - -## 7. Quick Start Mindset - -### Quick Start Checklist - -- Register in the relevant developer portal -- Get your API key and secret -- Install the SDK -- Create your first order -- Test using a demo application -- Deploy to production - -### Resources - -For ecosystem-related support and coordination, start with: - -- **x402 support email:** `x402support@goat.network` - -Additional product docs, SDK references, and integration guides can be added here as the resource set expands. - -### Support - -For questions about integration, ecosystem alignment, and launch coordination, please contact: - -- **x402 support email:** `x402support@goat.network` - ---- - -## 8. Summary - -### One-Line Summary - -> x402 turns any HTTP endpoint into a pay-per-request service with one SDK and far less payment infrastructure to build. - -### Before vs After - -| Before x402 | After x402 | -| --- | --- | -| Build a payment system | Use an SDK | -| Support each chain separately | Integrate once | -| Manage gas and nonce handling | Abstracted away | -| Build your own callback layer | Protocol-native support | -| Handle edge cases manually | Production-oriented flow | - -The web has had HTTP 402 for a long time. x402 makes it meaningful for Web3. diff --git a/docs/x402-agent-integration-guide.md b/docs/x402-agent-integration-guide.md deleted file mode 100644 index b93d64d..0000000 --- a/docs/x402-agent-integration-guide.md +++ /dev/null @@ -1,422 +0,0 @@ -# x402 DApp Integration Agent Guide - -## 1. What this agent does - -`x402-dapp-integration` is an integration-focused Agent/Skill used to turn an **existing Web DApp** into a DApp that supports **x402 payments**. - -Its goal is not to rebuild the DApp from scratch. Instead, it adds x402 payment gates to paid or protected actions while preserving the existing business logic as much as possible. - -This agent can start from either of the following inputs: - -- **A local project path** -- **A Git repository** - -To reduce risk, the agent should not modify the original project directly. It should first create a safe working copy and perform the integration there. - ---- - -## 2. Core capabilities - -- Analyze an existing DApp from a local path or Git repository -- Avoid direct edits to the original project by working in a safe copied workspace -- Preserve the original business logic and only add x402 payment gates before paid or protected actions -- Keep merchant credentials backend-only -- Never expose `API_SECRET` in the frontend -- Fill in missing backend APIs, frontend payment state machine, local validation, and delivery documentation - ---- - -## 3. Intended use cases - -Use this agent when you need to: - -- Add payment gating to downloads, queries, generation, API calls, minting, submissions, or other protected actions -- Introduce pay-per-use or protected access into an existing SaaS app or DApp -- Validate x402 integration feasibility without refactoring the full product -- Deliver an implementation that can be reviewed, tested locally, and maintained by other developers - -Do not use it to: - -- Build a brand-new DApp from scratch -- Refactor the entire frontend/backend architecture -- Expose merchant secrets to the client -- Re-architect the full product around payments - ---- - -## 4. Security boundaries - -The integration must follow these security rules: - -1. `API_SECRET` must stay on the backend only and must never be exposed to the frontend. -2. Merchant credentials must only be read and used by the backend. -3. Wallet signing must happen in a user-controlled wallet context. -4. Do not make risky direct edits to the original repository; prefer integration in a safe working copy. -5. Preserve existing business logic and only add payment enforcement to explicitly protected actions. -6. Do not call sensitive Merchant APIs directly from the frontend. -7. If using DELEGATE + callback, do not hardcode EIP-712 domain/type on the frontend; use the `calldataSignRequest` returned by the order. - ---- - -## 5. Default deliverables - -`x402-dapp-integration` should normally deliver the following: - -### 5.1 Backend interfaces - -At minimum: - -- Order creation endpoint -- Order status endpoint -- Proof retrieval endpoint or proof lookup logic -- Calldata signature submission endpoint (for DELEGATE + callback flows) -- Order cancellation endpoint or cancellation logic -- Payment confirmation / verification logic -- Access control for protected resources or actions -- Environment variable and merchant configuration loading - -### 5.2 Frontend payment state machine - -At minimum: - -- idle -- payment_required -- wallet_pending -- signing -- submitting -- confirming -- success -- failed -- cancelled -- expired - -### 5.3 Local validation - -At minimum: - -- Local startup instructions -- Environment variable setup guide -- Minimal payment flow validation steps -- Common error and troubleshooting notes -- Order cancellation and expiration validation -- Proof retrieval validation - -### 5.4 Delivery documentation - -Document: - -- Which modules were changed -- Which interfaces/components were added -- How to configure environment variables -- How to run locally -- How to validate and accept the payment flow -- How timeout and failed orders are handled - ---- - -## 6. Recommended base configuration - -Use the following environment variable naming consistently: - -```bash -GOATX402_API_URL=https://flow-api.goat.network -GOATX402_API_KEY=your_api_key -GOATX402_API_SECRET=your_api_secret -GOATX402_MERCHANT_ID=your_merchant_id -``` - -Notes: - -- **Production base URL**: `https://flow-api.goat.network` -- Local Core URL: `http://localhost:8180`; Docker-mapped Core URL: `http://localhost:8286` -- Local demo app URL: `http://localhost:3000` -- If older docs mention `GOATX402_BASE_URL`, migrate to `GOATX402_API_URL` - ---- - -## 7. Standard integration flow - -### Step 1: Identify the project shape - -Identify: - -- Frontend framework (React / Next.js / Vue / others) -- Backend shape (Node / serverless / API routes / separate service) -- Which actions need to be protected or paid - -### Step 2: Copy into a safe working directory - -- Do not modify the original project directly -- Create a safe editable copy -- Perform integration, testing, and delivery in that copy - -### Step 3: Define protected actions - -Clarify: - -- Which buttons, endpoints, pages, or operations require payment first -- How successful payment unlocks the existing business action -- How failure, cancellation, or expiration should be handled - -### Step 4: Implement backend integration - -The backend should: - -- Read merchant credentials -- Call `POST /api/v1/orders` to create x402 orders -- Query order status -- Retrieve proof -- Submit calldata signatures when needed -- Cancel orders when needed -- Verify completed payment -- Protect resource access - -### Step 5: Implement frontend payment state machine - -The frontend should: - -- Trigger the payment flow -- Show payment state to the user -- Guide wallet signing/payment steps -- Handle success, failure, cancellation, and timeout -- If `calldataSignRequest` exists, sign first and send the signature back to the backend -- Execute the actual transfer to the `payTo` value from the x402 challenge -- Resume the original business action after payment succeeds - -### Step 6: Run local validation - -At minimum validate: - -- Happy-path payment flow -- User cancellation flow -- Order expiration flow -- Missing or invalid backend configuration -- Blocking of protected actions before payment -- Proof retrieval -- Order cancellation behavior - -### Step 7: Deliver documentation - -Produce a final handoff document so other developers can maintain and validate the integration. - ---- - -## 8. API semantics that must stay aligned with the API reference - -To keep this guide consistent with the API reference, the integration must explicitly preserve these semantics: - -### 8.1 `HTTP 402` from Create Order is the normal success path - -- When `POST /api/v1/orders` returns `HTTP 402 Payment Required`, the order is usually created successfully -- This is not a normal application error; it is the expected x402 protocol response -- Both frontend and backend must avoid misclassifying it as failure - -### 8.2 Who the user actually pays - -- In all flow types, the user-side payment action is a transfer to the x402 **`payTo`** address -- `DIRECT`: usually the merchant address -- `DELEGATE`: usually the TSS / delegated settlement address - -### 8.3 Common backend endpoint mapping - -| Capability | Endpoint | -| --- | --- | -| Create order | `POST /api/v1/orders` | -| Query status | `GET /api/v1/orders/{order_id}` | -| Retrieve proof | `GET /api/v1/orders/{order_id}/proof` | -| Submit signature | `POST /api/v1/orders/{order_id}/calldata-signature` | -| Cancel order | `POST /api/v1/orders/{order_id}/cancel` | -| Get merchant info | `GET /merchants/{merchant_id}` | - -### 8.4 x402 `Payment Required` response shape - -When the HMAC-protected order API creates a payable order, `HTTP 402 Payment Required` is the expected success path. The response body and the base64 `PAYMENT-REQUIRED` header carry an x402 object with this shape: - -```json -{ - "x402Version": 2, - "resource": { - "url": "https://flow-api.goat.network/api/v1/orders/", - "description": "Payment: ", - "mimeType": "application/json" - }, - "accepts": [ - { - "scheme": "exact", - "network": "eip155:", - "amount": "", - "asset": "0x", - "payTo": "0x", - "maxTimeoutSeconds": 585, - "extra": { - "flow": "ERC20_DIRECT", - "tokenSymbol": "USDC" - } - } - ], - "extensions": { - "goatx402": { - "destinationChain": "eip155:", - "expiresAt": 1730000000, - "paymentMethod": "transfer", - "receiveType": "DIRECT" - } - }, - "order_id": "", - "flow": "ERC20_DIRECT", - "token_symbol": "USDC" -} -``` - -`maxTimeoutSeconds` is the order's remaining lifetime when the challenge is generated (clamped to at least 1 second), not a fixed 600-second window. - -For `ERC20_3009`, `scheme` is `exact-eip3009`. Whenever `calldata_sign_request` is present—including Permit2-style DELEGATE callbacks—submit the wallet signature to the advertised `extensions.goatx402.signatureEndpoint` (`POST /api/v1/orders/{order_id}/calldata-signature`). - -### 8.5 QuickPay agent-native surface - -For agent integrations that should not hold merchant API credentials, prefer QuickPay. QuickPay is a public, credential-less surface for enabled `DIRECT` merchants. - -| Capability | Endpoint | -| --- | --- | -| Public discovery | `GET /quickpay/v1/merchants/{merchant_id}` | -| Prompt-safe agent guide | `GET /quickpay/{merchant_id}/agent.md` | -| Machine-readable manifest | `GET /quickpay/{merchant_id}/manifest.json` | -| Create x402 session | `POST /quickpay/v1/x402/sessions` | -| Poll session status | `GET /quickpay/v1/x402/sessions/{session_id}` | - -QuickPay session creation returns normal `200` JSON with an `x402` object when the session is payable. Custom-amount sessions use `merchant_id`, `payer_addr`, `chain_id`, `token_contract`, `amount_wei`, optional `memo`, and optional `idempotency_key`. Product sessions use `product_key` plus the buyer-selected `chain_id` and `token_contract`; the server computes the token amount from the product's token-agnostic `price`. - -CLI examples: - -```bash -npx goatflow-quickpay inspect https://flow-api.goat.network/quickpay//agent.md -npx goatflow-quickpay pay-x402 https://flow-api.goat.network/quickpay//agent.md \ - --amount --token-contract --chain -npx goatflow-quickpay pay-product https://flow-api.goat.network/quickpay//agent.md \ - --product --token-contract --chain -``` - ---- - -## 9. Payment modes and state handling - -### 9.1 Payment modes - -| Mode | Flow Types | User transfer target | Callback support | -| --- | --- | --- | --- | -| `DIRECT` | `ERC20_DIRECT` | Merchant address | No | -| `DELEGATE` | `ERC20_3009`, `ERC20_APPROVE_XFER` | TSS address on the selected source chain | Yes, on the merchant's approved callback chain | - -DELEGATE uses EIP-3009 or Permit2 plus TSS submission and callback execution. -The merchant has one configured callback/settlement chain; eligible hosted-checkout -source payments may come from another chain. Treat this as a constrained payment -rail, not a general-purpose bridge. - -### 9.2 Supported mainnet matrix - -| Chain | Chain ID | DIRECT | DELEGATE | -| --- | ---: | --- | --- | -| Ethereum | `1` | Yes | Yes | -| Polygon | `137` | Yes | Yes | -| BSC | `56` | Yes | Yes | -| Arbitrum | `42161` | Yes | Yes | -| Optimism | `10` | Yes | Yes | -| Avalanche | `43114` | Yes | Yes | -| Base | `8453` | Yes | Yes | -| Berachain | `80094` | Yes | Yes | -| X Layer | `196` | Yes | Yes | -| GOAT | `2345` | Yes | Yes | -| Metis | `1088` | Yes | No | -| Tempo | `4217` | Yes | No | - -### 9.3 Common order states - -At minimum, the integration should handle these states: - -- `CHECKOUT_VERIFIED` -- `PAYMENT_CONFIRMED` -- `INVOICED` -- `FAILED` -- `EXPIRED` -- `CANCELLED` - -Both the frontend state machine and the backend fulfillment logic should cover them. - ---- - -## 10. Recommendations for order cancellation and proof handling - -### 10.1 Order cancellation - -- In practice, only `CHECKOUT_VERIFIED` orders can usually be cancelled -- If the user closes the payment page, times out, or abandons the flow, the backend should cancel promptly -- Do not leave long-lived unpaid orders hanging - -### 10.2 Proof handling - -- After payment confirmation, the backend should retrieve and persist proof -- Proof is useful for reconciliation, auditability, fulfillment evidence, and dispute handling -- If your DApp triggers downstream fulfillment, store proof in backend records -- The response's `signature` field is only an unsigned Keccak256 checksum over a subset of the payload fields (see the API reference for the exact list); verify `payload.tx_hash` on-chain for independent proof of payment - ---- - -## 11. Recommended repository structure - -Keep both the **unpacked source directory** and the **distribution package**: - -```text -docs/ - skills/ - x402-dapp-integration/ - SKILL.md - scripts/ - references/ - assets/ - x402-dapp-integration.skill -``` - -Notes: - -- **Unpacked directory**: for review, editing, and version control -- **`.skill` package**: for download, import, and distribution - -Do not keep only a zip file in the repository if the unpacked source is missing. - ---- - -## 12. Acceptance criteria - -Use the following as baseline acceptance criteria: - -- The integration can start from either a local path or a Git repository -- The original project is not modified directly -- Protected actions cannot run before payment -- The original business flow resumes after successful payment -- `API_SECRET` is not exposed in the frontend -- Merchant configuration is read correctly by the backend -- `POST /api/v1/orders` and its `402` semantics are handled correctly -- The frontend correctly handles `calldataSignRequest` when applicable -- The backend can query status, retrieve proof, and cancel stale orders -- Local validation steps are complete and reproducible -- Delivery documentation is sufficient for future maintainers - ---- - -## 13. Relationship to the API reference - -This guide describes the **role, boundaries, deliverables, and implementation flow** of the Agent/Skill. - -Related document: - -- `docs/x402-api-reference.md`: API capability, endpoint semantics, and response structure - -The relationship is: - -- **Protocol document**: defines how an x402 Agent interacts with payment capabilities -- **API reference**: defines core endpoints and semantics -- **Integration guide**: defines how to adapt an existing DApp to support x402 payments - ---- - -Contact email: x402support@goat.network diff --git a/docs/x402-api-reference.md b/docs/x402-api-reference.md deleted file mode 100644 index b742246..0000000 --- a/docs/x402-api-reference.md +++ /dev/null @@ -1,670 +0,0 @@ -# GOAT Flow API Reference - -> A practical API overview and implementation reference for developers integrating GOAT Flow. -> This document is intended to explain the core endpoints, authentication model, order lifecycle, and integration boundaries. For field-level details, use it together with the official repository and SDK sources. - ---- - -## 1. What this document is - -This document is a developer-facing x402 API overview and implementation reference. It is intended to help integrators quickly understand the core endpoints, authentication model, order lifecycle, and integration boundaries. - -It works as a docs-site-friendly API entry point for real integration, but it does not replace low-level field-by-field implementation sources. - -### For low-level implementation details and field sources - -Also review: - -- the `GOATNetwork/x402` repository -- `API.md` -- `DEVELOPER_FAST.md` -- `docs/x402-integration.md` -- `goatx402-sdk-server-ts/src/*` -- `goatx402-demo/server/index.ts` - ---- - -## 2. Prerequisites - -Before calling the x402 API, confirm that you already have: - -- a Merchant Account -- merchant receiving capability configured -- an `API Key` and `API Secret` -- test and production environment details confirmed -- sufficient fee balance funded - ---- - -## 3. Base configuration - -Use the following environment variable naming consistently: - -```bash -GOATX402_API_URL=https://flow-api.goat.network -GOATX402_API_KEY=your_api_key -GOATX402_API_SECRET=your_api_secret -GOATX402_MERCHANT_ID=your_merchant_id -``` - -Notes: - -- **Production base URL**: `https://flow-api.goat.network` -- Common local Core URL: `http://localhost:8180` -- Docker-mapped Core URL: `http://localhost:8286` -- Demo app URL: `http://localhost:3000` -- Older docs may mention `GOATX402_BASE_URL`; prefer `GOATX402_API_URL` - ---- - -## 4. Security and authentication requirements - -### 4.1 Backend authentication - -Protected endpoints use **HMAC-SHA256** authentication with these required headers: - -- `X-API-Key` -- `X-Timestamp` -- `X-Nonce` -- `X-Sign` - -### 4.2 Signature algorithm - -The signature process is: - -1. Take the request body/query fields and add `api_key`, `timestamp`, and `nonce` -2. Remove empty values and the `sign` field if present -3. Sort keys in ASCII order -4. Build a string like `k1=v1&k2=v2` -5. Sign with HMAC-SHA256 using the `API Secret` -6. Hex-encode the result and send it as `X-Sign` - -Example signed parameter set: - -```text -amount_wei=10000000 -api_key=merchant_api_key -chain_id=137 -dapp_order_id=order_123 -from_address=0x... -nonce=8b9a7c6d-... -timestamp=1760000000 -token_symbol=USDC -``` - -### 4.3 Important security principles - -- `GOATX402_API_SECRET` must stay on the backend only -- do not call sensitive merchant APIs directly from the frontend -- do not commit API credentials to public repositories -- wallet signing must happen in a user-controlled wallet context - ---- - -## 5. Core x402 payment flow - -The standard integration flow is: - -1. Frontend requests order creation from your backend -2. Backend calls `POST /api/v1/orders` -3. Core returns an x402 payment-required response -4. If `calldataSignRequest` exists, the frontend signs first and the backend submits the signature -5. Frontend performs the actual token transfer to `payToAddress` -6. Backend polls the order status -7. After confirmation, backend retrieves the proof -8. Unused orders should be cancelled while they are still cancellable - -### Key points - -- **`POST /api/v1/orders` returning HTTP 402 does not mean failure** - In the x402 protocol, this is the expected success path for order creation. -- For all flow types, the user-side payment action is still a transfer to **`payToAddress`**. - ---- - -## 6. Payment modes and flow types - -| Mode | Flow Types | User transfer target | Callback support | Typical use | -| --- | --- | --- | --- | --- | -| `DIRECT` | `ERC20_DIRECT` | Merchant address | No | Simple payment gating | -| `DELEGATE` | `ERC20_3009`, `ERC20_APPROVE_XFER` | TSS / delegated settlement address | Yes | Advanced settlement and callback workflows | - -Notes: - -- `DIRECT`: the user pays directly to the merchant address -- `DELEGATE`: the user first pays the source-chain TSS address, then Core settles - to the merchant's configured callback chain (which may be the same chain) -- If the order response contains `calldataSignRequest`, the frontend must sign first and the backend must submit that signature - ---- - -## 7. Supported chains and tokens (docs layer) - -The supported public network matrix is: - -| Chain | Chain ID | DIRECT | DELEGATE | Explorer | -| --- | ---: | :---: | :---: | --- | -| Ethereum | 1 | Yes | Yes | etherscan.io | -| Polygon | 137 | Yes | Yes | polygonscan.com | -| BSC | 56 | Yes | Yes | bscscan.com | -| Arbitrum | 42161 | Yes | Yes | arbiscan.io | -| Optimism | 10 | Yes | Yes | optimistic.etherscan.io | -| Avalanche | 43114 | Yes | Yes | snowtrace.io | -| Base | 8453 | Yes | Yes | basescan.org | -| Berachain | 80094 | Yes | Yes | berascan.com | -| X Layer | 196 | Yes | Yes | web3.okx.com/explorer/x-layer/evm | -| GOAT | 2345 | Yes | Yes | explorer.goat.network | -| Metis | 1088 | Yes | No | andromeda-explorer.metis.io | -| Tempo | 4217 | Yes | No | explore.tempo.xyz | - -> Important: the actual supported chains and tokens always depend on each merchant's Core configuration. Do not hardcode support purely from the static table. - ---- - -## 8. Endpoint summary - -| Server SDK method | Endpoint | Auth | -| --- | --- | --- | -| `createOrder` | `POST /api/v1/orders` | Yes | -| `createCheckoutSession` | `POST /api/v1/checkout/sessions` | Yes | -| `getOrderStatus` | `GET /api/v1/orders/{order_id}` | Yes | -| `getOrderProof` | `GET /api/v1/orders/{order_id}/proof` | Yes | -| `submitCalldataSignature` | `POST /api/v1/orders/{order_id}/calldata-signature` | Yes | -| `cancelOrder` | `POST /api/v1/orders/{order_id}/cancel` | Yes | -| `getMerchant` | `GET /merchants/{merchant_id}` | No | - -QuickPay public endpoints: - -| Surface | Endpoint | Auth | -| --- | --- | --- | -| QuickPay discovery | `GET /quickpay/v1/merchants/{merchant_id}` | No | -| Agent instructions | `GET /quickpay/{merchant_id}/agent.md` | No | -| Machine manifest | `GET /quickpay/{merchant_id}/manifest.json` | No | -| Create x402 session | `POST /quickpay/v1/x402/sessions` | No | -| Query x402 session | `GET /quickpay/v1/x402/sessions/{session_id}` | No | -| Read Hosted Checkout | `GET /checkout/v1/sessions/{checkout_id}` | No, opaque handle | -| Poll Hosted Checkout | `GET /checkout/v1/sessions/{checkout_id}/status` | No, opaque handle | -| Bind Hosted Checkout | `POST /checkout/v1/sessions/{checkout_id}/bind` | No, rate-limited | -| Submit Hosted Checkout signature | `POST /checkout/v1/sessions/{checkout_id}/signature` | No, rate-limited | - -QuickPay client package: - -- npm package / CLI: `goatflow-quickpay` -- CLI commands: `inspect`, `pay-x402`, `pay-product`, `pay-mpp` -- library exports include `QuickPayClient`, `inspect`, `payX402`, `payProduct`, - `payMpp`, `loadManifest`, and `EthersPaymentBackend` - ---- - -## 9. Core API details - -### 9.1 Create Order - -**Endpoint** - -```text -POST /api/v1/orders -``` - -**Purpose** - -Creates a payment order and returns the x402 Payment Required response. - -**Important behavior** - -- A successful order creation commonly returns **HTTP 402 Payment Required** -- This is the expected success path in x402 and should not be treated as a normal failure -- The `PAYMENT-REQUIRED` response header is the base64-encoded JSON form of the same x402 challenge returned in the body - -**Request fields** - -| Field | Type | Required | Description | -| --- | --- | --- | --- | -| `dapp_order_id` | string | Yes | Your app's unique order ID | -| `chain_id` | number | Yes | Payment chain ID | -| `token_symbol` | string | Yes | Payment token, such as `USDC` | -| `token_contract` | string | No | Token contract address | -| `from_address` | string | Yes | Payer address | -| `amount_wei` | string | Yes | Payment amount in atomic units | -| `callback_calldata` | string | No | Used in DELEGATE + callback flows | -| `merchant_id` | string | No | If present, must match the authenticated merchant | - -**Fields builders most often consume after backend normalization** - -- `orderId` -- `payToAddress` -- `amountWei` -- `flow` -- `expiresAt` -- `calldataSignRequest` - -**Key x402 response fields** - -- `x402Version` -- `resource` -- `accepts` -- `extensions.goatx402` -- `order_id` -- `flow` -- `token_symbol` -- `calldata_sign_request` - -**Literal x402 challenge shape** - -```http -HTTP/1.1 402 Payment Required -PAYMENT-REQUIRED: eyJ4NDAyVmVyc2lvbiI6Miwi... -Content-Type: application/json -``` - -```json -{ - "x402Version": 2, - "resource": { - "url": "https://flow-api.goat.network/api/v1/orders/{order_id}", - "description": "Payment: 10000000 USDC", - "mimeType": "application/json" - }, - "accepts": [ - { - "scheme": "exact", - "network": "eip155:137", - "amount": "10000000", - "asset": "0x3c499c542cef5e3811e1192ce70d8cc03d5c3359", - "payTo": "0xMerchantOrTssAddress", - "maxTimeoutSeconds": 585, - "extra": { - "flow": "ERC20_DIRECT", - "tokenSymbol": "USDC" - } - } - ], - "extensions": { - "goatx402": { - "destinationChain": "eip155:137", - "expiresAt": 1760000600, - "paymentMethod": "transfer", - "receiveType": "DIRECT" - } - }, - "order_id": "{order_id}", - "flow": "ERC20_DIRECT", - "token_symbol": "USDC" -} -``` - -`maxTimeoutSeconds` is the order's remaining lifetime when the challenge is generated (clamped to at least 1 second), not a fixed 600-second window. - -For `ERC20_3009`, `accepts[0].scheme` is `exact-eip3009`. Whenever the order carries a `calldata_sign_request`—including Permit2-style DELEGATE callbacks—`extensions.goatx402.signatureEndpoint` points to `POST /api/v1/orders/{order_id}/calldata-signature`. - ---- - -### 9.2 Query Order Status - -**Endpoint** - -```text -GET /api/v1/orders/{order_id} -``` - -**Purpose** - -Retrieves the current order state so the backend can determine whether payment succeeded, failed, expired, or was cancelled. - -**Common response fields** - -- `order_id` -- `merchant_id` -- `dapp_order_id` -- `chain_id` -- `token_contract` -- `token_symbol` -- `from_address` -- `amount_wei` -- `status` -- `tx_hash` -- `confirmed_at` - ---- - -### 9.3 Get Proof - -**Endpoint** - -```text -GET /api/v1/orders/{order_id}/proof -``` - -**Purpose** - -Retrieves the server-issued payment record, useful for: - -- reconciliation -- audit trails -- fulfillment evidence -- dispute handling - -**Common response shape** - -- `payload` - - `order_id` - - `tx_hash` - - `log_index` - - `from_addr` - - `to_addr` - - `amount_wei` - - `from_chain_id` - - `status` -- `signature` - -Despite its legacy field name, `signature` is an unsigned Keccak256 hash of seven payload fields concatenated without separators, in this exact order: `order_id`, `tx_hash`, `log_index`, `from_addr`, `to_addr`, `amount_wei`, `from_chain_id`. It does NOT cover `status` (or anything outside that list), so it is an integrity checksum of those fields only — not of the whole record, and not a cryptographic attestation; independently verify `payload.tx_hash` on-chain before relying on it as proof of payment. - ---- - -### 9.4 Submit Calldata Signature - -**Endpoint** - -```text -POST /api/v1/orders/{order_id}/calldata-signature -``` - -**Purpose** - -Submits the user's EIP-712 signature for `calldataSignRequest`. This is typically required in DELEGATE flows with callback execution. - -**Request field** - -| Field | Type | Required | Description | -| --- | --- | --- | --- | -| `signature` | string | Yes | User signature, typically `0x...` | - ---- - -### 9.5 Cancel Order - -**Endpoint** - -```text -POST /api/v1/orders/{order_id}/cancel -``` - -**Purpose** - -Cancels an order that has not completed payment. - -**Important limitation** - -- In practice, only orders in `CHECKOUT_VERIFIED` can be cancelled -- Cancellation releases reserved resources and refunds the corresponding fee inside Core - -**Best practices** - -- cancel when the user closes the payment page -- add backend cleanup for timed-out payments -- do not leave long-lived unpaid orders hanging - ---- - -### 9.6 Get Merchant - -**Endpoint** - -```text -GET /merchants/{merchant_id} -``` - -**Purpose** - -Returns public merchant information and supported payment capability, useful for configuration pages and backend initialization. - -**Common core response fields** - -- `merchant_id` -- `enabled` -- `receive_type` -- `wallets` -- `api_key` - -Display fields such as `name` and `logo` are not returned by this core endpoint. - -`wallets` typically contains: - -- `address` -- `chain_id` -- `token_symbol` -- `token_contract` - ---- - -### 9.7 QuickPay Public API - -QuickPay is the public, manifest-driven payer and agent surface. These endpoints are unauthenticated and expose only public merchant payment capability. - -**Agent instructions** - -```text -GET /quickpay/{merchant_id}/agent.md -``` - -Returns prompt-injection-hardened Markdown with same-host payment instructions and CLI examples. - -**Machine manifest** - -```text -GET /quickpay/{merchant_id}/manifest.json -``` - -Returns a `goatx402.quickpay.v1` manifest with links, x402 tokens, optional MPP routes, and the x402 session endpoint. - -**Create x402 session** - -```text -POST /quickpay/v1/x402/sessions -``` - -Request fields: - -| Field | Type | Required | Description | -| --- | --- | --- | --- | -| `merchant_id` | string | Yes | Merchant ID | -| `payer_addr` | string | Yes | Payer wallet address | -| `chain_id` | number | Yes | EVM chain ID | -| `token_contract` | string | Yes | ERC-20 token contract | -| `amount_wei` | string | Conditional | Atomic token amount for a custom-amount session | -| `product_key` | string | Conditional | Fixed-price product; server derives amount from product price and token decimals | -| `memo` | string | Conditional | Required only when the merchant requires a memo | -| `idempotency_key` | string | No | Reuse-safe session key | -| `client_reference_id` | string | No | Merchant correlation reference | -| `public_metadata` | object | No | Small non-secret metadata object | - -Supply either `amount_wei` for a custom payment or `product_key` for a -server-priced product. Product mode pins the authoritative decimal price and -ignores any browser-supplied amount. - -Response fields include `session_id`, `merchant_id`, `order_id`, `status`, -`expires_at`, and, when payable, an embedded `x402` challenge with the same -`x402Version: 2` / `accepts[]` shape described above. - -**Query x402 session** - -```text -GET /quickpay/v1/x402/sessions/{session_id} -``` - -Returns public session status fields including `status`, `order_status`, `tx_hash`, `amount_wei`, `chain_id`, `token_contract`, `token_symbol`, `pay_to_address`, and `expires_at`. - ---- - -### 9.8 Unified Hosted Checkout - -Hosted Checkout lets the platform page own wallet connection and payment UX while -the merchant backend pins authoritative terms. - -**Create** - -```text -POST /api/v1/checkout/sessions -``` - -This endpoint uses the same merchant HMAC authentication as order creation. -The API key determines the merchant. - -| Field | Required | Purpose | -| --- | --- | --- | -| `checkout_type` | Yes | `DIRECT` or `DELEGATE` | -| `price` | Conditional | DIRECT decimal price or cross-chain DELEGATE decimal price | -| `chain_id` | Conditional | Legacy fixed-wei DELEGATE source chain | -| `fixed_amount_wei` | Conditional | Legacy fixed-wei DELEGATE atomic amount | -| `acceptable_tokens` | Conditional | JSON-stringified token-contract array for legacy DELEGATE | -| `callback_calldata` | No | Optional legacy DELEGATE calldata | -| `client_reference_id` | No | Merchant correlation key, maximum 200 characters | -| `success_url` / `cancel_url` | No | Redirects checked against the merchant allowlist | -| `line_items_json` | No | JSON-stringified display rows | -| `public_metadata_json` | No | JSON-stringified public metadata | -| `private_metadata_json` | No | JSON-stringified metadata excluded from public reads | -| `expires_in` | No | Lifetime in seconds | - -DELEGATE accepts one of two amount forms: - -- `price`: cross-chain decimal-price mode. Core derives the merchant callback - chain and eligible source-chain/token candidates. -- `fixed_amount_wei` + `chain_id` + `acceptable_tokens`: compatibility - single-chain mode. - -Use `createCheckoutSession` from the TypeScript or Go server SDK so nested values -are encoded consistently with HMAC signing. - -Response: - -```json -{ - "checkout_id": "cs_...", - "checkout_type": "DIRECT", - "url": "https://pay.goat.network/checkout?cs=cs_...", - "expires_at": 1780000000 -} -``` - -Pass only `checkout_id` to -`GoatCheckout({ origin }).open({ checkoutId })`, or redirect to the returned -platform URL. - -**Hosted-page endpoints** - -```text -GET /checkout/v1/sessions/{checkout_id} -GET /checkout/v1/sessions/{checkout_id}/status -POST /checkout/v1/sessions/{checkout_id}/bind -POST /checkout/v1/sessions/{checkout_id}/signature -``` - -Merchant applications normally do not orchestrate these public calls; the hosted -checkout page does. The public read excludes private metadata. Bind creates the -real order, while the signature endpoint verifies/stores a DELEGATE EIP-712 -callback signature when required. - -Fulfill only from `quickpay.checkout.completed` or trusted backend status. Browser -`onSuccess` is not proof of payment. - -See [Hosted Checkout](x402-checkout.md). - ---- - -## 10. Order states - -Common order states are: - -- `CHECKOUT_VERIFIED` — order created, waiting for payment -- `PAYMENT_CONFIRMED` — payment observed and confirmed -- `INVOICED` — completed in the current flow -- `FAILED` — failed -- `EXPIRED` — expired before completion -- `CANCELLED` — cancelled while still cancellable - ---- - -## 11. Common errors and HTTP statuses - -| HTTP Status | Meaning | -| --- | --- | -| `200` | Success | -| `400` | Validation failure or business rule error | -| `401` | Authentication failure | -| `402` | **Payment Required: normally the success path for order creation** | -| `403` | Authorization error | -| `404` | Resource not found | -| `500` | Internal server error | - -### Common business errors - -- `insufficient fee balance` -- `invalid signature` -- `wrong chain / token / amount` -- `callback failed` -- `merchant not found` -- `token not supported on chain ` -- `cannot cancel order in status , only CHECKOUT_VERIFIED orders can be cancelled` - ---- - -## 12. Additional notes for DELEGATE / callback flows - -When using DELEGATE mode and post-payment on-chain execution is required, also pay attention to: - -- `callback_calldata` -- `calldataSignRequest` -- MerchantCallback contract setup -- callback caller allowlist -- EIP-712 domain config such as `eip712_name` and `eip712_version` - -The official docs and engineering implementation emphasize: - -- do not hardcode EIP-712 domain/type on the frontend -- use the `calldataSignRequest` returned in the order response -- only allow the authorized x402 Core caller to invoke the callback entrypoint - ---- - -## 13. Integration recommendations from the official page and engineering implementation - -Based on the official x402 page and local engineering files, the recommended sequence is: - -1. complete Merchant / receiving / fee setup -2. integrate the server SDK on the backend -3. integrate wallet + payment SDK on the frontend -4. create the order -5. if callback is used, sign and submit the signature first -6. execute the frontend payment -7. poll order status on the backend -8. retrieve proof -9. cancel stale or abandoned orders in time - ---- - -## 14. Related engineering docs and repositories - -Recommended references: - -- `GOATNetwork/x402` repository -- `README.md` -- `DEVELOPER_FAST.md` -- `API.md` -- `goatx402-demo/README.md` -- `goatx402-demo/server/index.ts` -- `docs/x402-integration.md` -- `goatx402-contract/README.md` -- `goatx402-contract/QUICK_START.md` -- `goatx402-contract/MERCHANT_CALLBACK.md` - ---- - -## 15. One-line summary - -The x402 API is not just “an order creation endpoint.” The real integration path is: - -**create order → return Payment Required → user pays `payToAddress` → poll status → retrieve proof → optionally callback / cancel** - ---- - -Contact email: x402support@goat.network diff --git a/docs/x402-checkout.md b/docs/x402-checkout.md index c58da7d..d7cf44d 100644 --- a/docs/x402-checkout.md +++ b/docs/x402-checkout.md @@ -1,295 +1,6 @@ -# GOAT Flow Hosted Checkout +# Hosted Checkout -Hosted Checkout is the recommended browser integration when a merchant does not -want to build wallet connection, payment UI, session polling, and settlement UX -inside its own application. +This path is retained for compatibility with previously published package +documentation. -The browser package is `goatflow-checkout`. It opens a platform-hosted, top-level -checkout page; the server packages create authenticated Checkout Sessions. - -## Choose the right path - -| Use case | Recommended path | Merchant backend required | -| --- | --- | --- | -| Fixed DIRECT catalog item | QuickPay product + `open({ merchant, productKey })` | No | -| Dynamic DIRECT cart/amount | Unified Checkout Session + `open({ checkoutId })` | Yes | -| DELEGATE checkout | Unified Checkout Session + `open({ checkoutId })` | Yes | -| Donation or buyer-entered amount | `openCustom({ merchant, amount })` | No, but server-side reconciliation is required | -| Fully custom wallet/order UI | `goatflow-sdk` + `goatflow-sdk-server` | Yes | - -Do not use `openCustom` for automatic fulfillment. Its amount originates in the -browser and is not a merchant-authoritative price. - -## Install - -```bash -npm install goatflow-checkout - -# Backend, when creating Checkout Sessions: -npm install goatflow-sdk-server -``` - -The checkout package is framework-free. It can also be delivered as -`https://pay.goat.network/sdk/checkout.js` and used through the global -`GoatCheckout` function. - -## Fixed DIRECT product, no merchant backend - -The merchant first configures a QuickPay product. The merchant page passes only the -merchant ID and product key: - -```ts -import { GoatCheckout } from 'goatflow-checkout' - -const goat = GoatCheckout({ origin: 'https://pay.goat.network' }) - -payButton.addEventListener('click', () => { - goat.open({ - merchant: 'merchant_123', - productKey: 'mug', - display: 'popup', - clientReferenceId: 'cart_9f31', - onSuccess: (result) => { - // Update the UI only. Do not fulfill from this callback. - console.log(result.status, result.tx_hash) - }, - onCancel: () => {}, - onError: (reason) => console.error(reason), - }) -}) -``` - -The hosted page resolves the product's server-side decimal price and the buyer -chooses an eligible chain/token. The browser never supplies the product amount. - -## Create a unified Checkout Session - -`POST /api/v1/checkout/sessions` is HMAC-authenticated. The server SDK signs it -with the merchant API secret and maps the response to: - -```ts -type CheckoutSession = { - checkoutId: string - checkoutType: string - url: string - expiresAt: number -} -``` - -The merchant is derived from the authenticated API key, not accepted from the -request body. - -### TypeScript: dynamic DIRECT checkout - -```ts -import { GoatFlowClient } from 'goatflow-sdk-server' - -const client = new GoatFlowClient({ - baseUrl: process.env.GOATX402_API_URL!, - apiKey: process.env.GOATX402_API_KEY!, - apiSecret: process.env.GOATX402_API_SECRET!, -}) - -const session = await client.createCheckoutSession({ - checkoutType: 'DIRECT', - price: '19.95', - clientReferenceId: 'cart_9f31', - lineItems: [ - { name: 'Coffee mug', quantity: 1, unit_price: '19.95' }, - ], - publicMetadata: { campaign: 'summer' }, - privateMetadata: { internal_customer_id: 'cus_42' }, - successUrl: 'https://merchant.example/pay/success', - cancelUrl: 'https://merchant.example/pay/cancel', - expiresIn: 1800, -}) -``` - -DIRECT Checkout Sessions require the authenticated merchant to be DIRECT and to -have QuickPay enabled. - -### TypeScript: cross-chain DELEGATE checkout - -```ts -const session = await client.createCheckoutSession({ - checkoutType: 'DELEGATE', - price: '19.95', - clientReferenceId: 'cart_9f31', - lineItems: [{ name: 'Coffee mug', quantity: 1 }], -}) -``` - -In decimal-price DELEGATE mode, Core derives: - -- the fixed callback/settlement chain from the merchant's approved callback - contract; -- eligible source-chain/token choices from merchant receiving configuration and - enabled TSS/token capabilities; -- the atomic payment amount from the selected token decimals. - -The buyer may therefore pay on an eligible source chain while settlement remains on -the merchant's callback chain. - -If `publicMetadata.callback_template` is set, the hosted page uses it to -ABI-encode an optional per-buyer `callback_calldata` at bind time. The template -must have this exact shape (`publicMetadata` is an unstructured object, so the -compiler cannot check it for you): - -```ts -publicMetadata: { - callback_template: { - // Solidity function signature (a leading "function " prefix is optional) - signature: 'testCallback(address payer, uint256 value, string message)', - // STATIC ABI parameters, encoded as-is — there is no runtime substitution, - // so e.g. a fixed uint256 amount bakes in ONE token-decimals assumption - args: ['0x0000000000000000000000000000000000000000', '3500000', 'Cotton T-Shirt'], - }, -}, -``` - -A malformed template (missing `signature`, un-encodable `args`) makes the hosted -page throw at encode time and blocks the bind — deliberately, so the buyer is -never silently downgraded to a plain payment the merchant did not intend. - -Understand the trust model before relying on it: - -- `callback_template` is only an encoding hint for the hosted UI — it is NOT a - server-enforced constraint. -- Bind-time calldata is buyer-controlled input: the buyer can omit it (the order - then settles via the plain no-calldata callback method) or substitute different - bytes, and Core does not re-validate it against the template. -- The callback contract is the on-chain authority: it must validate selectors, - parameters, and permissions itself, and treat every callable function as - buyer-reachable. -- If you need server-authoritative calldata (fulfillment that must run exactly as - pinned), use the legacy fixed-wei form's create-time `callbackCalldata`; price - mode cannot provide that guarantee. - -### TypeScript: legacy fixed-wei DELEGATE checkout - -```ts -const session = await client.createCheckoutSession({ - checkoutType: 'DELEGATE', - chainId: 97, - fixedAmountWei: '1000000', - acceptableTokens: ['0xTokenContract'], - callbackCalldata: '0x...', // optional when no calldata callback is needed - clientReferenceId: 'invoice_123', -}) -``` - -Use this compatibility form only when the merchant intentionally pins one chain and -an atomic token amount. `createDelegateCheckoutSession` remains as a deprecated -wrapper; new integrations should use `createCheckoutSession`. - -### Go - -The Go server SDK is source-only. First [clone this repository and configure a -local `replace`](../goatx402-sdk-server-go/README.md). - -```go -import goatflow "github.com/goatnetwork/goatflow-sdk-server" - -session, err := client.CreateCheckoutSession(ctx, goatflow.CreateCheckoutSessionParams{ - CheckoutType: "DIRECT", - Price: "19.95", - ClientReferenceID: "cart_9f31", - ExpiresIn: 1800, -}) -if err != nil { - return err -} - -// Send session.CheckoutID to the browser, or redirect to session.URL. -``` - -For DELEGATE cross-chain price mode, set `CheckoutType: "DELEGATE"` and -`Price`. For the legacy fixed-wei form, set `ChainID`, -`FixedAmountWei`, and `AcceptableTokens`. - -## Open the session in the browser - -Return the opaque `checkoutId` to the browser; never return the API secret. - -```ts -import { GoatCheckout } from 'goatflow-checkout' - -const goat = GoatCheckout({ origin: 'https://pay.goat.network' }) - -goat.open({ - checkoutId, - display: 'tab', // 'popup', 'tab', or 'redirect' - onSuccess: (result) => { - // UX only; await webhook/order verification before fulfillment. - }, - onCancel: () => {}, - onError: (reason) => { - if (reason === 'opener_unavailable') { - goat.redirectToCheckout({ checkoutId }) - } - }, -}) -``` - -If session creation requires an asynchronous request after the buyer clicks, either -redirect the current page or synchronously open a blank tab and navigate it after -the response. Calling `window.open` only after an `await` is commonly blocked by -browsers. - -## Lifecycle - -1. The merchant backend creates a server-authoritative Checkout Session. -2. The buyer opens the opaque checkout URL and connects a wallet. -3. The hosted page reads safe session terms from Core. -4. The buyer chooses an eligible token; bind creates the real order. -5. DIRECT sends the ERC-20 transfer to the merchant payment address. -6. DELEGATE may request an EIP-712 callback signature, then receives the buyer - transfer at the source TSS wallet and settles to the callback chain. -7. The platform marks the session completed and emits - `quickpay.checkout.completed`. - -Session states are `OPEN`, `BOUND`, `SIGNED` (DELEGATE only), -`COMPLETED`, `EXPIRED`, and `CANCELLED`. The linked order has its own -`CHECKOUT_VERIFIED` → `PAYMENT_CONFIRMED` → `INVOICED` lifecycle. - -## API surface - -| Endpoint | Auth | Intended caller | -| --- | --- | --- | -| `POST /api/v1/checkout/sessions` | Merchant HMAC | Server SDK | -| `GET /checkout/v1/sessions/{checkout_id}` | Public opaque handle | Hosted checkout | -| `GET /checkout/v1/sessions/{checkout_id}/status` | Public opaque handle | Hosted checkout | -| `POST /checkout/v1/sessions/{checkout_id}/bind` | Public, rate-limited | Hosted checkout | -| `POST /checkout/v1/sessions/{checkout_id}/signature` | Public, rate-limited | Hosted DELEGATE checkout | - -Merchant applications normally call only the authenticated create endpoint. The -platform-hosted page owns the public read/bind/signature sequence. - -Nested create fields (`acceptableTokens`, `lineItems`, -`publicMetadata`, and `privateMetadata`) are JSON-stringified by the server -SDK before HMAC signing because the current signing format accepts scalar fields. -Do not reproduce that encoding manually when an SDK is available. - -## Fulfillment and security - -- `onSuccess` and `postMessage` are UX signals, not payment proof. -- Fulfill from the `quickpay.checkout.completed` webhook or a trusted backend - status check. The webhook carries the checkout type, order ID, completion - transaction hash, and, when supplied, `client_reference_id`, line items, and - public metadata. -- The raw `cs_…` handle is high entropy. Treat it as a bearer capability, avoid - logging it, and do not place secrets in public metadata or line items. -- `privateMetadata` is excluded from the public session view. -- Success/cancel URLs must pass the merchant redirect allowlist. -- Popup/tab messages are accepted only from the exact configured origin, exact - opened window, and matching random channel nonce. -- Hosted checkout must remain a top-level page; do not embed it in an iframe. -- The merchant API key and secret stay exclusively on the backend. - -## Related modules - -- [Browser SDK](../goatx402-checkout/README.md) -- [Server SDK (TypeScript)](../goatx402-sdk-server-ts/src/client.ts) -- [Server SDK (Go)](../goatx402-sdk-server-go/client.go) -- [Demo](../goatx402-demo/README.md) -- [QuickPay payer/agent library](../goatx402-quickpay/README.md) +See the current [GOAT Flow Hosted Checkout Guide](./goat-flow-checkout.md). diff --git a/docs/x402-dapp-integration-prompts/SKILL.md b/docs/x402-dapp-integration-prompts/SKILL.md deleted file mode 100644 index 2e2c0c4..0000000 --- a/docs/x402-dapp-integration-prompts/SKILL.md +++ /dev/null @@ -1,234 +0,0 @@ ---- -name: x402-dapp-integration -description: Use this skill when integrating x402 payments into an existing Web DApp from a local folder or Git repository. Apply it when a user wants Codex to prepare merchant credentials, copy the app into a safe working directory, add backend and frontend x402 flows, run local validation, and produce runnable integration documentation without assuming any specific business domain. ---- - -# X402 DApp Integration - -## Overview - -This skill converts an existing Web DApp into an x402-enabled DApp with a reusable merchant-friendly workflow. It is designed for generic products, so do not assume any specific business domain, feature naming, or protected-content model unless the user explicitly provides one. - -## When To Use - -Use this skill when the user wants to: - -- integrate x402 into an existing Web DApp -- work from a local source directory or a Git repository URL -- preserve the original business flow and insert a paywall only around paid or protected actions -- support merchant credentials securely on the backend -- generate local run instructions, validation results, and handoff documentation - -Do not use this skill for: - -- building a brand-new DApp from scratch unless the user explicitly asks for that -- rewriting the product's business logic beyond the minimum payment-gating changes -- exposing merchant secrets to the frontend - -## Required Inputs - -Collect or infer these inputs before making changes: - -- `APP_SOURCE`: local project path or Git repository URL -- `WORKDIR_PARENT`: parent folder for the generated integration project -- `PROJECT_NAME`: name of the new working copy -- `ENV_FILE_PATH`: merchant environment file path -- `PRIVATE_KEY_STORE_PATH`: local storage path for the generated test wallet -- `X402_REPO`: canonical `goat-x402` monorepo URL or local path -- `MIN_PAY_AMOUNT`: minimum accepted stablecoin amount -- `MIN_PAY_TOKENS`: allowed payment tokens, usually `USDT,USDC` -- `BASE_PRICE_BTC`: BTC anchor price unit, such as `0.00001` -- `BINANCE_PRICE_URL`: source of `BTCUSDC` -- `QUOTE_TTL_SECONDS` -- `PRICE_CACHE_SECONDS` - -Only read these merchant fields from `ENV_FILE_PATH`: - -- `GOATX402_MERCHANT_ID` -- `GOATX402_API_URL` -- `GOATX402_API_KEY` -- `GOATX402_API_SECRET` - -If any required merchant field is missing, stop immediately and return the missing-field error. - -## Workflow - -### 1. Prepare Merchant Inputs - -Before any code change, validate that: - -- merchant credentials exist and are readable from the provided env file -- the target working directory is separate from the original source -- a private-key storage path is available -- supported chains, tokens, and minimum payment requirements are explicit - -Never place merchant credentials in frontend code or build artifacts. - -### 2. Copy the DApp Into a Safe Working Directory - -Detect whether `APP_SOURCE` is: - -- a local directory -- a Git repository URL - -Then: - -- copy or clone it into `WORKDIR_PARENT/PROJECT_NAME` -- leave the original source untouched -- inspect the copied project to detect the frontend framework and whether a backend already exists -- create a backend only if the project does not already have one - -### 3. Build and Install x402 SDKs - -Clone `X402_REPO` from the canonical `goat-x402` monorepo into a temporary directory and build: - -- `goatflow-sdk` -- `goatx402-sdk-server-ts` (repository directory; npm package name is `goatflow-sdk-server`) - -Install both packages into the working project from local paths using the package names `goatflow-sdk` and `goatflow-sdk-server`. Verify that each package has a valid `dist` build before continuing. - -### 4. Add Backend x402 Capabilities - -Use the following `/api/x402/*` routes as the **recommended integration interface convention** for this skill. Teams may adapt the route structure to match their existing backend architecture, as long as the payment flow, validation rules, and backend responsibilities remain consistent. - -Recommended x402 backend APIs: - -- `GET /api/health` -- `GET /api/x402/config` -- `GET /api/x402/quote` -- `POST /api/x402/session` -- `POST /api/x402/order` -- `POST /api/x402/order/:id/signature` -- `GET /api/x402/order/:id` -- `POST /api/x402/reveal` - -Backend rules: - -- enforce token allowlist -- enforce chain allowlist -- enforce minimum payment amount -- calculate pricing on the backend only -- derive `amountWei` from token decimals using ceiling -- reject expired quotes -- reject mismatched order amounts -- treat frontend-submitted amount as reference only -- never log private keys, API secrets, or full auth headers - -### 5. Apply Generic Pricing Logic - -Execute pricing on the backend with this model: - -If the merchant can use native QuickPay instead of a custom quote engine, prefer QuickPay Products: configure fixed-price products with `product_key` and token-agnostic `price`, then let the QuickPay session API compute the token amount from the buyer-selected chain and token. - -- fetch `BTCUSDC` from `BINANCE_PRICE_URL` -- compute `converted = BASE_PRICE_BTC * btcUsdcPrice` -- compute `finalAmount = max(converted, MIN_PAY_AMOUNT)` -- use the same `finalAmount` across all supported chains for stablecoins -- cache source price for `PRICE_CACHE_SECONDS` -- set quote expiry to `QUOTE_TTL_SECONDS` - -Each quote should include: - -- `quoteId` -- `basePriceBtc` -- `btcUsdcPrice` -- `finalAmount` -- `amountWei` -- `token` -- `chainId` -- `expiresAt` - -If the live Binance request fails: - -- use a still-valid cached price if available -- otherwise return `503` and reject order creation - -### 6. Add Frontend Payment Flow - -Insert a payment gate before protected or paid content without breaking the existing user flow. - -Implement the state machine: - -- `idle -> session_created -> quote_ready -> order_created -> paying -> confirming -> paid -> unlocked` - -Frontend payment flow: - -1. create session -2. request quote -3. create order -4. initiate wallet payment with `goatflow-sdk` -5. poll order status -6. call reveal after successful confirmation - -Frontend requirements: - -- block submission when the visible amount is below the minimum threshold -- auto-refresh expired quotes -- display BTC anchor, stablecoin quote, price source, and quote expiry - -### 7. Generate Test Wallet and Handle Faucet Setup - -Generate a local EVM test wallet and store it at `PRIVATE_KEY_STORE_PATH`. - -Rules: - -- store the private key in a local file only -- never print the private key in plain text -- set file permission to `600` -- add the private-key file to `.gitignore` - -If faucet calls require captcha fields and they are not provided, return exactly: - -`captcha_id/captcha_code required from user` - -### 8. Run Local Validation - -Start the local backend and frontend, then validate: - -- health endpoint works -- config endpoint works -- quote endpoint returns `finalAmount`, `amountWei`, and `expiresAt` -- an order below the minimum amount fails -- a minimum-boundary order behaves correctly based on the quote -- a correctly priced order can be created -- protected content remains locked until payment is confirmed - -If full on-chain payment cannot be completed, report the exact blocker instead of claiming success. - -### 9. Deliver Final Outputs - -Produce: - -- modified file list -- executed command list -- acceptance results -- unfinished items and blockers -- next-step suggestions -- a runnable integration guide in the new project root - -The integration guide should include: - -- environment variables and where they are read -- startup steps -- API definitions with examples -- frontend payment state machine -- backend pricing rules -- minimum amount rules -- faucet workflow -- troubleshooting -- a short demo script - -## Guardrails - -- Do not assume the DApp domain. -- Do not rename existing business APIs unless required by the fixed x402 endpoints. -- Do not expose merchant credentials to the frontend. -- Do not modify the original project in place. -- Do not remove existing product behavior unless it conflicts with required payment gating. -- Do not claim full payment success unless the order reaches confirmed status and reveal succeeds. - -## Example Trigger Prompts - -- "Use this skill to add x402 to my existing DApp from this Git repo." -- "Integrate x402 into the app at this local path and keep the original project untouched." -- "Wire a generic x402 paywall into my current frontend and backend, then give me local test steps." diff --git a/docs/x402-dapp-integration-prompts/openai.yaml b/docs/x402-dapp-integration-prompts/openai.yaml deleted file mode 100644 index 50f8282..0000000 --- a/docs/x402-dapp-integration-prompts/openai.yaml +++ /dev/null @@ -1,4 +0,0 @@ -interface: - display_name: "X402 DApp Integration" - short_description: "Integrate x402 into an existing DApp" - default_prompt: "Use the x402 dapp integration skill to add x402 payments to an existing DApp." diff --git a/docs/x402-developer-quickstart.md b/docs/x402-developer-quickstart.md deleted file mode 100644 index 6f4189f..0000000 --- a/docs/x402-developer-quickstart.md +++ /dev/null @@ -1,320 +0,0 @@ -# GOAT Flow Developer Quick Start - -> A quick-start guide for developers. -> The goal is to help you complete a basic x402 integration and run your first payment flow in the shortest possible time. - ---- - -## Who This Guide Is For - -This guide is for: - -- developers who want to start integrating x402 quickly -- teams that already have a Merchant Account and API credentials -- projects that want to get a minimal payment flow working before expanding further - ---- - -## What You Need Before You Start - -Before getting started, make sure you already have: - -- a created Merchant Account -- a configured Receiving Address -- generated API Key and API Secret -- a working test or development environment -- a chosen payment mode: DIRECT or DELEGATE - -For a fixed DIRECT QuickPay product opened through Hosted Checkout, API credentials -are not required in the browser. Dynamic DIRECT and every DELEGATE Checkout Session -still require credentials on the merchant backend. - -If these prerequisites are not ready yet, read: - -- **x402 Onboarding Guide** -- **Merchant Guide** - ---- - -## Quick Start Flow - -The recommended quick-start flow is: - -1. Get the x402 SDK or demo project -2. Configure API credentials -3. Create your first order -4. Initiate a payment -5. Query order status -6. Retrieve proof (if applicable) -7. Validate everything in the test environment - ---- - -# Step 01 — Get the SDK / Project Resources - -There are currently two main paths: - -## Option 1: Get Started Directly from GitHub - -Recommended entry point: - -- **x402 GitHub Repo** - https://github.com/GOATNetwork/x402 - -From the repository, you can access: - -- SDK packages -- example projects -- API documentation -- demos -- developer-focused docs - -## Option 2: Integrate Through an Agent - -If your team uses an agent-assisted payment flow, start from the merchant's QuickPay agent document: - -```text -GET https://flow-api.goat.network/quickpay/{merchant_id}/agent.md -``` - -The agent document points to the matching `manifest.json` and public QuickPay session endpoints. - -## Install the Core SDKs - -```bash -# Backend order creation and polling -npm install goatflow-sdk-server - -# Frontend wallet payment helper -npm install goatflow-sdk ethers - -# Agent / CLI QuickPay payer -npm install goatflow-quickpay - -# Hosted browser checkout -npm install goatflow-checkout -``` - ---- - -## Fastest Browser Path: Hosted Checkout - -For a fixed, server-priced DIRECT product: - -```ts -import { GoatCheckout } from 'goatflow-checkout' - -const goat = GoatCheckout({ origin: 'https://pay.goat.network' }) - -payButton.addEventListener('click', () => { - goat.open({ - merchant: 'merchant_123', - productKey: 'mug', - onSuccess: () => { - // UX only; fulfill from the webhook or trusted backend status. - }, - }) -}) -``` - -For a dynamic DIRECT amount or any DELEGATE checkout, use the server SDK's -`createCheckoutSession(...)` on your backend and pass the returned opaque -`checkoutId` to `goat.open({ checkoutId })`. See -[Hosted Checkout](x402-checkout.md). - -Use the remaining steps when you need the lower-level, build-your-own order and -wallet flow. - ---- - -# Step 02 — Configure API Credentials - -Configure the following values in your backend project: - -- API URL -- API Key -- API Secret - -```bash -GOATX402_API_URL=https://flow-api.goat.network -GOATX402_API_KEY=your_api_key -GOATX402_API_SECRET=your_api_secret -``` - -## Important Notes - -- `API Secret` must only be stored on the server side -- test and production credentials must be kept separate -- do not expose key / secret values in frontend code or public repositories - ---- - -# Step 03 — Create Your First Order - -The first integration step is usually handled on the backend. - -When creating an order, confirm the following: - -- chain -- token -- amount -- payment mode (DIRECT / DELEGATE) -- callback / execution configuration (if applicable) - -After order creation succeeds, the backend should return: - -- `orderId` -- payment-related parameters -- the context needed for the selected payment mode - -Minimal TypeScript backend example: - -```typescript -import { GoatFlowClient } from 'goatflow-sdk-server' - -const client = new GoatFlowClient({ - baseUrl: process.env.GOATX402_API_URL ?? 'https://flow-api.goat.network', - apiKey: process.env.GOATX402_API_KEY!, - apiSecret: process.env.GOATX402_API_SECRET!, -}) - -const order = await client.createOrder({ - dappOrderId: `order_${Date.now()}`, - chainId: 137, - tokenSymbol: 'USDC', - tokenContract: '0x3c499c542cef5e3811e1192ce70d8cc03d5c3359', - fromAddress: '0xUserWalletAddress', - amountWei: '10000000', -}) - -// Order creation returns an x402 HTTP 402 challenge under the hood. -// The SDK normalizes it into order.orderId, order.payToAddress, order.amountWei, and order.flow. -``` - ---- - -# Step 04 — Initiate Payment - -After the frontend receives the order payload, it can trigger the payment flow for the user. - -This step usually includes: - -- launching the payment action -- wallet signature / user confirmation -- showing loading / success / error UI -- handling any callback-related pre-signature if required - ---- - -# Step 05 — Query Order Status - -After payment is initiated, the frontend or backend should be able to query order status for: - -- payment progress tracking -- success / failure display -- fulfillment decisions - -At minimum, your implementation should handle: - -- waiting for payment -- payment in progress -- completed -- failed -- expired - ---- - -# Step 06 — Retrieve Proof (If Applicable) - -If your business logic requires payment proof, retrieve it after payment is completed. - -Treat the response as a payment record: its `signature` field is an unsigned Keccak256 checksum over a subset of the payload fields (see the API reference for the exact list), not a cryptographic attestation. Verify `payload.tx_hash` on-chain when independent proof is required. - -Typical proof use cases include: - -- reconciliation -- audit trails -- fulfillment evidence -- dispute handling - ---- - -# Step 07 — Validate in Test Environment - -Before going live, it is recommended to validate at least the following: - -- DIRECT flow works -- DELEGATE flow works (if applicable) -- receiving address is correct -- payment status updates correctly -- proof can be retrieved -- error handling behaves as expected -- fee balance is sufficient for order creation - -### Test environment endpoints and gas - -| Resource | GOAT Testnet3 (development) | GOAT Mainnet (production) | -| --- | --- | --- | -| Chain ID | `48816` | `2345` | -| RPC | `https://rpc.testnet3.goat.network` | `https://rpc.goat.network` | -| Explorer | `https://explorer.testnet3.goat.network` | `https://explorer.goat.network` | -| Merchant Portal | operator-provided per deployment | `https://x402-merchant.goat.network` | -| API base | operator-provided per deployment | `https://flow-api.goat.network` | - -GOAT Network is a Bitcoin L2, so native gas is BTC: - -- **Testnet3:** get BTC for gas from the [GOAT Testnet3 faucet](https://bridge.testnet3.goat.network/faucet); the minimum priority gas tip is `130000` wei. -- **Mainnet:** there is no faucet — fund the payer/deployer wallet with production native gas. - -> The per-deployment Testnet3 API base and test token contracts are environment-specific — ask the GOAT Flow team for your Testnet3 API base and test token addresses. Full operator detail (chains, tokens, RPC, deploy steps) is in `ONBOARDING.md`. - ---- - -## Common Questions - -### 1. Order creation fails -Check first: - -- whether the API key / secret is correct -- whether fee balance is sufficient -- whether chain / token / receiving configuration is correct - -### 2. Order status does not update after payment -Check: - -- whether payment was sent to the correct address -- whether token / chain / amount matches the order -- whether the required confirmations have been reached - -### 3. DELEGATE callback does not complete successfully -Check: - -- whether callback configuration is correct -- whether calldata is valid -- whether contract state matches expectations - ---- - -## Related Documents - -Recommended companion docs: - -- **Hosted Checkout Guide** -- **x402 Onboarding Guide** -- **Merchant Guide** -- **x402 FAQ** -- **API Reference** -- **DIRECT vs DELEGATE Guide** - ---- - -## One-Line Summary - -If you are a developer, the fastest path to integrate x402 is: - -**choose Hosted Checkout or a custom order flow → keep credentials on the backend -when required → confirm payment from webhook/status before fulfillment** - ---- - -Contact email: x402support@goat.network diff --git a/docs/x402-direct-vs-delegate.md b/docs/x402-direct-vs-delegate.md deleted file mode 100644 index 7246df4..0000000 --- a/docs/x402-direct-vs-delegate.md +++ /dev/null @@ -1,176 +0,0 @@ -# DIRECT vs DELEGATE - -> A guide to the two payment modes currently supported by GOAT Flow. - ---- - -## Why Are There Two Modes? - -Different business scenarios require different kinds of payment flows. - -Some scenarios only need to **collect payment**. -Others require the system to **continue executing on-chain logic after payment succeeds**. - -That is why GOAT Flow supports two modes: - -- **DIRECT** -- **DELEGATE** - ---- - -## What Is DIRECT? - -DIRECT mode means: - -> **The user pays directly to the merchant address.** - -This mode is simpler and better suited for lightweight payment scenarios. The payer sends an ERC-20 transfer on the selected EVM chain to the merchant's configured receiving address; the x402 watcher matches that transfer to the order. There is no TSS payout step and no callback contract in the fund path. - -### Good Fit For - -- simple product purchases -- paid content -- API monetization -- tips / donations -- payment flows that do not require complex on-chain execution - -### DIRECT Characteristics - -- simpler integration path -- funds go directly to the merchant address -- usually does not require complex callback execution -- available on all supported EVM mainnets -- lower per-chain configured fee - -### DIRECT Default Fee - -- **The default configuration is typically $0.10 per order, but fees are chain/admin-configured** - ---- - -## What Is DELEGATE? - -DELEGATE mode means: - -> **Payment does not just complete fund transfer — it also supports post-payment on-chain execution logic.** - -This mode is better suited for more advanced business flows. The merchant still -configures one EVM callback/settlement chain, and its receiving tokens plus approved -callback contract must agree on that chain. A DELEGATE order may, however, accept -payment on an eligible source chain and settle/call back on that fixed merchant -chain. Orders use EIP-3009 `receiveWithAuthorization` or Permit2 -`SignatureTransfer`, TSS co-signing, and SubmitMonitor submission. - -### Good Fit For - -- NFT minting -- in-game on-chain actions -- gas top-up flows -- agent-driven execution -- scenarios where payment success should immediately trigger callback or contract logic - -### DELEGATE Characteristics - -- more powerful payment flow -- supports callback / execution configuration through an approved merchant callback contract -- uses EIP-3009 or Permit2 authorization plus TSS-assisted submission -- requires one EVM callback/settlement chain per DELEGATE merchant configuration -- can expose eligible source-chain/token choices in cross-chain Hosted Checkout -- cannot use Metis or Tempo as the merchant callback/settlement chain in the current matrix -- better for payment + execution scenarios -- higher per-chain configured fee - -### DELEGATE Default Fee - -- **The default configuration is typically $0.20 per order, but fees are chain/admin-configured** - ---- - -## Chain Availability - -| Chain | Chain ID | DIRECT | DELEGATE | Explorer | -| --- | --- | --- | --- | --- | -| Ethereum | `1` | Yes | Yes | `etherscan.io` | -| Polygon | `137` | Yes | Yes | `polygonscan.com` | -| BSC | `56` | Yes | Yes | `bscscan.com` | -| Arbitrum | `42161` | Yes | Yes | `arbiscan.io` | -| Optimism | `10` | Yes | Yes | `optimistic.etherscan.io` | -| Avalanche | `43114` | Yes | Yes | `snowtrace.io` | -| Base | `8453` | Yes | Yes | `basescan.org` | -| Berachain | `80094` | Yes | Yes | `berascan.com` | -| X Layer | `196` | Yes | Yes | `web3.okx.com/explorer/x-layer/evm` | -| GOAT | `2345` | Yes | Yes | `explorer.goat.network` | -| Metis | `1088` | Yes | No | `andromeda-explorer.metis.io` | -| Tempo | `4217` | Yes | No | `explore.tempo.xyz` | - -This matrix describes merchant receiving/callback-chain availability. DELEGATE -requires Permit2 or EIP-3009 support plus a reviewed callback contract on the -merchant settlement chain. Metis and Tempo should be configured as DIRECT -settlement chains. Cross-chain Hosted Checkout source candidates are derived from -live TSS/token configuration and are not implied by this static matrix. - ---- - -## DIRECT vs DELEGATE Comparison - -| Dimension | DIRECT | DELEGATE | -| --- | --- | --- | -| Core goal | collect payment | collect payment + execute logic | -| Complexity | low | medium / high | -| User fund path | user wallet -> merchant address | user -> source-chain TSS path -> merchant callback/settlement chain | -| Callback / execution | usually not needed | supported through approved callback contract | -| Chain scope | selected payment chain | one merchant callback chain; eligible source chain may differ | -| Best fit | simple payments | advanced on-chain business flows | -| Default fee | per-chain/admin-configured; often $0.10 / order | per-chain/admin-configured; often $0.20 / order | - ---- - -## Pricing Model - -GOAT Flow currently uses: - -> **a fixed fee per order** - -It does **not** use: - -- percentage-based take rates -- GMV-based percentage fees - -### Pricing Rules - -- DIRECT: the default configured fee is often **$0.10 per order** -- DELEGATE: the default configured fee is often **$0.20 per order** -- actual fees are configured per chain by the platform/admin operator -- fees are paid from the merchant’s **fee balance** -- fee balance is checked when an order is created -- if an order completes successfully, the fee is consumed -- if an order expires or is canceled, the fee is refunded to the fee balance - ---- - -## How to Choose - -### Choose DIRECT if you need: - -- faster integration -- a simpler payment flow -- a payment-only experience without downstream execution - -### Choose DELEGATE if you need: - -- post-payment on-chain execution -- callback / contract execution -- more advanced merchant workflows -- a combined payment + business action experience -- one configured EVM callback chain, optionally accepting eligible cross-chain source payments - ---- - -## One-Line Summary - -- **DIRECT = get paid** -- **DELEGATE = get paid + do something after payment** - ---- - -Contact email: x402support@goat.network diff --git a/docs/x402-faq.md b/docs/x402-faq.md deleted file mode 100644 index 14ec9ea..0000000 --- a/docs/x402-faq.md +++ /dev/null @@ -1,531 +0,0 @@ -# GOAT Flow FAQ - -## Overview - -This document summarizes common questions about GOAT Flow, including architecture, security, user experience, settlement, decentralization, wallet compatibility, and developer integration. - ---- - -## Technical and Security - -### General Architecture - -#### What makes x402 different from existing payment solutions? - -x402 brings the HTTP 402 **Payment Required** standard to Web3. Unlike payment widgets or checkout redirects, x402 works as a protocol layer that: - -- returns a standard HTTP 402 response with payment details -- uses on-chain settlement with a retrievable payment record -- supports programmable callbacks for atomic payment + action flows -- enables multi-chain support through a single integration - -#### How does payment verification work? - -Payments are verified on-chain through event monitoring: - -1. The user transfers tokens to the designated `payTo` address in the x402 `accepts[]` entry. -2. A multi-chain listener monitors `Transfer` events. -3. The system matches the transfer to a pending order. -4. For DELEGATE orders, TSS-assisted callback / settlement execution is completed - on the merchant's configured callback chain. -5. A payment record is generated with the matched transaction details and an unsigned Keccak256 checksum over its key payment fields. -6. Merchants retrieve the record through the API and verify the referenced transaction directly on-chain when independent proof is required. - -The legacy `/proof` response field named `signature` is not a signature or attestation: no private key is involved, and anyone with the public payload can recompute it. - -#### What if a payment was sent but not detected? - -The monitoring system is designed for high reliability: - -- multiple RPC endpoints with automatic failover -- checkpointing to avoid skipping blocks -- order expiration leaves a 20-minute detection window -- if a payment is truly missed, contact support with the transaction hash - -#### How are TSS keys managed? - -The threshold signature scheme (TSS) is designed for enterprise-grade security: - -- key shares are distributed across multiple independent nodes (`t-of-n` threshold) -- no single node can access the full private key -- signing requires collaboration from at least `t` nodes -- nodes are geographically distributed -- keys are rotated and audited regularly - ---- - -## Security Model - -#### How does x402 prevent replay attacks? - -Replay protection uses multiple layers: - -| Layer | Mechanism | -| --- | --- | -| API | HMAC timestamp validation (±5 minutes) + required one-time `X-Nonce` | -| Callback signature | EIP-712 `calldataNonce` per original payer | -| Contract storage | `calldataNonceUsed` mapping inside the MerchantCallback contract, exposed through `isCalldataNonceUsed` | - -#### What prevents callback data from being tampered with? - -EIP-712 typed-data signatures bind the callback to the full execution context. - -The signature covers: - -`token + owner + payer + amount + orderId(bytes32) + calldataNonce + deadline + keccak256(calldata)` - -For Permit2 callback flows, the signature also covers `permit2`. - -Any change to any field invalidates the signature. - -#### How are chain reorganizations handled? - -Payments require chain/token-specific confirmations before being finalized. The exact threshold is configured by the platform operator per chain and token; use the live chain configuration for production values. - -Supported mainnet scope and mode availability: - -| Chain | Chain ID | DIRECT | DELEGATE | Explorer | -| --- | --- | --- | --- | --- | -| Ethereum | `1` | Yes | Yes | `etherscan.io` | -| Polygon | `137` | Yes | Yes | `polygonscan.com` | -| BSC | `56` | Yes | Yes | `bscscan.com` | -| Arbitrum | `42161` | Yes | Yes | `arbiscan.io` | -| Optimism | `10` | Yes | Yes | `optimistic.etherscan.io` | -| Avalanche | `43114` | Yes | Yes | `snowtrace.io` | -| Base | `8453` | Yes | Yes | `basescan.org` | -| Berachain | `80094` | Yes | Yes | `berascan.com` | -| X Layer | `196` | Yes | Yes | `web3.okx.com/explorer/x-layer/evm` | -| GOAT | `2345` | Yes | Yes | `explorer.goat.network` | -| Metis | `1088` | Yes | No | `andromeda-explorer.metis.io` | -| Tempo | `4217` | Yes | No | `explore.tempo.xyz` | - -The matrix describes merchant settlement/callback chains. DELEGATE requires an -approved EVM callback contract on that configured merchant chain; Metis and Tempo -use DIRECT as settlement modes. Eligible cross-chain source payments are derived -from live TSS/token configuration. - -#### What if a TSS node is compromised? - -Threshold signing provides resilience: - -- a single compromised node cannot sign transactions -- an attacker would need the threshold minimum (for example, 3 out of 5) -- compromised nodes can be rotated without changing wallet addresses -- anomaly detection monitors suspicious signing patterns - ---- - -## User Experience - -### Getting Started - -#### What does a user need in order to pay with x402? - -A user needs: - -1. A compatible wallet (MetaMask, Coinbase Wallet, WalletConnect-compatible wallets, etc.) -2. A supported token on a supported chain (such as USDC or USDT) -3. A small amount of native gas token, unless the flow uses a gasless mode - -#### Which wallets are supported? - -Any wallet that supports: - -- standard ERC-20 token transfers -- EIP-712 typed-data signatures -- supported signature-based payment flows when applicable - -Tested wallets include MetaMask, Coinbase Wallet, and Rainbow. - -#### How long does one payment take? - -Timing depends on the selected EVM chain, RPC health, and the confirmation threshold configured for that token. After the wallet broadcasts the transaction, low-latency L2/L1-style chains usually complete in seconds to tens of seconds, while Ethereum mainnet may take longer. - -| Chain | Chain ID | Mode Availability | -| --- | --- | --- | -| Ethereum | `1` | DIRECT + DELEGATE | -| Polygon | `137` | DIRECT + DELEGATE | -| BSC | `56` | DIRECT + DELEGATE | -| Arbitrum | `42161` | DIRECT + DELEGATE | -| Optimism | `10` | DIRECT + DELEGATE | -| Avalanche | `43114` | DIRECT + DELEGATE | -| Base | `8453` | DIRECT + DELEGATE | -| Berachain | `80094` | DIRECT + DELEGATE | -| X Layer | `196` | DIRECT + DELEGATE | -| GOAT | `2345` | DIRECT + DELEGATE | -| Metis | `1088` | DIRECT | -| Tempo | `4217` | DIRECT | - -#### Can users pay on mobile? - -Yes. The SDK supports mobile wallet flows through: - -- WalletConnect -- deep links into wallet apps -- embedded wallet SDKs - ---- - -## Payment Flow - -#### How many transactions does the user need to sign? - -Usually one: - -- **DIRECT mode**: a single ERC-20 transfer -- **DELEGATE mode with callback**: one signature that covers both payment and callback authorization - -If the token flow requires an approval-style step, that may introduce an additional setup action depending on the integration path. - -#### What if I do not hold tokens on the merchant's settlement chain? - -- **DIRECT**: the payer must hold the selected token on the merchant's receiving - chain and transfers directly to that chain's merchant address. -- **DELEGATE**: a Hosted Checkout decimal-price session may offer eligible - source-chain/token choices even though the merchant callback/settlement chain is - fixed. The hosted page shows only candidates derived from current merchant, - token, and TSS configuration. - -This is a controlled payment/settlement path, not a general-purpose bridge. If the -checkout does not list a chain/token you hold, move or acquire funds outside x402 -before paying. - -#### Can a payment be canceled after sending? - -Once tokens are transferred on-chain, the transfer cannot be reversed. However: - -- orders can be canceled before payment while still in `CHECKOUT_VERIFIED` -- nothing happens if an order expires without payment -- refunds depend on merchant policy - -#### What if I pay the wrong amount? - -The system matches exact amounts: - -- **underpayment**: the order remains unpaid (and tokens may be lost if sent to the wrong address) -- **overpayment**: the excess stays at the destination address and is not automatically refunded -- best practice: use the SDK so the exact amount is handled automatically - ---- - -## Fees and Settlement - -### Pricing - -#### How much does x402 charge merchants? - -GOAT Flow uses a **fixed fee model charged per order**, not a percentage-based fee. Fees are configured by mode and chain. In general, **DIRECT mode carries a lower fixed fee**, while **DELEGATE mode carries a higher fixed fee** because it includes additional settlement, payout gas, and execution overhead. Merchant fees are deducted from a **pre-funded USD fee balance** when orders are created. - -#### Who pays gas fees? - -It depends on the mode: - -- **User-paid**: standard ERC-20 transfer (user wallet → merchant) -- **Abstracted**: DELEGATE mode (user pays to the TSS address, and the TSS flow covers the payout gas) - -In DELEGATE mode, gas costs are included in the service fee. - -#### How do merchants pay fees? - -Merchants maintain a USD-denominated fee balance: - -1. top up through the dashboard or API -2. fees are deducted when orders are created -3. a successful order create returns the normal HTTP 402 x402 challenge -4. if the balance is too low, `POST /api/v1/orders` returns HTTP 400 with `{"error":"insufficient fee balance: available=$X, required=$Y"}`; QuickPay session creation returns HTTP 503 -5. unused fees from expired orders are refunded - -#### Which tokens are supported? - -Currently supported: - -- USDC -- USDT -- additional token support may be expanded over time based on product and merchant requirements - -#### What about volatile assets like ETH or BTC? - -The system is currently optimized for stablecoins such as USDC and USDT to support predictable pricing. Support for volatile assets is on the roadmap and would likely involve immediate conversion into stablecoins. - -### Settlement - -#### How quickly do merchants receive funds? - -| Mode | Settlement Time | -| --- | --- | -| DIRECT | Immediate (same transfer flow) | -| DELEGATE | Within 1–2 blocks after payment confirmation | - -#### Can merchants receive funds on a configured chain or token? - -Merchants configure the chains, tokens, receiving assets, and callback chain they accept: - -- **DIRECT**: user wallet -> merchant receiving address on the selected chain. -- **DELEGATE**: user payment on an eligible source chain -> TSS-assisted payout / - callback -> merchant's single configured EVM settlement chain. - -Cross-chain eligibility is runtime configuration, not an arbitrary bridge promise. - -#### Is there a minimum payment amount? - -There is no protocol-enforced minimum. In practice, the lower bound is around **$0.01**, because below that gas and operational costs may exceed the payment value. - -#### How do merchants reconcile payments with accounting systems? - -Available options include: - -- webhook notifications for `order.invoiced` -- merchant order ID correlation -- API polling and reconciliation for other order states -- CSV export from the dashboard - ---- - -## Decentralization - -### Trust Model - -#### How decentralized is x402? - -x402 is a hybrid system optimized for practicality: - -| Component | Centralization Level | Why | -| --- | --- | --- | -| API servers | Centralized | performance and user experience | -| Payment settlement | Decentralized | on-chain and trust-minimized | -| TSS signing | Distributed | multiple independent nodes | -| MerchantCallback | Decentralized | merchant-owned contract | - -#### Does x402 custody user funds? - -No. - -Funds flow as follows: - -1. **DIRECT mode**: user → merchant (x402 never touches the funds) -2. **DELEGATE mode**: user → TSS → merchant (TSS temporarily handles settlement, usually for less than one minute) - -The system does not have unilateral access to user or merchant funds. - -#### Can x402 freeze or censor payments? - -Its intervention ability is limited: - -- it cannot stop DIRECT-mode payments because those are direct user → merchant transfers -- in DELEGATE mode, TSS could theoretically delay payout, but funds are still destined for the merchant -- all transactions are auditable on-chain -- MerchantCallback contracts are owned and upgradeable by merchants - -#### What happens if x402 stops operating? - -Impact depends on the component: - -- **DIRECT orders**: unaffected, because they are direct wallet transfers -- **in-flight DELEGATE orders**: TSS would still need to settle and distribute funds -- **MerchantCallback contracts**: continue to function because merchants own them -- **API / SDK layer**: may require migration or open-source alternatives in the future - -#### Is the code open source? - -The public `GOATNetwork/x402` repository is the external integration and -documentation surface. It contains the browser/server SDKs, Checkout and QuickPay -tooling, middleware, demo, callback contracts, and public documentation. Platform -core and operator applications are not presented as public modules in this -repository. - -Production deployments are still operated environments, so confirm the deployed version, configuration, and TSS operator policy with the deployment operator. - ---- - -## Wallet, AA, and Paymaster Integration - -### Wallet Compatibility - -#### Does x402 work with smart contract wallets (AA / ERC-4337)? - -Yes. x402 is compatible with account abstraction: - -- smart contract wallets can sign EIP-712 messages -- compatible with Safe, Argent, and other AA wallets -- no special integration is required - -#### How does x402 compare with a Paymaster? - -They are complementary, not competitive: - -| Aspect | Paymaster | x402 | -| --- | --- | --- | -| Purpose | Gas abstraction | Payment protocol | -| Scope | Single transaction gas | Full payment lifecycle | -| Callback support | No | Yes (programmable) | -| Multi-chain | Per-chain setup | Unified flow | - -A combined flow is also possible: users can pay for the service via x402 while gas is sponsored through a Paymaster. - -#### Can x402 enable gasless transactions? - -Yes, on DELEGATE-capable chains through two common paths: - -1. **EIP-3009 tokens (such as USDC)**: `receiveWithAuthorization` supports gasless user flow -2. **Permit2**: users sign a permit and a relayer executes the transaction - -In both cases, the user signs but does not directly pay gas. - -#### Does x402 work with WalletConnect? - -Yes. WalletConnect is fully supported: - -- the SDK detects WalletConnect providers -- signature requests are forwarded to the connected mobile wallet -- the flow works across supported chains - -### Account Abstraction Details - -#### Can AA wallets execute x402 callback flows? - -Yes. - -#### What about session keys? - -Session keys can be used together with x402: - -- delegate signing authority to a session key -- let the session key sign x402 payment authorizations -- useful for subscriptions or automated payments -- spend limits remain enforced by the AA wallet - -#### Does x402 support ERC-4337 UserOperations directly? - -Not directly. x402 uses standard transactions, but it remains compatible: - -- AA wallets can still execute standard ERC-20 transfers -- UserOperations can include x402 payments as internal calls -- bundler compatibility is preserved - ---- - -## Integration and Development - -### Getting Started - -#### How do I get API credentials? - -1. Submit a merchant application through the merchant portal or `POST /merchant/v1/auth/register` with `merchant_id`, `name`, `email`, `password`, and `receive_type`. -2. The application is created as pending and disabled; no API tokens are issued at registration time. -3. An admin/operator reviews the merchant in the admin dashboard or `/admin/merchants` and approves it by enabling the merchant, or rejects it through `/admin/merchants/:merchant_id/reject`. -4. After approval, the owner logs into the merchant portal and uses Developer / API Keys, or `POST /merchant/v1/api-keys/rotate`, to generate the API key and secret. Admins can also rotate merchant API keys through `/admin/merchants/:merchant_id/rotate-keys`. - -#### Is there a testnet or sandbox? - -The public docs are mainnet-first. Use the supported mainnet matrix above for production integration. - -For sandbox testing, use the environment and chain IDs provided by the operator. Do not assume old public testnets are available. - -#### What is the quickest path for an AI agent to pay? - -Use QuickPay when the merchant has enabled it. Agents can discover the merchant's public payment surface without merchant API credentials: - -- `GET /quickpay/:merchant_id/agent.md` -- `GET /quickpay/:merchant_id/manifest.json` -- `POST /quickpay/v1/x402/sessions` -- `GET /quickpay/v1/x402/sessions/:session_id` - -The `goatflow-quickpay` CLI supports `inspect`, -`pay-x402 --amount --token-contract --chain`, -`pay-product --product --token-contract --chain`, and `pay-mpp --route`. - -#### What is the fastest browser checkout integration? - -Use `goatflow-checkout`. A QuickPay-enabled DIRECT merchant can open a fixed, -server-priced product with `open({ merchant, productKey })` and no merchant -backend. Dynamic DIRECT checkout and all DELEGATE checkout use the server SDK's -`createCheckoutSession(...)`, then pass the opaque `checkoutId` to -`open({ checkoutId })`. - -The hosted page owns wallet connection, token selection, payment, and status UX. -The browser success callback is not payment proof; fulfill from -`quickpay.checkout.completed` or trusted backend status. See -[Hosted Checkout](x402-checkout.md). - -#### How do I integrate x402 into an existing payment flow? - -x402 is designed to fit into an existing checkout path as an additional payment method: - -- existing flow: user → checkout → Stripe / PayPal → fulfillment -- x402 flow: user → checkout → x402 order → HTTP 402 response → user pays → fulfillment - ---- - -## Troubleshooting - -#### Why do I get HTTP 402 when creating an order? - -HTTP 402 is the normal x402 `Payment Required` challenge for a successfully created order. The response contains the payment options in the body and in the `PAYMENT-REQUIRED` header. - -Insufficient merchant fee balance is a different error. On `POST /api/v1/orders`, it returns HTTP 400: - -```json -{ - "error": "insufficient fee balance: available=$0.050000, required=$0.200000" -} -``` - -On the QuickPay session path, the same condition is surfaced as HTTP 503 `merchant temporarily unavailable`. Top up the merchant fee balance only for those insufficient-balance errors, not for a normal 402 challenge. - -#### The payment was sent, but the order is still `CHECKOUT_VERIFIED`. Why? - -Possible reasons: - -1. not enough confirmations yet -2. wrong amount sent -3. wrong token or wrong chain used -4. temporary listener delay (usually resolved within 1 minute) - -If the issue persists, contact support with the transaction hash. - -#### Callback execution failed. What happened? - -Common causes include: - -1. gas estimation failure because the callback reverts in simulation -2. invalid calldata encoding that does not match the target ABI -3. target contract state changed after the order was created -4. EIP-712 signature validation failed - -Check the on-chain `CalldataExecuted(bytes calldata_, bool success, bytes result)` event. A failed callback is emitted with `success=false`, and `result` contains the returned error data. - ---- - -## Compliance and Legal - -#### Is x402 compliant with regulations? - -x402 is infrastructure. Compliance depends on the merchant’s use case and jurisdiction: - -- merchants are responsible for their own regulatory compliance -- x402 provides transaction records for audit purposes -- x402 does not collect user personal data by default -- it can integrate with compliance tooling such as Chainalysis - -#### Do merchants need to run KYC on users? - -That depends on the merchant’s jurisdiction and risk posture. x402 does not require KYC, but it does not prevent merchants from implementing it. - -#### What about tax reporting? - -The system provides: - -- full transaction history through the API -- CSV export for accounting -- webhook events for real-time integration -- merchant-defined order IDs for reconciliation - -Tax calculation and reporting remain the merchant’s responsibility. - ---- - -## Contact and Support - -For support related to x402 integrations, merchant setup, and partner ecosystem questions, please contact: - -- **x402 support email:** `x402support@goat.network` diff --git a/docs/x402-integration.md b/docs/x402-integration.md deleted file mode 100644 index 7ae8816..0000000 --- a/docs/x402-integration.md +++ /dev/null @@ -1,1298 +0,0 @@ -# GOAT Flow Integration Guide - -## Table of Contents - -1. [Overview](#1-overview) -2. [Quick Start (10 minutes)](#2-quick-start-10-minutes) -3. [Architecture & System Structure](#3-architecture--system-structure) -4. [Two Payment Modes Explained](#4-two-payment-modes-explained) -5. [Fee Model](#5-fee-model) -6. [Backend Integration (Server SDK)](#6-backend-integration-server-sdk) -7. [Frontend Integration (Client SDK)](#7-frontend-integration-client-sdk) -8. [Security & Authentication Model](#8-security--authentication-model) -9. [API Reference](#9-api-reference) -10. [Error Handling & Troubleshooting](#10-error-handling--troubleshooting) -11. [Versioning & Compatibility](#11-versioning--compatibility) -12. [Best Practices](#12-best-practices) -13. [Appendix](#13-appendix) -14. [Gaps & TODOs](#14-gaps--todos) - ---- - -## 1. Overview - -### 1.1 What is GOAT Flow - -GOAT Flow is an EVM payment infrastructure for merchants, applications, and agent-oriented workflows. It provides two payment receiving modes to meet different merchant needs. - -### 1.2 SDK Components - -| SDK | Purpose | Package | -|-----|---------|---------| -| **goatflow-sdk** | Frontend client SDK | `npm install goatflow-sdk` | -| **goatflow-sdk-server** | TypeScript backend SDK | `npm install goatflow-sdk-server` | -| **goatflow-sdk-server** (Go) | Go backend SDK (source-only) | [Clone this repo and use a local `replace`](../goatx402-sdk-server-go/README.md) | -| **goatflow-checkout** | Drop-in hosted browser checkout | `npm install goatflow-checkout` | -| **goatflow-quickpay** | QuickPay public payer / agent library and CLI | `npm install goatflow-quickpay` | - -### 1.3 Two Payment Modes - -| Mode | Identifier | Receiving Method | Fixed Fee | Use Case | -|------|------------|------------------|-----------|----------| -| **Direct Mode** | `DIRECT` | User transfers directly to merchant wallet | Lower (e.g., $0.10/tx) | Simple payments, no callbacks | -| **Delegate Mode** | `DELEGATE` | User transfers to TSS wallet; system settles on the merchant chain | Higher (e.g., $0.20/tx) | Callbacks, complex business logic | - -### 1.4 Supported Blockchain Networks - -| Network | Chain ID | DIRECT | DELEGATE | Explorer | -|---------|---------:|:------:|:--------:|----------| -| Ethereum | 1 | Yes | Yes | etherscan.io | -| Polygon | 137 | Yes | Yes | polygonscan.com | -| BSC | 56 | Yes | Yes | bscscan.com | -| Arbitrum | 42161 | Yes | Yes | arbiscan.io | -| Optimism | 10 | Yes | Yes | optimistic.etherscan.io | -| Avalanche | 43114 | Yes | Yes | snowtrace.io | -| Base | 8453 | Yes | Yes | basescan.org | -| Berachain | 80094 | Yes | Yes | berascan.com | -| X Layer | 196 | Yes | Yes | web3.okx.com/explorer/x-layer/evm | -| GOAT | 2345 | Yes | Yes | explorer.goat.network | -| Metis | 1088 | Yes | No | andromeda-explorer.metis.io | -| Tempo | 4217 | Yes | No | explore.tempo.xyz | - ---- - -## 2. Quick Start (10 minutes) - -### 2.1 Prerequisites - -1. **Merchant Account**: Contact GOAT Flow to obtain -2. **API Credentials**: `API_KEY` and `API_SECRET` -3. **Fee Balance**: Ensure sufficient USD fee balance - -### 2.2 Installation - -```bash -# Backend SDK -npm install goatflow-sdk-server - -# Frontend SDK -npm install goatflow-sdk ethers - -# QuickPay public payer / agent CLI -npm install goatflow-quickpay - -# Drop-in hosted browser checkout -npm install goatflow-checkout -``` - -### 2.3 Backend: Create Order - -```typescript -import { GoatFlowClient } from 'goatflow-sdk-server' -import type { Order as ServerOrder } from 'goatflow-sdk-server' -import type { Order as ClientOrder } from 'goatflow-sdk' - -// Initialize client -const client = new GoatFlowClient({ - baseUrl: 'https://flow-api.goat.network', - apiKey: process.env.GOATX402_API_KEY, - apiSecret: process.env.GOATX402_API_SECRET, -}) - -function toClientOrder(order: ServerOrder, fromAddress: string): ClientOrder { - // Server SDK uses fromChainId/payToChainId; browser SDK expects chainId. - return { ...order, fromAddress, chainId: order.fromChainId } -} - -// Create payment order and return the browser-SDK shape to your frontend -async function createOrder(userAddress: string, amount: string) { - const order = await client.createOrder({ - dappOrderId: `order_${Date.now()}`, // Merchant order ID - chainId: 137, // Polygon - tokenSymbol: 'USDC', - tokenContract: '0x3c499c542cef5e3811e1192ce70d8cc03d5c3359', - fromAddress: userAddress, // User wallet address - amountWei: '10000000', // 10 USDC (6 decimals) - // callbackCalldata: '0x...', // Optional: callback data (delegate mode only) - }) - - return toClientOrder(order, userAddress) -} -``` - -### 2.4 Frontend: Execute Payment - -```typescript -import { PaymentHelper, type Order as ClientOrder } from 'goatflow-sdk' -import { ethers } from 'ethers' - -async function executePayment(order: ClientOrder) { - // Connect wallet - const provider = new ethers.BrowserProvider(window.ethereum) - const signer = await provider.getSigner() - const payment = new PaymentHelper(signer) - - // If callback sign request exists (delegate mode), sign first - if (order.calldataSignRequest) { - const signature = await payment.signCalldata(order) - await submitSignatureToBackend(order.orderId, signature) - } - - // Execute payment - const result = await payment.pay(order) - - if (result.success) { - console.log('Payment successful:', result.txHash) - } -} -``` - -### 2.5 Verify Integration - -```typescript -// Query order status -const status = await client.getOrderStatus(orderId) - -// Order status flow -// CHECKOUT_VERIFIED → PAYMENT_CONFIRMED → INVOICED -``` - ---- - -## 3. Architecture & System Structure - -### 3.1 System Architecture - -``` -┌─────────────────────────────────────────────────────────────────┐ -│ Merchant System │ -│ ┌──────────────────┐ ┌──────────────────┐ │ -│ │ Merchant Backend │ │ Merchant Frontend │ │ -│ │ (Server SDK) │ │ (Client SDK) │ │ -│ └────────┬─────────┘ └────────┬─────────┘ │ -└───────────┼────────────────────────────┼────────────────────────┘ - │ │ - │ HMAC Signature Auth │ Wallet Interaction - ▼ ▼ -┌───────────────────────────────────────────────────────────────────┐ -│ GOAT Flow Platform │ -│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ -│ │ API Gateway │ │ Payment Eng │ │ TSS Gateway │ │ -│ └─────────────┘ └─────────────┘ └─────────────┘ │ -│ │ -│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ -│ │ EVM Watcher │ │ Orchestrator│ │ Fee System │ │ -│ └─────────────┘ └─────────────┘ └─────────────┘ │ -└───────────────────────────────────────────────────────────────────┘ - │ │ - ▼ ▼ -┌───────────────────────────────────────────────────────────────────┐ -│ Blockchain Networks │ -│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ -│ │ Ethereum │ │ Polygon │ │ BSC │ │ GOAT │ │ -│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │ -└───────────────────────────────────────────────────────────────────┘ -``` - -### 3.2 Data Flow Overview - -```mermaid -sequenceDiagram - participant User as User - participant MFE as Merchant Frontend - participant MBE as Merchant Backend - participant API as GOAT Flow API - participant Chain as Blockchain - - MBE->>API: 1. Create Order (Server SDK) - API-->>MBE: 2. Return Order (with payToAddress) - MBE-->>MFE: 3. Pass order info - MFE->>User: 4. Display payment UI - User->>MFE: 5. Confirm payment - MFE->>Chain: 6. Send token transfer (Client SDK) - Chain-->>API: 7. Transfer event detected - API->>API: 8. Process payment confirmation - API-->>MBE: 9. Webhook notification (optional) -``` - ---- - -## 4. Two Payment Modes Explained - -### 4.1 Direct Mode (DIRECT) - -**Overview**: User directly transfers tokens to merchant's wallet address, without GOAT Flow intermediation. - -**Features**: -- Simplest payment method -- Lowest fixed fee -- No TSS wallet involvement -- No callback support - -**Payment Flow**: - -```mermaid -sequenceDiagram - participant User as User Wallet - participant SDK as Client SDK - participant Token as Token Contract - participant Merchant as Merchant Wallet - participant API as GOAT Flow API - - SDK->>Token: 1. Check balance - Token-->>SDK: Balance sufficient - SDK->>User: 2. Request signature - User->>Token: 3. transfer(merchant, amount) - Token-->>Merchant: 4. Tokens arrive directly - Token-->>SDK: 5. Transaction receipt - API->>API: 6. Watcher detects transfer - API->>API: 7. Update order status -``` - -**Flow Types**: `ERC20_DIRECT` - -**Code Example**: - -```typescript -// Backend creates order -const order = await client.createOrder({ - dappOrderId: 'order_001', - chainId: 137, - tokenSymbol: 'USDC', - tokenContract: '0x3c499c542cef5e3811e1192ce70d8cc03d5c3359', - fromAddress: userWallet, - amountWei: '10000000', - // No callbackCalldata - Direct mode -}) - -// order.flow = 'ERC20_DIRECT' -// order.payToAddress = Merchant wallet address -``` - ---- - -### 4.2 Delegate Mode (DELEGATE) - -**Overview**: The user transfers tokens to a TSS wallet on the selected source -chain. GOAT Flow then pays out and optionally executes the callback on the -merchant's configured callback/settlement chain, which may be the same chain. - -**Features**: -- Supports callback functionality (execute merchant contracts) -- Higher fixed fee (includes TSS payout gas cost) -- TSS multi-sig wallet ensures fund security -- Supports complex business logic -- Requires Permit2 / EIP-3009 support and an approved callback contract on the merchant chain -- Supports eligible cross-chain source payments while the merchant callback chain remains fixed - -**Payment Flow**: - -```mermaid -sequenceDiagram - participant User as User Wallet - participant SDK as Client SDK - participant Token as Token Contract - participant TSS as TSS Wallet - participant API as GOAT Flow API - participant Merchant as Merchant Wallet - - SDK->>Token: 1. Check balance - Token-->>SDK: Balance sufficient - - Note over SDK,User: If callback sign request exists - SDK->>User: 2. Request EIP-712 signature - User-->>SDK: 3. Return signature - SDK->>API: 4. Submit signature - - SDK->>User: 5. Request transfer signature - User->>Token: 6. transfer(TSS, amount) - Token-->>TSS: 7. Tokens arrive at TSS - Token-->>SDK: 8. Transaction receipt - - API->>API: 9. Watcher detects transfer - API->>API: 10. Create Payout task - API->>TSS: 11. Request TSS signature - TSS-->>API: 12. Return signature - API->>Token: 13. Execute Payout (with callback) - Token-->>Merchant: 14. Tokens arrive -``` - -**Flow Types**: `ERC20_3009`, `ERC20_APPROVE_XFER` - -**Code Example**: - -```typescript -// Backend creates order (with callback) -const order = await client.createOrder({ - dappOrderId: 'order_002', - chainId: 137, - tokenSymbol: 'USDC', - tokenContract: '0x3c499c542cef5e3811e1192ce70d8cc03d5c3359', - fromAddress: userWallet, - amountWei: '10000000', - callbackCalldata: '0x...', // Callback data (optional) -}) - -// order.flow = 'ERC20_3009' or 'ERC20_APPROVE_XFER' -// order.payToAddress = TSS wallet address -// order.calldataSignRequest = EIP-712 sign request (if callback exists) -``` - ---- - -### 4.3 Mode Comparison - -| Feature | Direct Mode (DIRECT) | Delegate Mode (DELEGATE) | -|---------|----------------------|--------------------------| -| **Receiving Address** | Merchant wallet | TSS wallet | -| **Fixed Fee** | Lower (e.g., $0.10) | Higher (e.g., $0.20) | -| **Funds Arrival** | Immediate after user transfer | After TSS Payout | -| **Callback Support** | ❌ Not supported | ✅ Supported | -| **Use Case** | Simple payments | Complex business logic | -| **Gas Cost** | User only | User + TSS both need Gas | -| **Chain relation** | Payment and receiving chain are the same | Source chain may differ from the single merchant callback chain | - ---- - -## 5. Fee Model - -### 5.1 Fee Structure - -GOAT Flow uses a **fixed fee** model (not percentage), charged per order. - -| Fee Type | Direct Mode | Delegate Mode | Description | -|----------|-------------|---------------|-------------| -| Order Fee | `fee_usd_direct` | `fee_usd_delegate` | Fixed USD fee configured per chain | -| Default Fee | $0.10/tx | $0.20/tx | Customizable per chain | - -**Why is Delegate Mode fee higher?** -- TSS needs to execute Payout transaction -- Additional on-chain gas cost -- Multi-signature security mechanism cost - -### 5.2 Fee Balance System - -Merchants need to **pre-fund a USD fee balance**, deducted when orders are created. - -``` -┌─────────────────────────────────────────────────────┐ -│ Fee Balance Flow │ -├─────────────────────────────────────────────────────┤ -│ │ -│ 1. Operator topup → merchant_fee_balance += $100 │ -│ │ -│ 2. Create order → Check balance │ -│ ├─ Insufficient → Return HTTP 400 error │ -│ └─ Sufficient → Charge fee, create order │ -│ │ -│ 3. Payment successful → Fee consumed (no refund) │ -│ │ -│ 4. Order expired → Fee refunded → balance += fee │ -│ │ -└─────────────────────────────────────────────────────┘ -``` - -### 5.3 Fee Calculation Examples - -```typescript -// Direct mode order -const directOrder = { - chainId: 137, // Polygon - mode: 'DIRECT', - fee: '$0.10', // Fixed fee -} - -// Delegate mode order -const delegateOrder = { - chainId: 137, // Polygon - mode: 'DELEGATE', - fee: '$0.20', // Fixed fee (includes Payout Gas) -} - -// Whether order amount is $10 or $10,000, fee is fixed -``` - -### 5.4 Insufficient Balance Handling - -```typescript -try { - const order = await client.createOrder(orderParams) -} catch (error) { - if (error.status === 400 && error.message?.includes('insufficient fee balance')) { - // Fee balance insufficient - console.error('Insufficient fee balance, please contact operator to topup') - // error.message: "Insufficient fee balance" - } -} -``` - ---- - -## 6. Backend Integration (Server SDK) - -### 6.1 Initialization - -```typescript -import { GoatFlowClient } from 'goatflow-sdk-server' - -const client = new GoatFlowClient({ - baseUrl: process.env.GOATX402_API_URL, // API URL - apiKey: process.env.GOATX402_API_KEY, // API Key - apiSecret: process.env.GOATX402_API_SECRET, // API Secret -}) -``` - -### 6.2 Create Order - -```typescript -interface CreateOrderParams { - dappOrderId: string // Merchant order ID (unique) - chainId: number // Chain ID - tokenSymbol: string // Token symbol - tokenContract: string // Token contract address - fromAddress: string // Payer address - amountWei: string // Amount (smallest unit) - callbackCalldata?: string // Callback data (delegate mode optional) -} - -const order = await client.createOrder({ - dappOrderId: `order_${Date.now()}`, - chainId: 137, - tokenSymbol: 'USDC', - tokenContract: '0x3c499c542cef5e3811e1192ce70d8cc03d5c3359', - fromAddress: '0x742d35Cc6634C0532925a3b844Bc...', - amountWei: '10000000', // 10 USDC -}) -``` - -### 6.3 Order Response - -```typescript -interface OrderResponse { - orderId: string // GOAT Flow order ID - flow: PaymentFlow // Payment flow type - payToAddress: string // Receiving address - expiresAt: number // Expiration time (Unix timestamp) - calldataSignRequest?: { // EIP-712 sign request (if callback exists) - domain: EIP712Domain - types: Record - primaryType: string - message: SignMessage - } -} -``` - -The raw create-order response is an x402 challenge. `HTTP 402` is the expected success path for order creation, and the `PAYMENT-REQUIRED` header contains the base64-encoded JSON challenge body. - -```http -HTTP/1.1 402 Payment Required -PAYMENT-REQUIRED: eyJ4NDAyVmVyc2lvbiI6Miwi... -Content-Type: application/json -``` - -```json -{ - "x402Version": 2, - "resource": { - "url": "https://flow-api.goat.network/api/v1/orders/{order_id}", - "description": "Payment: 10000000 USDC", - "mimeType": "application/json" - }, - "accepts": [ - { - "scheme": "exact", - "network": "eip155:137", - "amount": "10000000", - "asset": "0x3c499c542cef5e3811e1192ce70d8cc03d5c3359", - "payTo": "0xMerchantOrTssAddress", - "maxTimeoutSeconds": 585, - "extra": { - "flow": "ERC20_DIRECT", - "tokenSymbol": "USDC" - } - } - ], - "extensions": { - "goatx402": { - "destinationChain": "eip155:137", - "expiresAt": 1760000600, - "paymentMethod": "transfer", - "receiveType": "DIRECT" - } - }, - "order_id": "{order_id}", - "flow": "ERC20_DIRECT", - "token_symbol": "USDC" -} -``` - -`maxTimeoutSeconds` is the order's remaining lifetime when the challenge is generated (clamped to at least 1 second), not a fixed 600-second window. - -For `ERC20_3009`, `accepts[0].scheme` is `exact-eip3009`. Whenever the order carries a `calldata_sign_request`—including Permit2-style DELEGATE callbacks—`extensions.goatx402.signatureEndpoint` points to `POST /api/v1/orders/{order_id}/calldata-signature`. - -### 6.4 Query Order Status - -```typescript -const status = await client.getOrderStatus(orderId) - -// status.status possible values: -// - 'CHECKOUT_VERIFIED' : Awaiting payment -// - 'PAYMENT_CONFIRMED' : Payment confirmed -// - 'INVOICED' : Complete (invoiced) -// - 'EXPIRED' : Expired -// - 'FAILED' : Failed -// - 'CANCELLED' : Cancelled before payment -``` - -### 6.5 Submit Callback Signature - -```typescript -// After user signs on frontend, submit to backend -await client.submitCalldataSignature(orderId, '0x...') -``` - -### 6.6 Get Order Proof - -```typescript -// Get proof after payment completion -const proof = await client.getOrderProof(orderId) - -// proof contains the on-chain tx hash plus an unsigned checksum over a -// subset of the payload fields (not the whole record). -// Verify proof.payload.tx_hash on-chain when independent proof is required. -``` - ---- - -## 7. Frontend Integration (Client SDK) - -### 7.1 Installation & Initialization - -```typescript -import { - PaymentHelper, - ERC20Token, - parseUnits, - formatUnits, - type Order as ClientOrder, -} from 'goatflow-sdk' -import { ethers } from 'ethers' - -// Connect wallet -const provider = new ethers.BrowserProvider(window.ethereum) -await provider.send('eth_requestAccounts', []) -const signer = await provider.getSigner() - -// Create PaymentHelper -const payment = new PaymentHelper(signer) -``` - -The server SDK returns `fromChainId` and `payToChainId`. Before passing an order to the browser SDK, map `chainId` from `fromChainId` and include the payer address used at order creation. - -```typescript -import type { Order as ServerOrder } from 'goatflow-sdk-server' - -function toClientOrder(serverOrder: ServerOrder, fromAddress: string): ClientOrder { - return { ...serverOrder, fromAddress, chainId: serverOrder.fromChainId } -} -``` - -### 7.2 Complete Payment Flow - -```typescript -async function processPayment(order: ClientOrder) { - const provider = new ethers.BrowserProvider(window.ethereum) - const signer = await provider.getSigner() - const payment = new PaymentHelper(signer) - - // 1. Verify network - const network = await provider.getNetwork() - if (Number(network.chainId) !== order.chainId) { - await provider.send('wallet_switchEthereumChain', [ - { chainId: `0x${order.chainId.toString(16)}` }, - ]) - } - - // 2. Check balance - const balance = await payment.getTokenBalance(order.tokenContract) - const required = BigInt(order.amountWei) - if (balance < required) { - throw new Error('Insufficient balance') - } - - // 3. If callback sign request exists (delegate mode), sign first - if (order.calldataSignRequest) { - const signature = await payment.signCalldata(order) - // Submit signature to backend - await fetch('/api/orders/sign', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ orderId: order.orderId, signature }), - }) - } - - // 4. Execute payment - const result = await payment.pay(order) - - if (result.success) { - console.log('Transaction hash:', result.txHash) - // Notify backend payment initiated - await fetch('/api/orders/notify', { - method: 'POST', - body: JSON.stringify({ orderId: order.orderId, txHash: result.txHash }), - }) - } - - return result -} -``` - -### 7.3 React Hook Example - -```typescript -// hooks/useGoatFlowPayment.ts -import { useState, useCallback } from 'react' -import { PaymentHelper, type Order as ClientOrder, type PaymentResult } from 'goatflow-sdk' -import { ethers } from 'ethers' - -export function useGoatFlowPayment() { - const [loading, setLoading] = useState(false) - const [error, setError] = useState(null) - - const pay = useCallback(async (order: ClientOrder): Promise => { - setLoading(true) - setError(null) - - try { - const provider = new ethers.BrowserProvider(window.ethereum) - const signer = await provider.getSigner() - const payment = new PaymentHelper(signer) - - // Handle callback signature - if (order.calldataSignRequest) { - const sig = await payment.signCalldata(order) - await submitSignature(order.orderId, sig) - } - - const result = await payment.pay(order) - - if (!result.success) { - setError(result.error || 'Payment failed') - } - - return result - } catch (err: any) { - const msg = err.code === 'ACTION_REJECTED' - ? 'User cancelled operation' - : err.message - setError(msg) - return null - } finally { - setLoading(false) - } - }, []) - - return { pay, loading, error } -} -``` - -### 7.4 Pay Button Component - -```tsx -// components/PayButton.tsx -import { useGoatFlowPayment } from '../hooks/useGoatFlowPayment' -import type { Order as ClientOrder } from 'goatflow-sdk' - -interface PayButtonProps { - order: ClientOrder - onSuccess: (txHash: string) => void - onError: (error: string) => void -} - -export function PayButton({ order, onSuccess, onError }: PayButtonProps) { - const { pay, loading, error } = useGoatFlowPayment() - - const handleClick = async () => { - const result = await pay(order) - if (result?.success && result.txHash) { - onSuccess(result.txHash) - } else if (result?.error) { - onError(result.error) - } - } - - return ( - - ) -} -``` - ---- - -## 8. Security & Authentication Model - -### 8.1 Backend API Authentication (HMAC-SHA256) - -```typescript -// Server SDK handles signature automatically -// Signature algorithm: -// 1. Add api_key, timestamp, and nonce to request body/query params -// 2. Drop empty values and `sign` -// 3. Sort params by ASCII key -// 4. Concatenate: key1=value1&key2=value2 -// 5. HMAC-SHA256(secret, payload) - -// Request headers: -// X-API-Key: {apiKey} -// X-Timestamp: {unixSeconds} -// X-Nonce: {uniqueRequestNonce} -// X-Sign: {hmacSignature} -``` - -### 8.2 EIP-712 Signature (Callback Authorization) - -```typescript -// User signature authorizes callback execution. -// Sign the calldataSignRequest returned by createOrder; do not rebuild it. -if (!order.calldataSignRequest) { - throw new Error('Order does not require calldata signature') -} - -const { EIP712Domain, ...types } = order.calldataSignRequest.types -const signature = await signer.signTypedData( - order.calldataSignRequest.domain, - types, - order.calldataSignRequest.message -) - -// order.calldataSignRequest.domain is returned by GOAT Flow and must match -// the deployed MerchantCallback EIP-712 domain. MerchantCallback.initialize() -// defaults to name "GoatX402 Pay Callback" and version "1". -``` - -Returned callback message shapes: - -```typescript -type Eip3009CallbackData = { - token: string - owner: string - payer: string - amount: string - orderId: string // bytes32 keccak256(orderId) - calldataNonce: string // replay protection nonce - deadline: string - calldataHash: string // keccak256(callback calldata) -} - -type Permit2CallbackData = Eip3009CallbackData & { - permit2: string -} -``` - -### 8.3 Security Mechanisms - -| Mechanism | Description | -|-----------|-------------| -| **Nonce** | Each signature unique, prevents replay attacks | -| **Deadline** | Signature validity period, invalid after expiry | -| **Chain ID** | Bound to chain ID, prevents cross-chain replay | -| **Contract Binding** | Bound to contract address, prevents cross-contract replay | -| **Calldata Hash** | Callback data hash, prevents tampering | - ---- - -## 9. API Reference - -### 9.1 PaymentHelper Class - -| Method | Parameters | Returns | Description | -|--------|------------|---------|-------------| -| `constructor(signer)` | `ethers.Signer` | - | Initialize | -| `getAddress()` | - | `Promise` | Get wallet address | -| `pay(order)` | `Order` | `Promise` | Execute payment | -| `signCalldata(order)` | `Order` | `Promise` | Sign callback data | -| `getTokenBalance(token)` | `string` | `Promise` | Query token balance | -| `getTokenAllowance(token, spender)` | `string, string` | `Promise` | Query allowance | -| `approveToken(token, spender, amount, options?)` | `string, string, bigint, ApprovalOptions?` | `Promise` | Exact approval by default; `unlimited: true` opts into unlimited approval; resolves after confirmation, or with `undefined` when the allowance already equals the requested value (including revoking an already-zero allowance); changing a non-zero allowance first simulates the direct write via eth_call — standard ERC20s get a single approval with no reset window, only USDT-style tokens fall back to a confirmed approve(0) reset first | - -### 9.2 ERC20Token Class - -| Method | Parameters | Returns | Description | -|--------|------------|---------|-------------| -| `constructor(address, signerOrProvider)` | `string, Signer|Provider` | - | Initialize | -| `balanceOf(address)` | `string` | `Promise` | Query balance | -| `allowance(owner, spender)` | `string, string` | `Promise` | Query allowance | -| `decimals()` | - | `Promise` | Get decimals | -| `symbol()` | - | `Promise` | Get symbol | -| `approve(spender, amount)` | `string, bigint` | `Promise` | Approve | -| `transfer(to, amount)` | `string, bigint` | `Promise` | Transfer | -| `ensureApproval(owner, spender, amount, options?)` | `string, string, bigint, ApprovalOptions?` | `Promise<{needed, tx?, resetTx?}>` | Judge sufficiency against the requested amount; otherwise simulate the direct write first and only use a confirmed approve(0) reset for USDT-style tokens; `unlimited` only changes the value written | -| `setApproval(owner, spender, amount, options?)` | `string, string, bigint, ApprovalOptions?` | `Promise` | Set an allowance explicitly; no transaction when it already equals the target; simulate non-zero direct writes first, fall back to approve(0) only when needed, and safely follow wallet fee-bumps | - -### 9.3 Utility Functions - -```typescript -// Amount format conversion -import { parseUnits, formatUnits } from 'goatflow-sdk' - -// Human readable → Wei -const amountWei = parseUnits('100.5', 6) // 100500000n - -// Wei → Human readable -const amount = formatUnits(100500000n, 6) // "100.5" -``` - -### 9.4 QuickPay Public API and CLI - -QuickPay exposes public, credential-less endpoints for browser payers, CLIs, and agents. - -| Surface | Endpoint | Purpose | -|---------|----------|---------| -| Discovery | `GET /quickpay/v1/merchants/{merchant_id}` | Public merchant payment capability | -| Agent instructions | `GET /quickpay/{merchant_id}/agent.md` | Prompt-injection-hardened agent guide | -| Machine manifest | `GET /quickpay/{merchant_id}/manifest.json` | `goatx402.quickpay.v1` manifest | -| Create x402 session | `POST /quickpay/v1/x402/sessions` | Create or reuse a payable QuickPay session | -| Query x402 session | `GET /quickpay/v1/x402/sessions/{session_id}` | Poll public session status | - -Session creation request: - -```json -{ - "merchant_id": "merchant_123", - "payer_addr": "0xUserWalletAddress", - "chain_id": 137, - "token_contract": "0x3c499c542cef5e3811e1192ce70d8cc03d5c3359", - "amount_wei": "10000000", - "memo": "invoice-123", - "idempotency_key": "invoice-123:user-456" -} -``` - -When the session is payable, the response includes an embedded `x402` object with the same `x402Version: 2`, `accepts[0].network = eip155:`, `scheme = exact`, `amount`, `asset`, and `payTo` fields described in Section 6.3. - -For a fixed-price product, send `product_key` instead of `amount_wei`. The -server computes the atomic amount from the product's token-agnostic decimal price -and the selected token decimals. - -QuickPay package and CLI: - -```bash -npx goatflow-quickpay inspect https://flow-api.goat.network/quickpay/merchant_123/agent.md -npx goatflow-quickpay pay-x402 https://flow-api.goat.network/quickpay/merchant_123/agent.md \ - --amount 10 --token-contract 0xToken --chain 137 --idempotency-key invoice-123 -npx goatflow-quickpay pay-product https://flow-api.goat.network/quickpay/merchant_123/agent.md \ - --product mug --token-contract 0xToken --chain 137 -npx goatflow-quickpay pay-mpp https://flow-api.goat.network/quickpay/merchant_123/agent.md \ - --route -``` - -The library exports `QuickPayClient`, `inspect`, `payX402`, `payProduct`, -`payMpp`, `loadManifest`, and `EthersPaymentBackend`. - -### 9.5 Hosted Checkout - -For a platform-hosted payment page, use `goatflow-checkout`: - -```typescript -import { GoatCheckout } from 'goatflow-checkout' - -const goat = GoatCheckout({ origin: 'https://pay.goat.network' }) -goat.open({ merchant: 'merchant_123', productKey: 'mug' }) -``` - -Dynamic DIRECT and every DELEGATE checkout start on the backend: - -```typescript -const session = await client.createCheckoutSession({ - checkoutType: 'DIRECT', - price: '19.95', - clientReferenceId: 'cart_123', -}) - -// Browser: -goat.open({ checkoutId: session.checkoutId }) -``` - -DELEGATE supports cross-chain decimal `price` mode and the compatibility -single-chain `fixedAmountWei` mode. Fulfill from -`quickpay.checkout.completed`, never from the browser callback alone. See -[Hosted Checkout](x402-checkout.md). - -### 9.6 Type Definitions - -```typescript -// Payment flow types -type PaymentFlow = - | 'ERC20_DIRECT' // Direct mode - | 'ERC20_3009' // Delegate mode (EIP-3009) - | 'ERC20_APPROVE_XFER' // Delegate mode (Permit2) - -// Order status -type OrderStatus = - | 'CHECKOUT_VERIFIED' // Awaiting payment - | 'PAYMENT_CONFIRMED' // Payment confirmed - | 'INVOICED' // Complete - | 'EXPIRED' // Expired - | 'FAILED' // Failed - | 'CANCELLED' // Cancelled before payment - -// Server SDK order interface -interface ServerOrder { - orderId: string - flow: PaymentFlow - tokenSymbol: string - tokenContract: string - payToAddress: string - fromChainId: number - payToChainId: number - amountWei: string - expiresAt: number - calldataSignRequest?: CalldataSignRequest -} - -// Frontend SDK order interface -interface Order extends Omit { - fromAddress: string - chainId: number -} - -function toClientOrder(order: ServerOrder, fromAddress: string): Order { - return { ...order, fromAddress, chainId: order.fromChainId } -} - -// Payment result -interface PaymentResult { - success: boolean - txHash?: string - error?: string -} -``` - ---- - -## 10. Error Handling & Troubleshooting - -### 10.1 Common Error Codes - -| Error Code | Description | Solution | -|------------|-------------|----------| -| `400` | Request parameter or business rule error, including insufficient fee balance or duplicate `dappOrderId` | Check parameter format, fee balance, and uniqueness | -| `401` | Authentication failed | Check API Key/Secret and signature | -| `402` | Payment Required x402 challenge from successful order creation | Treat as expected order creation response | -| `404` | Order not found | Check order ID | -| `503` | QuickPay session creation temporarily unavailable | Retry after the merchant restores fee/config availability | - -### 10.2 Frontend Common Issues - -#### Issue 1: Fee Balance Insufficient (HTTP 400) - -```typescript -try { - const order = await client.createOrder(params) -} catch (error) { - if (error.status === 400 && error.message?.includes('insufficient fee balance')) { - // Display prompt - alert('Merchant fee balance insufficient, please contact admin to topup') - } -} -``` - -#### Issue 2: User Rejected Transaction - -```typescript -try { - const result = await payment.pay(order) -} catch (error) { - if (error.code === 'ACTION_REJECTED') { - console.log('User cancelled transaction') - } -} -``` - -#### Issue 3: Token Balance Insufficient - -```typescript -const balance = await payment.getTokenBalance(order.tokenContract) -const required = BigInt(order.amountWei) - -if (balance < required) { - const token = new ERC20Token(order.tokenContract, provider) - const symbol = await token.symbol() - const decimals = await token.decimals() - - alert(`Insufficient balance: need ${formatUnits(required, decimals)} ${symbol}`) -} -``` - -#### Issue 4: Network Mismatch - -```typescript -const network = await provider.getNetwork() -if (Number(network.chainId) !== order.chainId) { - try { - await provider.send('wallet_switchEthereumChain', [ - { chainId: `0x${order.chainId.toString(16)}` }, - ]) - } catch (error) { - alert('Please switch to correct network') - } -} -``` - -#### Issue 5: Order Expired - -```typescript -if (Date.now() / 1000 > order.expiresAt) { - alert('Order expired, please create a new one') - // Create new order -} -``` - -### 10.3 Debug Checklist - -```typescript -async function debugPayment(order: Order) { - console.log('=== Payment Debug ===') - - // 1. Wallet connection - const address = await payment.getAddress() - console.log('Wallet address:', address) - - // 2. Network check - const network = await provider.getNetwork() - console.log('Current network:', network.chainId) - console.log('Order network:', order.chainId) - console.log('Network match:', Number(network.chainId) === order.chainId) - - // 3. Order validity - const now = Date.now() / 1000 - console.log('Current time:', now) - console.log('Expiration:', order.expiresAt) - console.log('Order valid:', now < order.expiresAt) - - // 4. Token balance - const balance = await payment.getTokenBalance(order.tokenContract) - console.log('Token balance:', balance.toString()) - console.log('Required amount:', order.amountWei) - console.log('Sufficient:', balance >= BigInt(order.amountWei)) - - // 5. Payment mode - console.log('Payment mode:', order.flow) - console.log('Pay to address:', order.payToAddress) - console.log('Signature required:', !!order.calldataSignRequest) - - console.log('=== Debug Complete ===') -} -``` - ---- - -## 11. Versioning & Compatibility - -### 11.1 SDK Versions - -Use the package manifests as the version source of truth: - -| Package | Source | -|---------|--------| -| `goatflow-sdk` | `goatx402-sdk/package.json` | -| `goatflow-sdk-server` | `goatx402-sdk-server-ts/package.json` | -| `goatflow-checkout` | `goatx402-checkout/package.json` | -| `goatflow-quickpay` | `goatx402-quickpay/package.json` | - -### 11.2 Dependency Versions - -| Dependency | Version Requirement | -|------------|---------------------| -| ethers | ^6.9.0 | -| typescript | ^5.3.0 | -| node | >=18 | - -### 11.3 Browser Compatibility - -| Browser | Minimum Version | -|---------|-----------------| -| Chrome | 80+ | -| Firefox | 75+ | -| Safari | 14+ | -| Edge | 80+ | - ---- - -## 12. Best Practices - -### 12.1 Backend Best Practices - -```typescript -// ✅ Use environment variables -const client = new GoatFlowClient({ - baseUrl: process.env.GOATX402_API_URL, - apiKey: process.env.GOATX402_API_KEY, - apiSecret: process.env.GOATX402_API_SECRET, -}) - -// ✅ Validate order amount -function validateAmount(amount: string, minUsd: number, maxUsd: number) { - const value = parseFloat(amount) - if (value < minUsd || value > maxUsd) { - throw new Error(`Amount must be between $${minUsd} - $${maxUsd}`) - } -} - -// ✅ Handle Webhook notifications -app.post('/webhook/goatx402', async (req, res) => { - const { orderId, status, txHash } = req.body - - // Verify signature - if (!verifyWebhookSignature(req)) { - return res.status(401).send('Invalid signature') - } - - // Update order status - await updateOrderStatus(orderId, status) - - res.status(200).send('OK') -}) -``` - -### 12.2 Frontend Best Practices - -```typescript -// ✅ Cache Provider and Signer -let cachedPayment: PaymentHelper | null = null - -async function getPaymentHelper() { - if (!cachedPayment) { - const provider = new ethers.BrowserProvider(window.ethereum) - const signer = await provider.getSigner() - cachedPayment = new PaymentHelper(signer) - } - return cachedPayment -} - -// ✅ Display user-friendly errors -function getErrorMessage(error: any): string { - if (error.code === 'ACTION_REJECTED') return 'You cancelled the transaction' - if (error.message?.includes('insufficient')) return 'Insufficient balance' - if (error.status === 400 && error.message?.includes('insufficient fee balance')) return 'Merchant fee balance insufficient' - return 'Payment failed, please try again' -} - -// ✅ Transaction status tracking -function PaymentStatus({ txHash, chainId }: { txHash: string, chainId: number }) { - const explorerUrl = getExplorerUrl(chainId, txHash) - return ( - - View transaction details - - ) -} -``` - -### 12.3 Security Best Practices - -```typescript -// ✅ Get order from backend, don't construct on frontend -const order = await fetch('/api/orders', { - method: 'POST', - body: JSON.stringify({ productId }), -}).then(r => r.json()) - -// ✅ Validate order expiration -if (Date.now() / 1000 > order.expiresAt) { - throw new Error('Order expired') -} - -// ✅ Validate payer address -if (order.fromAddress.toLowerCase() !== userAddress.toLowerCase()) { - throw new Error('Order address mismatch') -} - -// ❌ Don't hardcode amounts on frontend -const order = { amountWei: '1000000' } // Dangerous! - -// ❌ Don't store sensitive info -localStorage.setItem('apiSecret', secret) // Dangerous! -``` - ---- - -## 13. Appendix - -### 13.1 Example Project Structure - -``` -my-payment-app/ -├── backend/ -│ ├── src/ -│ │ ├── routes/ -│ │ │ └── orders.ts # Order API -│ │ ├── services/ -│ │ │ └── goatx402.ts # GOAT Flow service -│ │ └── index.ts -│ └── package.json -├── frontend/ -│ ├── src/ -│ │ ├── hooks/ -│ │ │ └── usePayment.ts # Payment Hook -│ │ ├── components/ -│ │ │ └── PayButton.tsx # Pay Button -│ │ └── App.tsx -│ └── package.json -└── README.md -``` - -### 13.2 Token Contract Addresses - -| Token | Chain | Contract Address | -|-------|-------|------------------| -| USDC | Ethereum | `0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48` | -| USDC | Polygon | `0x3c499c542cef5e3811e1192ce70d8cc03d5c3359` | -| USDT | Ethereum | `0xdAC17F958D2ee523a2206206994597C13D831ec7` | -| USDT | Polygon | `0xc2132D05D31c914a87C6611C10748AEb04B58e8F` | - -### 13.3 Chain RPC Configuration - -```typescript -const CHAIN_RPCS: Record = { - 1: process.env.RPC_ETHEREUM!, - 137: process.env.RPC_POLYGON!, - 56: process.env.RPC_BSC!, - 42161: process.env.RPC_ARBITRUM!, - 10: process.env.RPC_OPTIMISM!, - 43114: process.env.RPC_AVALANCHE!, - 8453: process.env.RPC_BASE!, - 80094: process.env.RPC_BERACHAIN!, - 196: process.env.RPC_X_LAYER!, - 2345: process.env.RPC_GOAT!, - 1088: process.env.RPC_METIS!, - 4217: process.env.RPC_TEMPO!, -} -``` - ---- - -## 14. Gaps & TODOs - -### 14.1 Documentation Gaps - -| Content | Priority | -|---------|----------| -| Complete Demo Project | High | -| Webhook Integration Guide | High | -| Go SDK Detailed Docs | Medium | -| Production Chain Configuration | Medium | - -### 14.2 SDK Features TODO - -| Feature | Status | -|---------|--------| -| Order Status Polling | Available through `getOrderStatus()` and `waitForConfirmation()` | -| WebSocket Real-time Notifications | ⏳ | -| Batch Payments | ⏳ | - ---- - -*Last verified against the repository implementation: 2026-06-26* diff --git a/docs/x402-onboarding-guide.md b/docs/x402-onboarding-guide.md deleted file mode 100644 index da32717..0000000 --- a/docs/x402-onboarding-guide.md +++ /dev/null @@ -1,368 +0,0 @@ -# GOAT Flow Onboarding Guide - -> An onboarding guide for Merchants, Builders, and Operations / PM teams. -> This document walks you through the full process of getting started with GOAT Flow, from merchant registration and receiving setup to API credentials, SDK integration, testing, and launch. - ---- - -## Who This Guide Is For - -This guide is intended for the following audiences: - -- **Merchant / business owner**: to complete merchant registration, confirm receiving setup, and drive launch readiness -- **Builder / developer**: to complete API integration, SDK setup, testing, and implementation -- **Operations / PM**: to understand the full workflow, coordinate dependencies, and confirm launch readiness - ---- - -## Overall Onboarding Flow - -The recommended onboarding flow for GOAT Flow is: - -1. **Step 01 — Register Merchant Account** -2. **Step 02 — Configure Receiving Address** -3. **Step 03 — Generate API Credentials** -4. **Step 04 — Integrate x402 SDK** -5. **Step 05 — Test & Go Live** - -If this is your first integration, it is recommended to complete these steps in order. - ---- - -# Step 01 — Register Merchant Account - -## Goal - -Create your merchant account and obtain access for the next stages of setup and integration. - -## What You Will Complete - -- Create a Merchant Account -- Fill in the required basic information -- Confirm merchant identity details -- Get access to continue with receiving setup and API integration - -## Merchant Registration Link - -- **Merchant Registration** - https://x402-merchant.goat.network/ - -## Detailed Registration Guide - -For a more detailed registration guide, please refer to: - -- [GOAT Flow Merchant Guide (English)](./merchant-guide.md) - -## Information You Should Prepare in Advance - -- Merchant name / project name -- Contact information -- Basic business description -- Your expected x402 use case (for example: API monetization, paid content, gaming payments, or agent payments) - -## What You Should Have After This Step - -- A created Merchant Account -- A merchant identity that can continue into configuration -- Access to proceed to the receiving setup step -- A clear account-security plan, including whether each user should enable self-service 2FA in Settings - ---- - -# Step 02 — Configure Receiving Address - -## Goal - -Complete the merchant receiving setup and define how settlement should be received. - -## Why This Step Matters - -Before integration and launch, you must clearly define: - -- where funds should be received -- which chain should be used for settlement -- which token should be used for settlement -- whether your payment flow requires more advanced post-payment execution - -## What This Step Usually Includes - -### 1. Configure Settlement Chain -Confirm which chain the merchant wants to receive settlement on. - -### 2. Configure Settlement Token -Confirm which token the merchant wants to receive, such as USDC or USDT. - -### 3. Configure Receiving Address -Set and verify the final receiving address. - -### 4. Select Payment Mode -Choose the payment mode based on your business scenario: - -- **DIRECT**: the user pays directly to the merchant address -- **DELEGATE**: TSS-assisted EVM settlement using EIP-3009 or Permit2 and an - approved callback contract on one merchant settlement chain; eligible payment - source chains may differ - -### 5. Configure Callback / Execution Logic (if applicable) -If your flow requires payment-triggered on-chain execution, complete the related callback or execution configuration here. - -If your flow needs public payment links or agent-native payments, use `DIRECT` and plan to enable QuickPay after receiving addresses are configured. - -## What You Should Have After This Step - -- A confirmed receiving address -- A confirmed settlement chain and token -- A confirmed payment mode -- If applicable, a registered callback / execution configuration - ---- - -# Step 03 — Generate API Credentials - -## Goal - -Generate the API credentials required by your development team to begin integration. - -## What This Step Will Complete - -- Generate API Key -- Generate API Secret -- Confirm how credentials are separated between test and production environments -- Deliver credentials securely to the backend team - -## Why This Step Matters - -API credentials are the foundation of backend integration. Without valid credentials, you cannot: - -- create orders -- query payment status -- retrieve payment proof -- call merchant-related APIs - -## Usage Requirements - -### 1. Separate Environments -Keep test and production credentials separate. Do not mix them. - -### 2. API Secret Is Server-Side Only -The `API Secret` must only be stored on the backend. It must never be exposed in frontend code or public repositories. - -### 3. Secure Storage -It is recommended that your development team store credentials in environment variables or a secure secret manager. - -## What You Should Have After This Step - -- API URL -- API Key -- API Secret -- Clear guidance on test vs production environments - -Production API integrations should use `https://flow-api.goat.network`. - ---- - -# Step 04 — Integrate x402 SDK - -## Goal - -Choose the integration path that best fits your team and complete the x402 SDK integration. - -## The Two Supported Integration Approaches - -### Option 1: Integrate Directly from the x402 GitHub Repository - -This approach is suitable for teams that: - -- have a development team that can work directly with code and technical documentation -- want to start from the SDK, sample projects, and API references -- prefer to pull the latest resources directly from GitHub - -Developers can use the GitHub repository to access: - -- the x402 SDK -- integration documentation -- API documentation -- demo examples -- related project code - -Recommended entry point: - -- **x402 GitHub Repo** - https://github.com/GOATNetwork/x402 - -If your team is already comfortable with standard frontend and backend integration, this is the most direct way to get started. - ---- - -### Option 2: Integrate Through an Agent - -This approach is suitable for teams that: - -- want to reduce manual integration overhead -- want an Agent to assist with the integration process -- want to standardize payment execution through the merchant's public QuickPay agent surface - -In this mode, the merchant's QuickPay **agent guide** is the concrete agent-readable file: - -```text -https://flow-api.goat.network/quickpay//agent.md -``` - -The same merchant also exposes a machine-readable manifest: - -```text -https://flow-api.goat.network/quickpay//manifest.json -``` - -The QuickPay `agent.md` helps the Agent understand: - -- which public endpoints are safe to call -- which tokens and chains are offered -- how to create and poll QuickPay x402 sessions -- how to pay custom amounts with `goatflow-quickpay` -- which predefined Products are available through `product_key`, when configured - -This approach is better suited for teams that want a more standardized, AI-assisted, or workflow-driven integration process. - ---- - -## No Matter Which Approach You Use, You Should Ultimately Complete - -- install and configure the x402 SDK -- use the API credentials in your integration -- create orders -- initiate payments -- query payment status -- retrieve proof for confirmed orders -- enable QuickPay and Products when public payment links or agent payments are needed -- complete test environment validation -- complete production launch preparation - -## What You Should Have After This Step - -- a usable x402 SDK integration capability -- a working baseline payment flow -- readiness to move into testing and go-live - ---- - -# Step 05 — Test & Go Live - -## Goal - -Complete testing, validation, and launch checks before going live. - -## A. Test Environment Validation - -It is recommended to verify the following in the test environment: - -- merchant configuration is correct -- API credentials are valid -- receiving address is correct -- DIRECT flow works -- DELEGATE flow works (if applicable) -- payment status updates correctly -- proof can be retrieved -- webhook notifications are received correctly (if applicable) - -## B. Pre-Launch Checklist - -### Merchant Setup -- [ ] Merchant account has been created -- [ ] Receiving address has been confirmed -- [ ] Settlement chain / token has been confirmed -- [ ] Payment mode has been confirmed -- [ ] Merchant users have reviewed 2FA and recovery-code setup - -### Technical Integration -- [ ] API key / secret has been switched to the correct environment -- [ ] Frontend payment flow has been fully tested -- [ ] Backend order creation flow has been fully tested -- [ ] Status query and proof retrieval are working correctly -- [ ] Callback configuration has been verified (if applicable) -- [ ] QuickPay `agent.md` and `manifest.json` have been tested if agent payments are in scope -- [ ] QuickPay Products have been created if fixed-price product checkout is in scope - -### Documentation and Support -- [ ] Support email is ready -- [ ] FAQ / Guide is ready -- [ ] Documentation links are accessible -- [ ] Contact path is confirmed - -### Launch Preparation -- [ ] The team has checked the **fee balance** in the Dashboard -- [ ] If needed, the fee balance has been topped up from the Topup page -- [ ] Test orders have been successfully completed -- [ ] Error messaging has been reviewed -- [ ] The support path is clearly defined - -## Fee Items That Must Be Confirmed Before Launch - -Before going live, make sure the merchant’s **fee balance** is sufficient to support order creation. - -Please check the **Dashboard** before launch to confirm: - -- the current fee balance is greater than 0 -- the balance is sufficient for initial production usage -- any required top-up has already been completed - -If the fee balance is insufficient, order creation will fail with an error such as **insufficient fee balance**. - -## Direct and Delegate Pricing Model - -x402 currently uses a **fixed-fee per order model**, not a percentage-based take rate. Fees are chain/admin configured; the values below are the documentation baseline and should be checked against the active fee configuration before launch. - -### DIRECT -- The default fixed fee is typically **$0.10 per order** -- Best suited for simple payment flows -- The user typically pays directly to the merchant address - -### DELEGATE -- The default fixed fee is typically **$0.20 per order** -- The higher fee reflects additional settlement, payout gas, and execution overhead -- Best suited for flows that require post-payment on-chain execution - -### Pricing Logic Summary -- Pricing is based on a **fixed fee per order**, not a percentage of payment amount -- **DIRECT typically defaults to $0.10 per order** -- **DELEGATE typically defaults to $0.20 per order** -- Fees may vary by chain or configuration, but the default documentation baseline uses these two tiers -- Fees are deducted from the merchant’s **fee balance** -- Fee balance is checked when an order is created -- If the order completes successfully, the fee is consumed -- If the order expires or is canceled, the corresponding fee is refunded to the fee balance - -## What You Should Have After This Step - -- test environment validation has been completed -- production configuration is ready -- documentation and support paths are ready -- your x402 payment flow is ready to go live - ---- - -# Related Documents - -It is recommended to read this guide together with: - -- [Merchant Guide](./merchant-guide.md) -- [x402 FAQ](./x402-faq.md) -- [Why x402](./why-x402.md) -- [Developer Quick Start](./x402-developer-quickstart.md) -- [API Reference](./x402-api-reference.md) -- [Agent Integration Guide](./x402-agent-integration-guide.md) -- [DApp Integration Skill](./x402-dapp-integration-prompts/SKILL.md) - ---- - -# One-Line Summary - -The GOAT Flow onboarding path can be summarized as: - -**register merchant → configure receiving → generate credentials → integrate SDK → test and go live** - -Once all five steps are complete, you have the core requirements needed to launch x402 payment capability. - ---- - -Contact email: x402support@goat.network diff --git a/goatx402-checkout/README.md b/goatx402-checkout/README.md index 0dcc2b4..018649c 100644 --- a/goatx402-checkout/README.md +++ b/goatx402-checkout/README.md @@ -1,11 +1,12 @@ # goatflow-checkout Framework-free browser SDK for GOAT Flow hosted checkout. It opens the -platform-controlled payment page in a top-level popup, tab, or full-page redirect; -wallet connection and payment happen there, not in the merchant page. +GOAT Flow-hosted checkout interface in a top-level popup, tab, or full-page +redirect; the buyer connects a wallet and authorizes a direct ERC-20 transfer +there, not in the merchant page. -See the public [Hosted Checkout guide](../docs/x402-checkout.md) for the complete -DIRECT/DELEGATE flow and server SDK examples. +See the public [Hosted Checkout guide](../docs/goat-flow-checkout.md) for the complete +DIRECT flow and server SDK examples. ## Install @@ -13,10 +14,13 @@ DIRECT/DELEGATE flow and server SDK examples. npm install goatflow-checkout ``` -The package also builds a browser IIFE for script-tag delivery: +The package also builds `dist/checkout.global.js` as a browser IIFE for +self-hosted script-tag delivery. Do not assume the file is deployed at a fixed +GOAT Flow URL; use the npm import above unless your deployment contract provides +a script URL. ```html - + ``` ## DIRECT product checkout @@ -27,7 +31,7 @@ merchant backend. The browser carries the product key, never the amount. ```ts import { GoatCheckout } from 'goatflow-checkout' -const goat = GoatCheckout({ origin: 'https://pay.goat.network' }) +const goat = GoatCheckout({ origin: 'https://flow-quickpay.goat.network' }) button.addEventListener('click', () => { goat.open({ @@ -51,18 +55,18 @@ button.addEventListener('click', () => { `open()` must run synchronously inside the user gesture so browsers do not block the popup or tab. -## Unified Checkout Session +## Hosted Checkout Session -Dynamic DIRECT checkout and all DELEGATE checkout start on the merchant backend. -Create a server-authoritative session with `goatflow-sdk-server`, return only the -opaque `checkoutId` to the browser, then open it with this package: +Dynamic DIRECT checkout starts on the merchant backend. Create a +server-authoritative session with `goatflow-sdk-server`, return only the opaque +`checkoutId` to the browser, then open it with this package: ```ts goat.open({ checkoutId: session.checkoutId, display: 'tab', onSuccess: (result) => { - // Confirm fulfillment with quickpay.checkout.completed. + // Confirm fulfillment with trusted backend status or a verified webhook. }, }) @@ -70,36 +74,31 @@ goat.open({ goat.redirectToCheckout({ checkoutId: session.checkoutId }) ``` -The hosted page reads the session and determines whether it is DIRECT or DELEGATE. -The old `openDelegate({ handle })` and -`redirectToDelegateCheckout({ handle })` methods remain as deprecated aliases for -one compatibility cycle. +The hosted page reads the session terms. The server SDK response exposes +`checkoutType` as `string`; public merchant integrations use `DIRECT` and +handle unknown future values explicitly. ## Payment modes and amount integrity | Browser call | Price source | Backend | Intended use | | --- | --- | --- | --- | | `open({ merchant, productKey })` | QuickPay product configured server-side | No | Fixed DIRECT catalog item | -| `open({ checkoutId })` | HMAC-created Checkout Session | Yes | Dynamic DIRECT or any DELEGATE checkout | +| `open({ checkoutId })` | HMAC-created Checkout Session | Yes | Dynamic DIRECT checkout; operator-provisioned compatibility sessions | | `openCustom({ merchant, amount })` | Browser-supplied amount | No | Donation/custom payment only | `openCustom` is deliberately untrusted. Never auto-fulfill a purchase from its browser amount; reconcile the confirmed amount server-side. -### DELEGATE session forms +### Compatibility aliases and session variants -The unified server endpoint supports: - -- cross-chain decimal-price mode: `checkoutType: 'DELEGATE'` plus `price`; - Core derives the callback chain and payable source-chain/token candidates from - merchant configuration; -- legacy single-chain fixed-wei mode: `fixedAmountWei`, `chainId`, and - `acceptableTokens`, with optional callback calldata. - -DELEGATE is not a zero-backend flow. The merchant API secret stays on the backend, -which creates the session over HMAC. The buyer may then sign the returned EIP-712 -callback authorization, transfer the selected token, and wait for platform -settlement. +The package retains `openDelegate({ handle })` and +`redirectToDelegateCheckout({ handle })` as deprecated aliases for one +compatibility cycle. Server SDK types also retain an operator-provisioned +DELEGATE session value. These are not part of the current public onboarding path; +the merchant API secret remains backend-only, and availability must come from an +explicit deployment contract. See the +[API Reference](../docs/goat-flow-api-reference.md) for the canonical compatibility +fields and signature endpoint. ## Security and delivery @@ -107,8 +106,9 @@ settlement. HTTP for local development, and must not include a path, query, or credentials. - `display: 'popup'` and `display: 'tab'` use a hardened `postMessage` channel validated by exact origin, exact window source, and a per-open random nonce. -- `onSuccess` is only a UX event. Fulfill from - `quickpay.checkout.completed` (or a verified order-status query). +- `onSuccess` is only a UX event. Fulfill from verified backend status or an + authenticated webhook contract confirmed for the deployment; the public SDK + does not define one canonical webhook event name. - Redirect URLs are honored only when allowed by the merchant's redirect allowlist. - A strict `Cross-Origin-Opener-Policy` can sever the popup channel; use redirect mode when `onError('opener_unavailable')` is reported. @@ -120,11 +120,13 @@ uses `/quickpay/checkout`; deployments with a different hosted route can set ## Develop ```bash -pnpm install -pnpm build -pnpm test:run -pnpm typecheck +npm install +npm run build +npm run test:run +npm run typecheck ``` The tests cover URL validation, popup/tab lifecycle, exact message-channel checks, -settle/cancel races, superseded opens, and deprecated alias behavior. +success/cancel races, superseded opens, and deprecated alias behavior. + +See the [Changelog](./CHANGELOG.md). diff --git a/goatx402-contract/MERCHANT_CALLBACK.md b/goatx402-contract/MERCHANT_CALLBACK.md index 7d560af..e16a454 100644 --- a/goatx402-contract/MERCHANT_CALLBACK.md +++ b/goatx402-contract/MERCHANT_CALLBACK.md @@ -1,48 +1,60 @@ # MerchantCallback Contract -## Overview +## Product Status -`MerchantCallback` is a test contract for receiving payment callbacks from goatx402. It implements two callback functions: +`MerchantCallback` is a reference UUPS receiver for an optional, operator- +provisioned callback transfer flow. It is not required by the current public DIRECT merchant +product, where the payer transfers tokens to the merchant's configured +receiving address. -1. **x402SpentEip3009** - Callback for EIP-3009 (transferWithAuthorization) payments -2. **x402SpentPermit2** - Callback for Permit2 payments +The internal `topup-service` uses `TopupCallback`, not `MerchantCallback`. +`TopupCallback` has its own EIP-712 domain, omits the `withCalldata` +entrypoints, and verifies the exact balance increase. Its EIP-712 domain is not +used to hash or recover token/Permit2 payment signatures: the supplied +EIP-3009 token or Permit2 contract validates those signatures. -The platform `topup-service` uses the separate `TopupCallback`: it has the -`GoatX402 Topup Callback` / `1` EIP-712 domain and deliberately exposes no -`withCalldata` entrypoints. Merchant integrations should continue to use -`MerchantCallback`. +## Deployment Model -## Features +`MerchantCallback` is deployed as: -- ✅ Tracks all callback invocations with full parameter history -- ✅ Emits events for each callback -- ✅ Prevents duplicate nonce usage (for testing) -- ✅ Toggle revert mode for error handling tests -- ✅ Query callback history by index -- ✅ Owner-only administrative functions +- a `MerchantCallback` implementation; and +- an ERC1967 proxy initialized with the deployer as owner. -## Contract Structure +Use and register the proxy address. The implementation disables initializers. +The default EIP-712 domain is: -### State Variables +```text +name: GoatX402 Pay Callback +version: 1 +chainId: current callback chain +verifyingContract: proxy address +``` -```solidity -// Callback history arrays -Eip3009Callback[] public eip3009Callbacks; -Permit2Callback[] public permit2Callbacks; +`version()` returns `2.1.0`. The owner can invoke the one-time +`reinitialize(... )` step at reinitializer version `2`, normally as part of a +coordinated upgrade. -// Nonce tracking -mapping(bytes32 => bool) public eip3009NonceUsed; -mapping(uint256 => bool) public permit2NonceUsed; +## Trust Model -// Test mode flag -bool public shouldRevert; -``` +All payment entrypoints use `onlyAuthorized`. The owner controls the +`authorizedCallers` mapping, and an authorized operator caller supplies the +token, Permit2, TSS owner, original payer, amount, and authorization fields. + +The contract then pulls tokens into itself: -### Main Functions +- EIP-3009 through `receiveWithAuthorization`; or +- Permit2 through `permitTransferFrom`. + +The underlying token or Permit2 contract validates the payment authorization. +`MerchantCallback` does not maintain its own token, Permit2, or amount +allowlist. + +## Callback ABI -#### x402SpentEip3009 ```solidity function x402SpentEip3009( + address token, + address originalPayer, address owner, uint256 amount, uint256 validAfter, @@ -51,372 +63,302 @@ function x402SpentEip3009( uint8 v, bytes32 r, bytes32 s -) external -``` - -Called by x402 after successful EIP-3009 payment. +) external; -#### x402SpentPermit2 -```solidity function x402SpentPermit2( + address permit2, + address token, + address originalPayer, address owner, uint256 amount, uint256 nonce, uint256 deadline, bytes calldata signature -) external +) external; ``` -Called by x402 after successful Permit2 payment. - -### View Functions - -```solidity -// Get callback counts -function getEip3009CallbackCount() external view returns (uint256) -function getPermit2CallbackCount() external view returns (uint256) - -// Get callback details by index -function getEip3009Callback(uint256 index) external view returns (...) -function getPermit2Callback(uint256 index) external view returns (...) - -// Check nonce usage -function isEip3009NonceUsed(bytes32 nonce) external view returns (bool) -function isPermit2NonceUsed(uint256 nonce) external view returns (bool) -``` +The base callbacks transfer tokens and emit an event. They do **not** +cryptographically bind `originalPayer`; that value is supplied by the +authorized caller for attribution. -### Admin Functions (Owner Only) +The calldata variants add a payer signature: ```solidity -// Toggle revert mode for testing -function setShouldRevert(bool _shouldRevert) external onlyOwner - -// Reset callback history -function resetCallbacks() external onlyOwner -``` - -## Deployment - -### Prerequisites - -1. Set up environment variables in `.env`: -```bash -PRIVATE_KEY=your_private_key_here -RPC_URL=your_rpc_url -``` - -2. Ensure you have Foundry installed: -```bash -curl -L https://foundry.paradigm.xyz | bash -foundryup -``` - -### Deploy to Local Network - -```bash -# Start local Anvil node (in a separate terminal) -anvil - -# Deploy contract -forge script script/DeployMerchantCallback.s.sol:DeployMerchantCallback \ - --rpc-url http://localhost:8545 \ - --broadcast \ - --private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 -``` - -### Deploy to BSC Testnet - -```bash -forge script script/DeployMerchantCallback.s.sol:DeployMerchantCallback \ - --rpc-url bsc_testnet \ - --broadcast \ - --verify \ - --private-key $PRIVATE_KEY -``` - -### Deploy to GOAT Testnet - -```bash -forge script script/DeployMerchantCallback.s.sol:DeployMerchantCallback \ - --rpc-url goat_testnet3 \ - --broadcast \ - --private-key $PRIVATE_KEY -``` - -## Testing - -### Run All Tests - -```bash -forge test -vvv -``` - -### Run Specific Test - -```bash -forge test --match-contract MerchantCallbackTest -vvv -``` - -### Run with Gas Report - -```bash -forge test --gas-report +function x402SpentEip3009WithCalldata( + address token, + address originalPayer, + address owner, + uint256 amount, + uint256 validAfter, + uint256 validBefore, + bytes32 nonce, + uint8 v, + bytes32 r, + bytes32 s, + bytes calldata calldata_, + bytes32 orderId, + uint256 calldataNonce, + uint256 calldataDeadline, + uint8 calldataV, + bytes32 calldataR, + bytes32 calldataS +) external; + +function x402SpentPermit2WithCalldata( + address permit2, + address token, + address originalPayer, + address owner, + uint256 amount, + uint256 nonce, + uint256 deadline, + bytes calldata signature, + bytes calldata calldata_, + bytes32 orderId, + uint256 calldataNonce, + uint256 calldataDeadline, + uint8 calldataV, + bytes32 calldataR, + bytes32 calldataS +) external; ``` -### Test Coverage +## Calldata Signature -```bash -forge coverage -``` +The EIP-3009 struct is: -## Integration with x402 - -### 1. Store Contract Address in Database - -After deploying, store the contract address in the `merchant_callback_contract` table: - -```sql -INSERT INTO merchant_callback_contract ( - merchant_id, - chain_id, - spent_address, - spent_permit2_func_abi, - spent_erc3009_func_abi -) VALUES ( - 'your_merchant_id', - 97, -- BSC Testnet chain ID - '0xYourContractAddress', - '{"name":"x402SpentPermit2","type":"function","stateMutability":"nonpayable","inputs":[{"name":"owner","type":"address"},{"name":"amount","type":"uint256"},{"name":"nonce","type":"uint256"},{"name":"deadline","type":"uint256"},{"name":"signature","type":"bytes"}],"outputs":[]}', - '{"name":"x402SpentEip3009","type":"function","stateMutability":"nonpayable","inputs":[{"name":"owner","type":"address"},{"name":"amount","type":"uint256"},{"name":"validAfter","type":"uint256"},{"name":"validBefore","type":"uint256"},{"name":"nonce","type":"bytes32"},{"name":"v","type":"uint8"},{"name":"r","type":"bytes32"},{"name":"s","type":"bytes32"}],"outputs":[]}' -); +```solidity +Eip3009CallbackData( + address token, + address owner, + address payer, + uint256 amount, + bytes32 orderId, + uint256 calldataNonce, + uint256 deadline, + bytes32 calldataHash +) ``` -### 2. Test EIP-3009 Callback - -Create a test order with `ERC20_3009` flow and verify the callback is received: - -```bash -# Check callback count before -cast call $CONTRACT_ADDRESS "getEip3009CallbackCount()" --rpc-url $RPC_URL - -# Process payment through x402 -# ... (payment processing) - -# Check callback count after -cast call $CONTRACT_ADDRESS "getEip3009CallbackCount()" --rpc-url $RPC_URL +The Permit2 struct prepends `address permit2`: -# Get callback details -cast call $CONTRACT_ADDRESS "getEip3009Callback(uint256)" 0 --rpc-url $RPC_URL +```solidity +Permit2CallbackData( + address permit2, + address token, + address owner, + address payer, + uint256 amount, + bytes32 orderId, + uint256 calldataNonce, + uint256 deadline, + bytes32 calldataHash +) ``` -### 3. Test Permit2 Callback +For both variants the contract: -Create a test order with `ERC20_APPROVE_PERMIT2` flow: +1. rejects a used `(originalPayer, calldataNonce)`; +2. rejects a deadline earlier than the current block timestamp; +3. hashes the calldata and full payment context; +4. recovers an EOA signer with `ECDSA.recover`; +5. requires that signer to equal `originalPayer`; +6. marks the calldata nonce used; +7. pulls the payment tokens; and +8. performs an internal self-call with the signed calldata. -```bash -# Check callback count -cast call $CONTRACT_ADDRESS "getPermit2CallbackCount()" --rpc-url $RPC_URL +Any revert before completion rolls the transaction state back, including the +nonce write and token transfer. -# Get callback details after payment -cast call $CONTRACT_ADDRESS "getPermit2Callback(uint256)" 0 --rpc-url $RPC_URL -``` +## Calldata Execution Semantics -### 4. Monitor Callbacks via Events +`_executeCalldata` uses: -```bash -# Watch for Eip3009 callbacks -cast logs --address $CONTRACT_ADDRESS \ - "Eip3009CallbackReceived(address,uint256,uint256,uint256,bytes32,uint8,bytes32,bytes32)" \ - --rpc-url $RPC_URL - -# Watch for Permit2 callbacks -cast logs --address $CONTRACT_ADDRESS \ - "Permit2CallbackReceived(address,uint256,uint256,uint256,bytes)" \ - --rpc-url $RPC_URL +```solidity +(bool success, bytes memory result) = address(this).call(calldata_); +emit CalldataExecuted(calldata_, success, result); ``` -## Testing Error Handling +Consequences: -### Enable Revert Mode +- calldata shorter than four bytes is ignored; +- the call can only reach functions exposed by the proxy's current + implementation; +- code inside that call sees `msg.sender == address(this)`, not the payer or + authorized operator caller; +- failure is reported in `CalldataExecuted` and does not revert the completed + payment; and +- ERC-1271 contract-wallet signatures are not supported. -```bash -# Enable revert mode (only owner) -cast send $CONTRACT_ADDRESS \ - "setShouldRevert(bool)" true \ - --private-key $PRIVATE_KEY \ - --rpc-url $RPC_URL - -# Now callbacks will revert -# Test x402's error handling - -# Disable revert mode -cast send $CONTRACT_ADDRESS \ - "setShouldRevert(bool)" false \ - --private-key $PRIVATE_KEY \ - --rpc-url $RPC_URL -``` +There is **no selector allowlist**. Solidity comments that mention a selector +whitelist describe behavior that is not implemented by the current contract; +there is no selector mapping or check before the self-call. -## Useful Commands +The bundled `testCallback(address,uint256,string)` only emits +`TestCallbackExecuted`. It does not verify that its `payer` or `value` arguments +match the payment and should be treated as a demo helper, not production +business logic. -### Check Callback History - -```bash -# Total callbacks -cast call $CONTRACT_ADDRESS "getEip3009CallbackCount()" --rpc-url $RPC_URL -cast call $CONTRACT_ADDRESS "getPermit2CallbackCount()" --rpc-url $RPC_URL +## Administration -# Get specific callback -cast call $CONTRACT_ADDRESS "getEip3009Callback(uint256)" 0 --rpc-url $RPC_URL -cast call $CONTRACT_ADDRESS "getPermit2Callback(uint256)" 0 --rpc-url $RPC_URL +```solidity +function setAuthorizedCaller(address caller, bool authorized) external onlyOwner; +function setAuthorizedCallers( + address[] calldata callers, + bool[] calldata authorized +) external onlyOwner; +function withdrawTokens( + address token, + address to, + uint256 amount +) external onlyOwner; +function reinitialize( + string memory eip712Name, + string memory eip712Version +) public reinitializer(2) onlyOwner; ``` -### Check Nonce Usage - -```bash -# EIP-3009 nonce -cast call $CONTRACT_ADDRESS \ - "isEip3009NonceUsed(bytes32)" \ - 0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef \ - --rpc-url $RPC_URL - -# Permit2 nonce -cast call $CONTRACT_ADDRESS "isPermit2NonceUsed(uint256)" 12345 --rpc-url $RPC_URL -``` +UUPS upgrade authorization is owner-only through `_authorizeUpgrade`. -### Reset Callback History +Useful views: -```bash -# Clear all callbacks (owner only) -cast send $CONTRACT_ADDRESS \ - "resetCallbacks()" \ - --private-key $PRIVATE_KEY \ - --rpc-url $RPC_URL +```solidity +function authorizedCallers(address caller) external view returns (bool); +function calldataNonceUsed(address payer, uint256 nonce) external view returns (bool); +function isCalldataNonceUsed(address payer, uint256 nonce) external view returns (bool); +function getDomainSeparator() external view returns (bytes32); +function owner() external view returns (address); +function version() external pure returns (string memory); ``` -### Transfer Ownership - -```bash -cast send $CONTRACT_ADDRESS \ - "transferOwnership(address)" \ - 0xNewOwnerAddress \ - --private-key $PRIVATE_KEY \ - --rpc-url $RPC_URL -``` +There are no callback-history arrays, callback counters, reset functions, or +test revert toggle. ## Events -### Eip3009CallbackReceived ```solidity event Eip3009CallbackReceived( + address indexed token, + address indexed originalPayer, address indexed owner, uint256 amount, uint256 validAfter, uint256 validBefore, - bytes32 nonce, - uint8 v, - bytes32 r, - bytes32 s + bytes32 nonce ); -``` -### Permit2CallbackReceived -```solidity event Permit2CallbackReceived( + address indexed token, + address indexed originalPayer, address indexed owner, uint256 amount, uint256 nonce, - uint256 deadline, - bytes signature + uint256 deadline ); -``` - -### CallbackFailed -```solidity -event CallbackFailed(string reason); -``` - -## Example Integration Test Flow - -1. **Deploy Contract** - ```bash - forge script script/DeployMerchantCallback.s.sol:DeployMerchantCallback \ - --rpc-url $RPC_URL --broadcast - ``` - -2. **Store in Database** - ```sql - INSERT INTO merchant_callback_contract (...) VALUES (...); - ``` - -3. **Create Test Order** - ```bash - curl -X POST http://localhost:8080/api/v1/orders \ - -H "Content-Type: application/json" \ - -H "X-API-Key: $API_KEY" \ - -H "X-Timestamp: $(date +%s)" \ - -H "X-Sign: $SIGNATURE" \ - -d '{ - "flow": "ERC20_3009", - "amount_wei": "1000000", - "token_symbol": "USDC", - "chain_id": 97 - }' - ``` - -4. **Process Payment** - (User completes payment via wallet) - -5. **Verify Callback** - ```bash - # Check callback was received - cast call $CONTRACT_ADDRESS "getEip3009CallbackCount()" --rpc-url $RPC_URL - - # Get callback details - cast call $CONTRACT_ADDRESS "getEip3009Callback(uint256)" 0 --rpc-url $RPC_URL - ``` - -## Troubleshooting -### Contract Not Receiving Callbacks +event Eip3009CallbackWithCalldataReceived( + address indexed token, + address indexed originalPayer, + address indexed owner, + uint256 amount, + bytes32 nonce, + bytes calldata_, + uint256 calldataNonce +); -1. Verify contract address in database: - ```sql - SELECT * FROM merchant_callback_contract WHERE merchant_id = 'your_merchant_id'; - ``` +event Permit2CallbackWithCalldataReceived( + address indexed token, + address indexed originalPayer, + address indexed owner, + uint256 amount, + uint256 nonce, + bytes calldata_, + uint256 calldataNonce +); -2. Check x402 logs for callback attempts +event CalldataExecuted(bytes calldata_, bool success, bytes result); +event TokensWithdrawn(address indexed token, address indexed to, uint256 amount); +event AuthorizedCallerUpdated(address indexed caller, bool authorized); +event TestCallbackExecuted(address indexed payer, uint256 value, string message); +``` -3. Verify network connectivity +Custom errors: -### Callbacks Reverting +```solidity +error UnauthorizedCaller(address caller); +error CalldataNonceAlreadyUsed(address user, uint256 nonce); +error CalldataSignatureExpired(uint256 deadline, uint256 currentTime); +error InvalidCalldataSignature(address expected, address actual); +``` -1. Check if revert mode is enabled: - ```bash - cast call $CONTRACT_ADDRESS "shouldRevert()" --rpc-url $RPC_URL - ``` +## Deploy And Upgrade -2. Review transaction logs for revert reason +Follow [`QUICK_START.md`](QUICK_START.md) for initial deployment and +registration. The canonical source is +[`src/MerchantCallback.sol`](src/MerchantCallback.sol). -## Security Considerations +Upgrade commands: -⚠️ **This is a TEST contract**. For production use: +```bash +read -r -s PRIVATE_KEY +export PRIVATE_KEY +export PROXY_ADDRESS=0x... + +forge script script/DeployMerchantCallback.s.sol:UpgradeMerchantCallback \ + --rpc-url "" --broadcast + +export EIP712_NAME="GoatX402 Pay Callback" +export EIP712_VERSION="2" +forge script \ + script/DeployMerchantCallback.s.sol:UpgradeMerchantCallbackWithReinit \ + --rpc-url "" --broadcast +``` -1. Add access control for callback functions -2. Implement proper business logic -3. Add reentrancy guards if calling external contracts -4. Verify caller address (should be x402 payout contract) -5. Add rate limiting -6. Implement proper error handling and recovery +Only the current proxy owner can upgrade. Before broadcasting, test storage +compatibility and coordinate an EIP-712 domain change with every producer and +verifier of calldata signatures. + +## Integration Checklist + +1. Confirm DELEGATE is enabled for the intended non-public environment. +2. Obtain the exact operator caller for that chain/environment. +3. Deploy and record the ERC1967 proxy address. +4. Confirm `owner()` and `authorizedCallers(operatorCaller)` on-chain. +5. Submit the proxy through the approved operator/merchant configuration flow. +6. Run an end-to-end payment with the environment's real token and Permit2 or + EIP-3009 configuration. +7. Monitor both payment events and `CalldataExecuted` when calldata is used. +8. Withdraw or account for tokens held by the callback contract. + +Do not register the implementation address or copy an old internal database +schema/ABI into a production system. + +## Security Notes + +- Authorized callers are a strong trust root and must be environment-specific. +- Base callbacks trust the caller-provided `originalPayer` attribution. +- `MerchantCallback` does not verify an exact balance delta; fee-on-transfer or + malicious tokens can make the emitted amount differ from the amount received. +- Owner compromise permits caller changes, token withdrawals, and upgrades. +- The contract holds tokens received through this callback flow until the owner + withdraws them. +- Calldata execution is non-atomic with business logic because a failed + self-call does not revert payment. +- A user signature authorizes bytes, not the safety or correctness of the + function reached by those bytes. + +## Validation -## Gas Estimates +```bash +forge build +forge test --match-contract MerchantCallbackTest -vv +forge test --match-contract TopupCallbackTest -vv +``` -| Function | Gas Used | -|----------|----------| -| x402SpentEip3009 | ~60,000 | -| x402SpentPermit2 | ~55,000 | -| getEip3009CallbackCount | ~2,300 | -| getPermit2CallbackCount | ~2,300 | +The source currently defines 16 MerchantCallback tests and 8 TopupCallback +tests. ## License -MIT +The Solidity source files declare SPDX `MIT`. No repository-wide license is +defined by this document. diff --git a/goatx402-contract/QUICK_START.md b/goatx402-contract/QUICK_START.md index 026e679..1df8680 100644 --- a/goatx402-contract/QUICK_START.md +++ b/goatx402-contract/QUICK_START.md @@ -1,313 +1,189 @@ -# MerchantCallback Quick Start Guide +# MerchantCallback Quick Start -> `topup-service` is the exception: deploy `TopupCallback` with -> `script/DeployTopupCallback.s.sol`. It uses a dedicated EIP-712 domain and does -> not support `withCalldata`. Regular DELEGATE merchants use `MerchantCallback`. +`MerchantCallback` is a reference contract for an optional, operator-provisioned +callback transfer flow. It is not part of the current public DIRECT merchant setup. +DIRECT merchants should configure receiving addresses in the Merchant Portal +and skip this guide. -## 1. Environment Setup +`topup-service` uses the separate `TopupCallback`; do not substitute +`MerchantCallback` for that internal service. -```bash -# Ensure a .env file exists and contains your deployer key -cat > .env << EOF -PRIVATE_KEY=your_private_key_here -EOF -``` +## 1. Install And Validate -## 2. Build Contracts +From `goatx402-contract/`, install the Solidity libraries as documented in the +[`README` prerequisites](README.md#prerequisites). That section records the +current unpinned `forge-std` reproducibility blocker. ```bash forge build -``` - -## 3. Run Tests - -```bash forge test --match-contract MerchantCallbackTest -vv ``` -You should see all 8 tests passing: - -```text -[PASS] testEip3009Callback() (gas: 207230) -[PASS] testEip3009DuplicateNonce() (gas: 200542) -[PASS] testMultipleCallbacks() (gas: 1302938) -[PASS] testOnlyOwnerFunctions() (gas: 18029) -[PASS] testPermit2Callback() (gas: 182984) -[PASS] testPermit2DuplicateNonce() (gas: 175997) -[PASS] testResetCallbacks() (gas: 287944) -[PASS] testShouldRevertToggle() (gas: 214431) -``` - -## 4. Deploy to a Local Testnet - -### Start a local node - -```bash -# Run this in another terminal window -anvil -``` - -### Deploy the contract +The current suite contains 16 MerchantCallback tests. It covers the two base +payment callbacks, both `withCalldata` variants, authorization, withdrawals, +EIP-712 validation, deadlines, replay protection, domain separation, and +version reporting. -```bash -forge script script/DeployMerchantCallback.s.sol:DeployMerchantCallback \ - --rpc-url http://localhost:8545 \ - --broadcast \ - --private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 -``` +## 2. Understand The Deployment -A successful deployment prints output like: +`DeployMerchantCallback` deploys: -```text -=========================================== -MerchantCallback deployed successfully! -=========================================== -Contract Address: 0x5FbDB2315678afecb367f032d93F642f64180aa3 -Deployer: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 -Chain ID: 31337 -=========================================== -``` +1. a `MerchantCallback` implementation; and +2. an ERC1967 proxy initialized with the deployer as owner. -## 5. Deploy to BSC Testnet +The proxy is the operational contract address. The implementation address must +not be registered as the merchant callback. -```bash -forge script script/DeployMerchantCallback.s.sol:DeployMerchantCallback \ - --rpc-url bsc_testnet \ - --broadcast \ - --verify \ - --private-key $PRIVATE_KEY -``` - -## 6. Configure the Database - -After deployment, insert or update the callback contract config: - -```sql --- Connect to database -psql -h localhost -U postgres -d goatx402 - --- Upsert callback contract configuration -INSERT INTO merchant_callback_contract ( - merchant_id, - chain_id, - spent_address, - spent_permit2_func_abi, - spent_erc3009_func_abi -) VALUES ( - 'default', -- your merchant ID - 97, -- BSC Testnet (or 31337 for local) - '0xYourContractAddress', - '{"name":"x402SpentPermit2","type":"function","stateMutability":"nonpayable","inputs":[{"name":"owner","type":"address"},{"name":"amount","type":"uint256"},{"name":"nonce","type":"uint256"},{"name":"deadline","type":"uint256"},{"name":"signature","type":"bytes"}],"outputs":[]}', - '{"name":"x402SpentEip3009","type":"function","stateMutability":"nonpayable","inputs":[{"name":"owner","type":"address"},{"name":"amount","type":"uint256"},{"name":"validAfter","type":"uint256"},{"name":"validBefore","type":"uint256"},{"name":"nonce","type":"bytes32"},{"name":"v","type":"uint8"},{"name":"r","type":"bytes32"},{"name":"s","type":"bytes32"}],"outputs":[]}' -) ON CONFLICT (merchant_id, chain_id) DO UPDATE SET - spent_address = EXCLUDED.spent_address, - spent_permit2_func_abi = EXCLUDED.spent_permit2_func_abi, - spent_erc3009_func_abi = EXCLUDED.spent_erc3009_func_abi, - updated_at = NOW(); -``` +The deployment script reads the private key from `PRIVATE_KEY`. If +`X402_CALLER_ADDRESS` is nonzero, it authorizes that operator caller in the same +broadcast. -## 7. Test Callback Functions +## 3. Local Anvil Deployment -### Use `cast` commands +Start Anvil in one terminal: ```bash -# Save addresses and RPC target -CONTRACT_ADDRESS=0xYourContractAddress -RPC_URL=http://localhost:8545 # or a BSC testnet RPC endpoint - -# Check callback counters -cast call $CONTRACT_ADDRESS "getEip3009CallbackCount()" --rpc-url $RPC_URL -cast call $CONTRACT_ADDRESS "getPermit2CallbackCount()" --rpc-url $RPC_URL - -# Simulate an EIP-3009 callback (must be sent by an authorized caller) -cast send $CONTRACT_ADDRESS \ - "x402SpentEip3009(address,uint256,uint256,uint256,bytes32,uint8,bytes32,bytes32)" \ - 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 \ - 1000000 \ - $(date +%s) \ - $(($(date +%s) + 3600)) \ - 0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef \ - 27 \ - 0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa \ - 0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb \ - --private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 \ - --rpc-url $RPC_URL - -# Verify callback was recorded -cast call $CONTRACT_ADDRESS "getEip3009CallbackCount()" --rpc-url $RPC_URL -# Expected: 1 - -# Read callback detail -cast call $CONTRACT_ADDRESS "getEip3009Callback(uint256)" 0 --rpc-url $RPC_URL +anvil ``` -## 8. Integrate with x402 - -### End-to-end test flow - -1. Start the x402 service. +In another terminal, enter one of the private keys printed by that local Anvil +process and authorize a second local account as the callback caller. Reading the +key interactively keeps it out of the guide and shell history: ```bash -go run cmd/x402d/main.go -``` - -2. Create a test order. +read -r -s PRIVATE_KEY +export PRIVATE_KEY +export X402_CALLER_ADDRESS=0x70997970C51812dc3A010C7d01b50e0d17dc79C8 -```bash -# Create an ERC20_3009 or ERC20_APPROVE_PERMIT2 order via API -curl -X POST http://localhost:8080/api/v1/orders \ - -H "Content-Type: application/json" \ - -H "X-API-Key: your_api_key" \ - -H "X-Timestamp: $(date +%s)" \ - -H "X-Nonce: your_request_nonce" \ - -H "X-Sign: your_signature" \ - -d '{ - "flow": "ERC20_3009", - "token_symbol": "USDC", - "amount_wei": "1000000", - "chain_id": 97, - "from_address": "0xUserAddress" - }' +forge script script/DeployMerchantCallback.s.sol:DeployMerchantCallback \ + --rpc-url http://localhost:8545 --broadcast ``` -3. Complete payment from the user wallet. - -4. Verify callback execution. +Use the printed `Proxy (use this)` address: ```bash -# Verify contract received callback -cast call $CONTRACT_ADDRESS "getEip3009CallbackCount()" --rpc-url $RPC_URL +export CONTRACT_ADDRESS=0x... +export RPC_URL=http://localhost:8545 -# Read latest callback -cast call $CONTRACT_ADDRESS "getEip3009Callback(uint256)" 0 --rpc-url $RPC_URL +cast call "$CONTRACT_ADDRESS" "owner()(address)" --rpc-url "$RPC_URL" +cast call "$CONTRACT_ADDRESS" \ + "authorizedCallers(address)(bool)" "$X402_CALLER_ADDRESS" \ + --rpc-url "$RPC_URL" +cast call "$CONTRACT_ADDRESS" "version()(string)" --rpc-url "$RPC_URL" ``` -## 9. Monitor Callback Events +Do not try to test the payment callbacks with placeholder signatures. The base +entrypoints call the supplied EIP-3009 token or Permit2 contract and require a +real authorization/permit. The Foundry suite supplies controlled mocks for this +purpose. -### Live event monitoring +## 4. Guided Testnet Deployment -```bash -# Monitor EIP-3009 callback events -cast logs --address $CONTRACT_ADDRESS \ - "Eip3009CallbackReceived(address,uint256,uint256,uint256,bytes32,uint8,bytes32,bytes32)" \ - --rpc-url $RPC_URL \ - --follow - -# Monitor Permit2 callback events -cast logs --address $CONTRACT_ADDRESS \ - "Permit2CallbackReceived(address,uint256,uint256,uint256,bytes)" \ - --rpc-url $RPC_URL \ - --follow -``` - -## 10. Administrative Operations - -### Reset callback history +The helper defaults to BSC Testnet and keeps the key out of command-line +arguments: ```bash -cast send $CONTRACT_ADDRESS "resetCallbacks()" \ - --private-key $PRIVATE_KEY \ - --rpc-url $RPC_URL +cp .env.deploy.example .env.deploy +chmod 600 .env.deploy ``` -### Toggle revert mode for testing +Edit `.env.deploy`: -```bash -# Enable revert mode (callbacks will revert) -cast send $CONTRACT_ADDRESS "setShouldRevert(bool)" true \ - --private-key $PRIVATE_KEY \ - --rpc-url $RPC_URL - -# Disable revert mode -cast send $CONTRACT_ADDRESS "setShouldRevert(bool)" false \ - --private-key $PRIVATE_KEY \ - --rpc-url $RPC_URL +```dotenv +PRIVATE_KEY=0x... +X402_CALLER_ADDRESS=0x... +ETHERSCAN_API_KEY= +RPC_ALIAS=bsc_testnet +CHAIN_ID=97 ``` -## Command Reference - -### Contract information +Then run: ```bash -# Get owner -cast call $CONTRACT_ADDRESS "owner()" --rpc-url $RPC_URL - -# Check revert mode -cast call $CONTRACT_ADDRESS "shouldRevert()" --rpc-url $RPC_URL +bash deploy-merchant-callback.sh ``` -### Callback queries - -```bash -# Total EIP-3009 callbacks -cast call $CONTRACT_ADDRESS "getEip3009CallbackCount()" --rpc-url $RPC_URL - -# Total Permit2 callbacks -cast call $CONTRACT_ADDRESS "getPermit2CallbackCount()" --rpc-url $RPC_URL - -# Read a specific callback -cast call $CONTRACT_ADDRESS "getEip3009Callback(uint256)" INDEX --rpc-url $RPC_URL -cast call $CONTRACT_ADDRESS "getPermit2Callback(uint256)" INDEX --rpc-url $RPC_URL -``` +If local Foundry is unavailable, the helper uses its pinned Docker image. It +also installs missing Solidity dependencies. Set `ETHERSCAN_API_KEY` only when +you want the helper to add `--verify`. -### Nonce checks +For a direct script invocation: ```bash -# Check whether an EIP-3009 nonce has been used -cast call $CONTRACT_ADDRESS "isEip3009NonceUsed(bytes32)" NONCE --rpc-url $RPC_URL - -# Check whether a Permit2 nonce has been used -cast call $CONTRACT_ADDRESS "isPermit2NonceUsed(uint256)" NONCE --rpc-url $RPC_URL +read -r -s PRIVATE_KEY +export PRIVATE_KEY +export X402_CALLER_ADDRESS=0x... +forge script script/DeployMerchantCallback.s.sol:DeployMerchantCallback \ + --rpc-url goat_testnet3 --broadcast ``` -## Troubleshooting +## 5. Register With The Deployment Operator -### Issue: Contract did not receive callback +Send the following to the operator or use the environment's approved merchant +configuration flow: -1. Check database config. +- merchant identifier; +- callback chain ID; +- deployed ERC1967 proxy address; and +- the intended authorized operator caller and environment. -```sql -SELECT * FROM merchant_callback_contract WHERE merchant_id = 'your_merchant_id'; -``` +The callback remains unusable until both sides agree: -2. Check contract address correctness. +- the proxy has authorized the operator caller on-chain; and +- the deployment operator has approved the callback address and ABI for that merchant. -```bash -cast code $CONTRACT_ADDRESS --rpc-url $RPC_URL -# Should return contract bytecode, not 0x -``` +Do not write directly to an internal database using copied SQL. The storage +schema and callback ABI are deployment-operated and may change independently of this +repository. -3. Check x402 logs. +## 6. Monitor And Administer -Review payout executor logs to confirm callback invocation attempts. +Read the exact event signatures from `MERCHANT_CALLBACK.md` or the Solidity +source: -### Issue: Callback transaction failed +```bash +cast logs --address "$CONTRACT_ADDRESS" \ + "Eip3009CallbackReceived(address,address,address,uint256,uint256,uint256,bytes32)" \ + --rpc-url "$RPC_URL" -1. Check whether revert mode is enabled. +cast logs --address "$CONTRACT_ADDRESS" \ + "Permit2CallbackReceived(address,address,address,uint256,uint256,uint256)" \ + --rpc-url "$RPC_URL" -```bash -cast call $CONTRACT_ADDRESS "shouldRevert()" --rpc-url $RPC_URL -# Expected: false +cast call "$CONTRACT_ADDRESS" \ + "isCalldataNonceUsed(address,uint256)(bool)" "$PAYER" "$NONCE" \ + --rpc-url "$RPC_URL" ``` -2. Inspect failed transaction details. +Owner-only operations include: ```bash -cast tx TRANSACTION_HASH --rpc-url $RPC_URL -``` +# The examples use an encrypted Foundry keystore and prompt for its password. +cast send "$CONTRACT_ADDRESS" \ + "setAuthorizedCaller(address,bool)" "$CALLER" true \ + --rpc-url "$RPC_URL" --keystore /path/to/keystore -## More Information +cast send "$CONTRACT_ADDRESS" \ + "withdrawTokens(address,address,uint256)" "$TOKEN" "$RECIPIENT" "$AMOUNT" \ + --rpc-url "$RPC_URL" --keystore /path/to/keystore +``` -See related files: +There are no callback-history arrays, callback counters, reset functions, or +test revert toggle in the current contract. Use emitted events and operator +order records for observability. -- [MERCHANT_CALLBACK.md](./MERCHANT_CALLBACK.md) - Full documentation -- [src/MerchantCallback.sol](./src/MerchantCallback.sol) - Contract source -- [test/MerchantCallback.t.sol](./test/MerchantCallback.t.sol) - Test suite +## 7. Calldata And Upgrade Caveats -## Notes +- `withCalldata` verifies an EOA EIP-712 signature from `originalPayer`. +- The calldata is executed with `address(this).call`, so it can only invoke a + function implemented by the proxy's current implementation. +- The bundled `testCallback` only emits an event and is a demo helper, not + merchant business logic. +- A failed self-call emits `CalldataExecuted(..., false, ...)` but does not + revert the token transfer. +- The source has no selector allowlist and no ERC-1271 signer support. +- `reinitialize(... )` is a one-time reinitializer at version `2`; coordinate + any domain change with all signers and backend code. -- The contract passes the full unit test suite. -- Typical gas usage is around 55,000-60,000 per callback. -- Callback history is unbounded and queryable. -- Access control is implemented via OpenZeppelin `Ownable`. -- Every callback emits events for off-chain monitoring. +See [`MERCHANT_CALLBACK.md`](MERCHANT_CALLBACK.md) for the complete behavior and +security model. diff --git a/goatx402-contract/README.md b/goatx402-contract/README.md index d2d77c7..2c9723b 100644 --- a/goatx402-contract/README.md +++ b/goatx402-contract/README.md @@ -1,210 +1,151 @@ # goatx402-contract -This project contains the smart contracts for goatx402, including: -- `USDC`: ERC20 token with EIP-3009 support and configurable decimals. -- `USDT`: Standard ERC20 token with configurable decimals. -- `MerchantCallback`: Callback contract for merchant payment notifications. -- `TopupCallback`: Dedicated topup-service callback without arbitrary calldata execution. +Foundry project for callback-contract development and local payment-token +testing. -## Prerequisites - -- [Foundry](https://book.getfoundry.sh/getting-started/installation) - -## Configuration +## Scope And Product Status -1. Copy `.env.example` to `.env` (or create it) and fill in your keys: - ```bash - PRIVATE_KEY=your_private_key - BSC_SCAN_API_KEY=your_bsc_scan_api_key - ETHERSCAN_API_KEY=your_etherscan_api_key - # For Goat Testnet3 (Blockscout), you might not need an API key, or use any value if required by forge - GOAT_SCAN_API_KEY=any_value - ``` +These contracts are **not required for the current public DIRECT merchant +product**. A DIRECT payer transfers tokens to the merchant's configured +receiving address and does not call a merchant callback contract. -2. Load environment variables: - ```bash - source .env - ``` +| Contract | Intended use | +| --- | --- | +| [`MerchantCallback`](src/MerchantCallback.sol) | Reference UUPS receiver for an optional, operator-provisioned callback transfer flow | +| [`TopupCallback`](src/TopupCallback.sol) | Dedicated internal receiver for `topup-service` | +| [`USDC`](src/USDC.sol) | Mintable EIP-3009 test token with configurable decimals | +| [`USDT`](src/USDT.sol) | Mintable ERC-20 test token with configurable decimals | -## Token Decimals +`USDC` and `USDT` are development tokens. Their names and symbols do not make +them canonical or production assets. -The USDC and USDT contracts support configurable decimals during deployment. This allows simulating different chain configurations: +## Prerequisites -| Chain | USDC Decimals | USDT Decimals | -|-------|---------------|---------------| -| Ethereum / Sepolia | 6 | 6 | -| BSC / BSC Testnet | 18 | 18 | -| Goat Testnet3 | 6 (default) | 6 (default) | -| Metis Sepolia | 6 | 6 | +- Foundry with Solidity `0.8.24` support +- Git, or Docker when using `deploy-merchant-callback.sh` +- RPC access and native gas only when broadcasting -Set the `TOKEN_DECIMALS` environment variable before deployment: +The Solidity libraries are not committed in this checkout. Install them before +manual builds: ```bash -# Deploy with 6 decimals (ETH-style, default) -forge script script/Deploy.s.sol --rpc-url sepolia --broadcast - -# Deploy with 18 decimals (BSC-style) -TOKEN_DECIMALS=18 forge script script/Deploy.s.sol --rpc-url bsc_testnet --broadcast +forge install foundry-rs/forge-std --no-commit +forge install OpenZeppelin/openzeppelin-contracts@v5.1.0 --no-commit +forge install OpenZeppelin/openzeppelin-contracts-upgradeable@v5.1.0 --no-commit ``` -## Deployment +> **Known reproducibility blocker:** the `forge-std` install is not pinned to a +> tag or commit. Do not treat a contract build or deployment artifact as +> reproducible release evidence until a pinned revision is reviewed and merged. +> The Foundry project is outside the npm release runbook. -### Sepolia (ETH Testnet) +The MerchantCallback helper tries the same dependency setup automatically when +the libraries are absent. -Deploy with 6 decimals (Ethereum standard): +## Build And Test ```bash -# Deploy and verify -forge script script/Deploy.s.sol \ - --rpc-url sepolia \ - --broadcast \ - --verify \ - --etherscan-api-key $ETHERSCAN_API_KEY - -# Or with explicit decimals -TOKEN_DECIMALS=6 forge script script/Deploy.s.sol \ - --rpc-url sepolia \ - --broadcast \ - --verify \ - --etherscan-api-key $ETHERSCAN_API_KEY +forge build +forge test + +# Focused suites +forge test --match-contract MerchantCallbackTest -vv +forge test --match-contract TopupCallbackTest -vv ``` -### BSC Testnet +The current source has 16 `MerchantCallbackTest` cases and 8 +`TopupCallbackTest` cases. Treat the command result, not a copied gas snapshot, +as authoritative. -Deploy with 18 decimals (BSC standard): +## Test Tokens -```bash -# Deploy and verify -TOKEN_DECIMALS=18 forge script script/Deploy.s.sol \ - --rpc-url bsc_testnet \ - --broadcast \ - --verify \ - --etherscan-api-key $ETHERSCAN_API_KEY -``` +`script/Deploy.s.sol:DeployScript` deploys both test tokens to the selected +network. It reads: -Manual verification (if needed): +- `PRIVATE_KEY` - required deployer/initial owner key +- `TOKEN_DECIMALS` - optional, defaults to `6` ```bash -# Verify USDC (replace with your contract address and deployer address) -forge verify-contract \ - --chain-id 97 \ - \ - src/USDC.sol:USDC \ - --verifier etherscan \ - --etherscan-api-key $ETHERSCAN_API_KEY \ - --constructor-args $(cast abi-encode "constructor(address,uint8)" 18) \ - --watch - -# Verify USDT (replace with your contract address and deployer address) -forge verify-contract \ - --chain-id 97 \ - \ - src/USDT.sol:USDT \ - --verifier etherscan \ - --etherscan-api-key $ETHERSCAN_API_KEY \ - --constructor-args $(cast abi-encode "constructor(address,uint8)" 18) \ - --watch +read -r -s PRIVATE_KEY +export PRIVATE_KEY +TOKEN_DECIMALS=6 forge script script/Deploy.s.sol:DeployScript \ + --rpc-url goat_testnet3 --broadcast ``` -### Goat Testnet3 +Both tokens mint `1,000,000,000 * 10^decimals` units to the deployer. Choose +decimals to match the test scenario; do not infer a production token's decimals +from this script. -Deploy with default 6 decimals: +## MerchantCallback -```bash -# Set dummy API key for Blockscout (may not require actual key) -export GOAT_SCAN_API_KEY="dummy" - -# Deploy and verify using Blockscout -forge script script/Deploy.s.sol \ - --rpc-url goat_testnet3 \ - --broadcast \ - --verify \ - --verifier blockscout \ - --verifier-url https://explorer.testnet3.goat.network/api -``` +Read [`QUICK_START.md`](QUICK_START.md) for the deployment sequence and +[`MERCHANT_CALLBACK.md`](MERCHANT_CALLBACK.md) for the ABI and security model. +In particular, the current `withCalldata` self-call has no selector allowlist; +any function exposed by the proxy implementation can be attempted with +properly signed calldata. -Or with custom decimals: +The supported helper uses a gitignored local environment file: ```bash -TOKEN_DECIMALS=6 forge script script/Deploy.s.sol \ - --rpc-url goat_testnet3 \ - --broadcast \ - --verify \ - --verifier blockscout \ - --verifier-url https://explorer.testnet3.goat.network/api +cp .env.deploy.example .env.deploy +# Set PRIVATE_KEY and, preferably, X402_CALLER_ADDRESS. +bash deploy-merchant-callback.sh ``` -## MerchantCallback Deployment +The helper: -See [QUICK_START.md](./QUICK_START.md) for MerchantCallback deployment instructions. +1. builds the contracts; +2. deploys a `MerchantCallback` implementation and ERC1967 proxy; +3. optionally authorizes `X402_CALLER_ADDRESS` in the deployment broadcast; and +4. prints the proxy address that must be registered with the deployment operator. -```bash -# BSC Testnet -forge script script/DeployMerchantCallback.s.sol:DeployMerchantCallback \ - --rpc-url bsc_testnet \ - --broadcast \ - --verify \ - --etherscan-api-key $BSC_SCAN_API_KEY - -# Goat Testnet3 -forge script script/DeployMerchantCallback.s.sol:DeployMerchantCallback \ - --rpc-url goat_testnet3 \ - --broadcast \ - --verify \ - --verifier blockscout \ - --verifier-url https://explorer.testnet3.goat.network/api - -# Sepolia -forge script script/DeployMerchantCallback.s.sol:DeployMerchantCallback \ - --rpc-url sepolia \ - --broadcast \ - --verify \ - --etherscan-api-key $ETHERSCAN_API_KEY -``` +Its defaults are BSC Testnet (`RPC_ALIAS=bsc_testnet`, `CHAIN_ID=97`). Change +both values together for another configured network. Register the **proxy**, not +the implementation. -For a guided deployment that keeps the key in a gitignored local file, copy -`.env.deploy.example` to `.env.deploy` and run `bash deploy-merchant-callback.sh`. +## TopupCallback -## TopupCallback Deployment +`TopupCallback` is for the internal `topup-service`, not a regular merchant. It +has no `withCalldata` entrypoints and checks the exact token balance increase +for each callback. -`TopupCallback` is only for the platform topup-service merchant. It uses the -EIP-712 domain `GoatX402 Topup Callback` / `1` and intentionally omits -`withCalldata` callback methods. Regular merchants should use `MerchantCallback`. +Although it initializes and exposes an EIP-712 domain, `TopupCallback` does not +hash or recover the EIP-3009 or Permit2 payment signatures itself. The supplied +token contract validates EIP-3009 authorization, and the supplied Permit2 +contract validates Permit2 signatures. The callback adds authorized-caller +gating and the exact balance-delta check. ```bash -PRIVATE_KEY= AUTHORIZED_CALLER= \ - forge script script/DeployTopupCallback.s.sol:DeployTopupCallback \ +read -r -s PRIVATE_KEY +export PRIVATE_KEY +export AUTHORIZED_CALLER=0x... +forge script script/DeployTopupCallback.s.sol:DeployTopupCallback \ --rpc-url goat_testnet3 --broadcast ``` -## Contracts - -- `src/USDC.sol`: Implements ERC20, Ownable, and EIP712 for EIP-3009 (TransferWithAuthorization). Supports configurable decimals. -- `src/USDT.sol`: Implements standard ERC20 and Ownable. Supports configurable decimals. -- `src/MerchantCallback.sol`: Upgradeable callback contract for receiving payment notifications via EIP-3009 or Permit2. -- `src/TopupCallback.sol`: Upgradeable, topup-specific EIP-3009 / Permit2 receiver with exact-amount checks. - -## Testing +The script accepts a zero `AUTHORIZED_CALLER`, but that leaves every callback +entrypoint unusable until the owner authorizes a caller. -```bash -# Run all tests -forge test - -# Run with verbosity -forge test -vvv +## Configured Foundry Networks -# Run specific test -forge test --match-contract MerchantCallbackTest -vv +| Alias | Chain ID | Purpose | +| --- | ---: | --- | +| `bsc_testnet` | `97` | BSC Testnet | +| `goat_testnet3` | `48816` | GOAT Testnet3 | +| `goat_mainnet` | `2345` | GOAT mainnet | +| `sepolia` | `11155111` | Ethereum Sepolia | +| `metis_sepolia` | `59902` | Metis Sepolia | -# Run topup callback tests -forge test --match-contract TopupCallbackTest -vv -``` +RPC and verifier URLs are defined in `foundry.toml`. Explorer API keys are only +needed when verification is requested. -## Chain Configuration +## Deployment Safety -| Network | Chain ID | RPC URL | Explorer | -|---------|----------|---------|----------| -| BSC Testnet | 97 | https://data-seed-prebsc-1-s1.binance.org:8545 | https://testnet.bscscan.com | -| Goat Testnet3 | 48816 | https://rpc.testnet3.goat.network | https://explorer.testnet3.goat.network | -| Sepolia | 11155111 | https://rpc.sepolia.org | https://sepolia.etherscan.io | -| Metis Sepolia | 59902 | https://sepolia.metisdevops.link | https://sepolia-explorer.metisdevops.link | +- Keep deployment keys in `.env.deploy` or another local secret store. +- Authorize only the operator caller supplied for the target environment. +- Use the ERC1967 proxy address for configuration and monitoring. +- Do not copy database SQL or callback ABIs from old docs; submit the proxy and + let the current operator workflow register the supported ABI. +- Review and test upgrades against the existing proxy storage before broadcast. +- These scripts broadcast irreversible transactions when `--broadcast` is + present. diff --git a/goatx402-demo/README.md b/goatx402-demo/README.md index 34c4bcd..0f68969 100644 --- a/goatx402-demo/README.md +++ b/goatx402-demo/README.md @@ -1,166 +1,181 @@ # GOAT Flow Demo -The UI has two top-level integration paths: - -- **Checkout SDK (recommended, default):** a drop-in hosted checkout. It shows a - DIRECT QuickPay product when `VITE_QUICKPAY_MERCHANT` is configured and can - additionally show the server-created DELEGATE catalog described below. -- **Advanced (build your own):** contains the existing **Classic** and optional - **MPP** sub-tabs. Classic uses the backend - `goatflow-sdk-server` to call `goatx402-core` with HMAC-signed - requests; the `X-Nonce` / `X-Timestamp` / `X-Sign` headers are - generated by the SDK and not constructed by hand. - MPP is the optional buyer-side flow using `MPPClient` from - `goatflow-sdk` (browser-direct to Core's `/mpp/v1/challenge` and - `/mpp/v1/verify`), plus a backend `/api/mpp/protected` endpoint - hosting `@goatnetwork/mpp-middleware` to demonstrate receipt - consumption. - -## DELEGATE hosted checkout (cross-chain price mode, optional) - -The Checkout SDK tab shows a **DELEGATE catalog** **only when the backend is enabled** -(`DELEGATE_ENABLED=1`, and the demo's `GOATX402_*` key belongs to a DELEGATE merchant -with a callback contract). The browser sends ONLY a `product_key`; the backend pins a -**token-agnostic USD `price`** and mints a unified session via -`createCheckoutSession({ checkoutType: 'DELEGATE', price })`, returning -`{ checkout_id, url }`. The frontend redirects the buyer to the hosted page -(`goat.redirectToCheckout({ checkoutId })`). - -### Cross-chain: pay on any source chain, callback on one chain - -DELEGATE means **the buyer pays on ANY supported source chain; the merchant's -callback/settlement stays on a SINGLE chain** (the callback contract's chain). The -platform (core) derives both from the merchant's config: - -- the **callback chain** = the merchant's single `merchant_callback_contract` row; -- the **payable `(source chain, token)` candidates** = every chain with an enabled TSS - wallet for a token whose symbol the merchant receives on the callback chain, where the - price is representable and the source/destination settlement flow agrees. - -The buyer picks a `(source chain, token)` on the hosted page; the amount is computed -server-side as `price * 10^decimals`; the order is created cross-chain -(`from_chain = source`, `pay_to_chain = callback`) and the buyer simply transfers the -token to the source TSS wallet (`pay_to_address`). - -### The merchant site never touches web3 — calldata is optional and chain-independent - -The merchant backend sends only `{ product_key }` → `price`: no chain, no token, no -amount. A product MAY additionally declare a **callback template** (a function signature + -static args) — the hosted checkout page ABI-encodes it into `callback_calldata` at bind, so -the merchant site still imports no web3. The calldata is signed + executed on the merchant's -**callback chain**, independent of whichever source chain the buyer pays on, so it works the -same for every cross-chain candidate. A product with **no** template settles via the plain -no-calldata callback (the buyer just transfers — no signing). - -In this demo: `mug` ($1) has no callback (plain transfer); `tee` ($3.50) declares -`testCallback(address payer, uint256 value, string message)` (payer is a zero-address -placeholder — `MerchantCallback` resolves the real buyer via `originalPayer`), so paying for -`tee` prompts **Sign & Pay**: sign the callback on the callback chain, then transfer on the -chosen source chain. See the public [Hosted Checkout guide](../docs/x402-checkout.md). - -- Backend routes: `GET /api/delegate-config` → `{ enabled }`, - `GET /api/delegate-catalog` → `{ merchant, products[] }`, - `POST /api/create-delegate-checkout` (body `{ product_key }`) → `{ checkout_id, url }` - (HTTP 501 when not enabled). -- **Async create, then new tab:** the demo synchronously opens a blank tab inside - the click gesture, fetches the checkout ID, then navigates that tab. This avoids - popup blockers; the authoritative outcome comes from webhook/status. - -### Env vars (optional — leave blank to hide the DELEGATE catalog) +This private workspace demo contains one default public path and several +advanced, config-gated examples. +## Modes + +| UI path | Status | What it demonstrates | +| --- | --- | --- | +| Checkout SDK / DIRECT | Default public path | A merchant product opened in hosted checkout with `goatflow-checkout` | +| Advanced / Classic | Optional | Custom wallet UI, backend HMAC order creation, status polling, and calldata signing when returned by Core | +| Operator-provisioned checkout | Optional/internal | Compatibility session backed by `MerchantCallback` | +| Advanced / MPP | Optional | Browser flow for the GOAT Flow MPP adapter plus profile `Payment-Receipt` verification | + +The DIRECT QuickPay merchant is configured by `VITE_QUICKPAY_MERCHANT`. It is +separate from the merchant represented by the backend `GOATX402_*` API key. + +## Prerequisites + +- Node.js 18 or newer +- pnpm +- the sibling packages in this repository +- a running hosted-checkout origin for DIRECT checkout + +`pnpm dev` runs `predev`, which installs and builds the local TypeScript SDK, +server SDK, checkout SDK, and MPP middleware before starting the demo. + +> **Current checkout caveat:** the package-local `pnpm-workspace.yaml` files +> contain build-policy settings but no `packages` field. pnpm `9.15.9` rejects +> `install`, `run`, and `exec` with `packages field missing or empty`. The +> workspace configuration must be corrected before the documented fresh-clone +> `pnpm install` / `pnpm dev` path is usable. Do not carry +> `--ignore-workspace` into release procedures because that also ignores the +> workspace policy file. + +## Run The DIRECT Demo + +```bash +cp .env.example .env ``` -DELEGATE_ENABLED=1 # enable the DELEGATE catalog -# DELEGATE_SUCCESS_URL=https://your.app/success # optional, allowlist-gated by the hosted page -# DELEGATE_CANCEL_URL=https://your.app/cancel # optional, allowlist-gated by the hosted page + +Set the public checkout values: + +```dotenv +VITE_CHECKOUT_ORIGIN=http://localhost:3005 +VITE_QUICKPAY_MERCHANT=test-merchant-1 +VITE_QUICKPAY_PRODUCT=mug ``` -`DELEGATE_ENABLED=1` is the only required flag — the callback chain and payable tokens are -derived server-side. The `GOATX402_API_KEY` / `GOATX402_API_SECRET` used by this backend -must belong to a **DELEGATE** merchant that has a `MerchantCallback` contract configured. +`VITE_CHECKOUT_ORIGIN` defaults to `http://localhost:3005` when omitted. The +merchant/product pair must exist in that hosted-checkout environment. The +browser sends the merchant, product key, and a client reference; it does not +choose the authoritative price or payment amount. -## Running +Start the app: ```bash -cp .env.example .env -# Configure VITE_QUICKPAY_* for the DIRECT hosted-checkout button. -# Fill in GOATX402_* for Advanced/Classic and optional DELEGATE checkout. -# Set DELEGATE_ENABLED=1 and/or MPP_* only for those optional demos. pnpm install -pnpm dev # starts server on :3001 + client on :3000 +pnpm dev +``` + +Open `http://localhost:3000`. The Express backend listens on +`http://localhost:3001` and Vite proxies `/api` to it. + +Use `DEMO_WEB_PORT` to move the Vite port. The proxy target is currently fixed +to port `3001`, so changing `PORT` also requires updating `vite.config.ts`. + +Hosted-checkout success callbacks are a browser UX signal. Fulfill orders from +an authenticated webhook or server-side status check. + +## Advanced Classic Mode + +Classic mode needs merchant API credentials on the Express backend: + +```dotenv +GOATX402_MERCHANT_ID=your_merchant_id +GOATX402_API_URL=http://localhost:8286 +GOATX402_API_KEY=your_api_key +GOATX402_API_SECRET=your_api_secret ``` -Visit `http://localhost:3000`. Start with **Checkout SDK**, or open -**Advanced** and switch between its **Classic** and **MPP** sub-tabs. +The backend uses `goatflow-sdk-server` for HMAC-authenticated merchant and order +calls. The browser never receives the API secret. It fetches the merchant's +configured chains/tokens, connects an EVM wallet, creates an order through the +backend, and uses `PaymentHelper` from `goatflow-sdk` to submit the buyer- +authorized transfer returned by the order flow. + +If Core returns a calldata-sign request, the browser signs it and submits the +signature through `POST /api/orders/:orderId/signature`. + +## Operator-provisioned Checkout (Internal) -## MPP mode bootstrap +This section is hidden unless: + +```dotenv +DELEGATE_ENABLED=1 +# DELEGATE_SUCCESS_URL=https://your.app/success +# DELEGATE_CANCEL_URL=https://your.app/cancel +``` -The MPP tab requires an MPP-enabled Core environment plus a bootstrapped DIRECT -merchant with at least one active route. Ask the deployment operator for the -environment's Tempo/token configuration; admin operations (Enable MPP and route -CRUD) are easiest from the operator's MPP page. +This source-only compatibility demo is outside public merchant onboarding. Its +API credentials must belong to a merchant whose callback contract has already +been deployed, authorized, and approved by the deployment operator. -### Env vars +The browser sends only `{ product_key }`. The demo backend owns the catalog and +USD price and creates: +```ts +createCheckoutSession({ + checkoutType: 'DELEGATE', + price: product.priceUsd, + // server-owned line items and metadata +}) ``` + +Core derives the callback chain and available source-chain/token candidates +from merchant configuration. The bundled `mug` has no callback template. The +`tee` uses `MerchantCallback.testCallback(...)`, which is only a demo event +hook. Its template arguments are static demo values: +`MerchantCallback` neither replaces the template's `payer` argument with +`originalPayer` nor validates `payer` or `value` against the payment. See the +canonical +[`MerchantCallback` behavior](../goatx402-contract/MERCHANT_CALLBACK.md#calldata-execution-semantics) +and [hosted checkout guide](../docs/goat-flow-checkout.md). + +## Optional MPP Mode + +[Machine Payments Protocol (MPP)](https://mpp.dev/overview) is an independent +open protocol. This demo exercises GOAT Flow's current adapter and signed +receipt extension, not a generic MPP client/server exchange. The repository has +no interoperability test with official MPP SDKs. + +The GOAT Flow MPP demo mode requires all four runtime values: + +```dotenv MPP_CORE_URL=http://localhost:8080 MPP_MERCHANT_ID=test-merchant-1 +MPP_RECEIPT_KEY_HEX= MPP_RECEIPT_ALG=ed25519 -MPP_RECEIPT_KEY_HEX=<32-byte hex public key> ``` -`MPP_RECEIPT_KEY_HEX` is **the 32-byte ed25519 PUBLIC key**, not the -64-byte private key. Two ways to get it: +Do not add a trailing slash to `MPP_CORE_URL`. For `ed25519`, +`MPP_RECEIPT_KEY_HEX` is the 32-byte public key, not the 64-byte private key. +For `hmac-sha256`, it is a shared secret of at least 32 bytes and is suitable +only for controlled single-tenant development. -1. **Easy path** — open the admin UI's `Merchants → → MPP` - page after Enable MPP; the "Receipt verifier public key" card shows - the value with a copy button. +The demo discovers active routes from: -2. **Derivation from Core's private key** — Go's ed25519 stores the - full private key as `seed(32) || public(32)`. Strip the first 32 - bytes: +```text +GET /merchants/:merchantId/mpp/routes +``` - ```bash - node -e 'const k=process.argv[1].trim().replace(/^0x/,""); if (k.length !== 128) throw new Error("want 64-byte ed25519 private key hex"); console.log(k.slice(64))' "$MPP_RECEIPT_PRIVATE_KEY_HEX" - ``` +`MPP_ROUTE_OPTIONS` can override discovery with a JSON array. It is optional, +but every configured route must already exist in Core or the challenge request +will fail. - `MPP_RECEIPT_PRIVATE_KEY_HEX` here is a transient shell variable - carrying Core's 64-byte private key for the derivation only — do - **NOT** put the private key into the demo `.env` or commit it. +The browser calls Core directly for `/mpp/v1/challenge` and +`/mpp/v1/verify`. Core must allow the demo origin through +`mpp.cors.allowed_origins` or `MPP_API_ALLOWED_ORIGINS`, and expose the payment +response headers required by the SDK. -If `MPP_*` vars are blank, the MPP tab still renders but shows -"MPP demo not configured" instead of the Try MPP button. The Classic -tab is unaffected. +After verification, the browser sends the returned `Payment-Receipt` to: -### Cross-origin gotcha +```text +GET /api/mpp/protected/:optionId +``` -The browser calls Core directly for `/mpp/v1/challenge` and -`/mpp/v1/verify`. Core must therefore have CORS enabled for the demo's -origin, with `Payment-Receipt`, `Retry-After`, `WWW-Authenticate`, and -`X-Request-Id` listed in `Access-Control-Expose-Headers`. Either: - -- Set `mpp.cors.allowed_origins: ["http://localhost:3000"]` in - `config_x402d.yaml`, or -- Export `MPP_API_ALLOWED_ORIGINS=http://localhost:3000` before starting - x402d. - -The expose-headers list is fixed by Core's CORS middleware; only the -allowed-origins list is operator-tunable. - -### Manual smoke test - -1. Connect wallet (MetaMask, any EVM signer). -2. Switch to the MPP tab. -3. Click "Try MPP" — wallet pops for ERC-20 `transfer`. -4. Wait ~30s for the verify polling to settle. -5. Green "Protected access OK" panel shows `receipt_id`, `payer`, - `tx_hash`, etc. - -Failure modes worth probing: - -- Stop Core mid-flow → see `network_error` / `service_unavailable`. -- Edit the route to a different `amount_wei` than the one being sent → - see `bad_request: settle validation failed`. -- Set `MPP_ROUTE_OPTIONS` to a routeCanonical not registered in Core → see - `route_not_found` on the challenge request. (Routes are otherwise - auto-discovered from Core's public `GET /merchants/:id/mpp/routes`; - there is no static route env var anymore.) +The Express server loads `@goatnetwork/mpp-middleware` lazily and verifies that +receipt against the configured merchant, route, algorithm, and key. If MPP +configuration or middleware loading fails, `/api/mpp/config` returns `503` and +the UI disables payment. + +## Build Checks + +```bash +pnpm build +pnpm build:server +``` + +`pnpm build` validates the client TypeScript/Vite bundle. +`pnpm build:server` validates the Express server output used by `pnpm start`. diff --git a/goatx402-mpp-middleware-go/README.md b/goatx402-mpp-middleware-go/README.md index 9fd529d..e987679 100644 --- a/goatx402-mpp-middleware-go/README.md +++ b/goatx402-mpp-middleware-go/README.md @@ -1,31 +1,118 @@ -# GOAT Flow Go MPP middleware +# MPP Receipt Middleware for the GOAT Flow Integration (Go) -This Go module is distributed as source inside the canonical -[`GOATNetwork/x402`](https://github.com/GOATNetwork/x402) repository. It is not -published from a standalone repository, so a direct `go get` of its logical -module path will not work. +[Machine Payments Protocol (MPP)](https://mpp.dev/overview) is an independent +open protocol; it is not owned or defined by GOAT Flow. This module verifies +the repository-specific signed receipt extension emitted by the current GOAT +Flow MPP integration profile. It is not an official MPP SDK and does not parse +the standard MPP HTTP Credential or generic Receipt representation. -Clone the source next to your application: +The module verifies GOAT Flow profile evidence and does not interact with buyer +or merchant funds. + +Module: + +```text +github.com/goatnetwork/goatflow-mpp-middleware-go +``` + +## Install + +This module is source-only in this repository and is not published from a +standalone source repository. Clone `GOATNetwork/x402`, then bind the logical +module path to the checked-in directory: ```bash -git clone https://github.com/GOATNetwork/x402.git +go mod edit -require=github.com/goatnetwork/goatflow-mpp-middleware-go@v0.0.0 +go mod edit -replace=github.com/goatnetwork/goatflow-mpp-middleware-go=../x402/goatx402-mpp-middleware-go ``` -Then add the logical dependency and a local replacement to your application's -`go.mod`: +Add the imports shown below, then run `go mod tidy`. The checked-in module +declares Go 1.22. -```go -require github.com/goatnetwork/goatflow-mpp-middleware-go v0.0.0 +## Receipt Format -replace github.com/goatnetwork/goatflow-mpp-middleware-go => ../x402/goatx402-mpp-middleware-go +This format is a GOAT Flow integration extension, not the generic MPP Receipt +encoding: + +```text +.. ``` -Adjust the relative path for your checkout, run `go mod tidy`, and import the -package normally: +The base64url segments are unpadded. `algorithm` is `ed25519` or +`hmac-sha256`; the value is not a JWT. The root package exports HTTP +middleware, `FromContext`, rejection constants, and the replay-store contract. +The `receiptspec` subpackage exports the receipt/envelope types plus strict +header/envelope encode/decode and signature helpers. + +## Protect a Handler ```go -import mppmiddleware "github.com/goatnetwork/goatflow-mpp-middleware-go" +package main + +import ( + "crypto/ed25519" + "net/http" + + mppmiddleware "github.com/goatnetwork/goatflow-mpp-middleware-go" + receiptspec "github.com/goatnetwork/goatflow-mpp-middleware-go/receiptspec" +) + +func paidHandler(receiptVerificationKey ed25519.PublicKey, store mppmiddleware.ReceiptIDStore) http.Handler { + middleware := mppmiddleware.Middleware(mppmiddleware.Config{ + MerchantID: "merchant_123", + RouteCanonical: "GET:api:data", + Algorithm: receiptspec.AlgEd25519, + Ed25519Public: receiptVerificationKey, + ReceiptIDStore: store, + }) + + return middleware(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + receipt, ok := mppmiddleware.FromContext(r.Context()) + if !ok { + http.Error(w, "missing verified receipt", http.StatusInternalServerError) + return + } + + w.Write([]byte(receipt.ReceiptID)) + })) +} ``` -The local `replace` directive is required until this module has an independently -published source location. +For HMAC verification, use `receiptspec.AlgHMACSHA256` and set +`Config.HMACSecret`. + +## Verification + +The middleware checks: + +1. One well-formed `Payment-Receipt` header is present. +2. The configured Ed25519 or HMAC-SHA256 signature is valid. +3. `merchant_id` matches `Config.MerchantID`. +4. `request_canonical` matches `Config.RouteCanonical` exactly or as a + colon-delimited suffix. +5. The receipt has not expired. +6. When configured, the receipt ID has not already been consumed. + +Rejected requests use `application/problem+json` and expose a stable `error` +reason. Signature, audience, malformed receipt, and replay failures use `401`; +route mismatch and expiry use `402`; receipt-store failure uses `503`. + +## Replay Protection + +`ReceiptIDStore.MarkConsumed` must be concurrency-safe and should be atomic +across replicas. Use Redis `SET NX` with expiry or a database uniqueness +constraint. A process-local map cannot prevent replay between service replicas. + +## Operational Hook + +`Config.OnReject` receives the request and stable rejection reason before the +response is written. Use it for metrics and server-side logs. It must not write +the response. + +`Middleware` panics at construction time for invalid configuration so deployment +errors fail during startup instead of on the first paid request. + +See: + +- [GOAT Flow MPP integration profile](../docs/mpp.md) +- [Runnable example](./example_test.go) diff --git a/goatx402-mpp-middleware-ts/README.md b/goatx402-mpp-middleware-ts/README.md new file mode 100644 index 0000000..2836fc6 --- /dev/null +++ b/goatx402-mpp-middleware-ts/README.md @@ -0,0 +1,150 @@ +# MPP Receipt Middleware for the GOAT Flow Integration + +[Machine Payments Protocol (MPP)](https://mpp.dev/overview) is an independent +open protocol; it is not owned or defined by GOAT Flow. This package verifies +the repository-specific signed receipt extension emitted by the current GOAT +Flow MPP integration profile. It is not an official MPP SDK and does not parse +the standard MPP HTTP Credential or generic Receipt representation. + +The package verifies GOAT Flow profile evidence and does not interact with +buyer or merchant funds. + +Package name: + +```text +@goatnetwork/mpp-middleware +``` + +The package exposes a framework-agnostic verifier plus separate Express and +Fastify entry points. + +## Install + +This package is source-only in this repository and is not included in the npm +release runbook. As of July 23, 2026, the package name is not available from the +public npm registry. Build and install the checked-in directory locally instead +of treating the name as a published dependency. From a sibling application: + +```bash +cd ../goatx402-mpp-middleware-ts +npm install +npm run build +cd ../your-application +npm install ../goatx402-mpp-middleware-ts +``` + +Install `express` or `fastify` in the application that uses the corresponding +adapter. + +## Receipt Format + +This format is a GOAT Flow integration extension, not the generic MPP Receipt +encoding: + +```text +.. +``` + +The base64url segments are unpadded. `algorithm` is the plain string +`ed25519` or `hmac-sha256`; this is not a JWT. The package root exports +`Receipt`, `Envelope`, `decodeHeader`, `decodeEnvelope`, and `signingBytes`. + +## Express + +```ts +import express from "express"; +import { expressMiddleware } from "@goatnetwork/mpp-middleware/express"; + +const app = express(); + +app.get( + "/api/data", + expressMiddleware({ + merchantId: "merchant_123", + routeCanonical: "GET:api:data", + algorithm: "ed25519", + ed25519Public: receiptVerificationPublicKey, + store: receiptStore, + }), + (req, res) => { + res.json({ + paid: true, + receiptId: req.mppReceipt?.receipt_id, + }); + }, +); +``` + +## Fastify + +```ts +import Fastify from "fastify"; +import { fastifyPreHandler } from "@goatnetwork/mpp-middleware/fastify"; + +const app = Fastify(); + +app.get("/api/data", { + preHandler: fastifyPreHandler({ + merchantId: "merchant_123", + routeCanonical: "GET:api:data", + algorithm: "hmac-sha256", + hmacSecret, + store: receiptStore, + }), +}, async (request) => ({ + paid: true, + receiptId: request.mppReceipt?.receipt_id, +})); +``` + +`fastifyPlugin` is also available for an encapsulated group of routes. Prefer +per-route configuration when each route has a different canonical identifier. + +## Verification + +The middleware checks: + +1. `Payment-Receipt` is present and parseable. +2. The Ed25519 or HMAC-SHA256 signature is valid. +3. `merchant_id` matches the configured merchant. +4. `request_canonical` matches the configured route exactly or as a + colon-delimited suffix. +5. The receipt has not expired. +6. When configured, `receipt_id` has not already been consumed. + +Stable rejection reasons: + +| HTTP | Reason | +| ---: | --- | +| `401` | `payment_required`, `invalid_payment_receipt`, `invalid_signature`, `audience_mismatch`, `receipt_already_consumed` | +| `402` | `route_mismatch`, `receipt_expired` | +| `503` | `receipt_store_unavailable` | + +The wire response contains only `{ "error": "" }`. Use `onReject` for +operator diagnostics; never reflect its untrusted `detail` value to clients. + +## Replay Protection + +`InMemoryReceiptIDStore` is suitable only for tests and a single process. A +multi-replica production service needs an atomic shared store such as Redis or a +database unique constraint with TTL. + +## Core Verifier + +```ts +import { + decodeHeader, + verifyReceipt, +} from "@goatnetwork/mpp-middleware"; + +const result = await verifyReceipt(config, paymentReceiptHeader); +if (!result.ok) { + console.error(result.status, result.reason); +} + +const decoded = decodeHeader(paymentReceiptHeader); +console.log(decoded.receipt.receipt_id, decoded.algorithm); +``` + +See [GOAT Flow MPP integration profile](../docs/mpp.md) for the implemented +buyer, challenge, transfer, and receipt lifecycle. diff --git a/goatx402-quickpay/README.md b/goatx402-quickpay/README.md index ee5a276..af9444e 100644 --- a/goatx402-quickpay/README.md +++ b/goatx402-quickpay/README.md @@ -4,46 +4,87 @@ Public payer/agent library and CLI for **GOAT Flow QuickPay**. It is generic, stateless, and manifest-driven: it does not know any specific merchant — the merchant identity comes entirely from the link a merchant shares. +The library coordinates a buyer-wallet transfer directly to the instructed +merchant recipient and verifies the resulting session or receipt. It does not +interact with merchant customer funds as an intermediary. + +[Machine Payments Protocol (MPP)](https://mpp.dev/overview) is an independent +open protocol. QuickPay's `pay-mpp` command uses GOAT Flow's current MPP adapter, +whose JSON challenge/verify endpoints and signed three-segment receipt are +GOAT-specific. It is not a generic MPP transport, and this repository contains +no interoperability test with official MPP SDKs. + +## Install + +```bash +npm install goatflow-quickpay + +# Required only for the built-in pay-mpp backend: +npm install goatflow-sdk +``` + ```bash # show available commands npx goatflow-quickpay --help # inspect a merchant's payment capabilities (machine-readable JSON) -npx goatflow-quickpay inspect https://pay.goat.network/quickpay/acme/agent.md --json +npx goatflow-quickpay inspect https://flow-quickpay.goat.network/quickpay/acme/agent.md --json # Provide the payer key WITHOUT writing the secret into a command (shell history and # agent transcripts leak it): set QUICKPAY_PRIVATE_KEY in your environment out-of-band # (e.g. from a secret manager), or pass --wallet-file (a chmod 600 key file). # pay a custom amount via x402 -npx goatflow-quickpay pay-x402 https://pay.goat.network/quickpay/acme/agent.md \ +npx goatflow-quickpay pay-x402 https://flow-quickpay.goat.network/quickpay/acme/agent.md \ --amount 12.50 --token-contract 0xToken --chain 4217 # buy a fixed-price product (the merchant prices it; you only pick the token + chain) -npx goatflow-quickpay pay-product https://pay.goat.network/quickpay/acme/agent.md \ +npx goatflow-quickpay pay-product https://flow-quickpay.goat.network/quickpay/acme/agent.md \ --product mug --token-contract 0xToken --chain 4217 # pay a fixed MPP route -npx goatflow-quickpay pay-mpp https://pay.goat.network/quickpay/acme/agent.md \ +npx goatflow-quickpay pay-mpp https://flow-quickpay.goat.network/quickpay/acme/agent.md \ --route GET:api:data ``` `inspect` lists the merchant's products (`product_key`, `name`, `price`) under `x402_products`. A product carries a **token-agnostic** decimal price; the buyer picks the token, and `pay-product` re-denominates the price in that token and -**refuses to broadcast unless the session's quoted amount matches** — so the -server can never charge more than the manifest-advertised price. +**refuses to broadcast unless the session's quoted amount matches**. This +prevents the client from submitting a transfer above the manifest-advertised +price. Library usage: ```ts import { QuickPayClient } from 'goatflow-quickpay' -const client = new QuickPayClient('https://pay.goat.network/quickpay/acme/agent.md') +const client = new QuickPayClient('https://flow-quickpay.goat.network/quickpay/acme/agent.md') const manifest = await client.loadManifest() const summary = await client.inspect() ``` +Library options are camelCase, not raw API JSON. `payX402()` accepts `amount`, +`chainId`, `tokenSymbol`/`tokenContract`, `memo`, and `idempotencyKey`; +`payProduct()` accepts `productKey`, chain/token selection, and optional +`idempotencyKey`. The client derives wire `merchant_id` from the trusted URL and +`payer_addr` from the payment backend. It does not currently expose a +`clientReferenceId` option. + +The corresponding custom-amount wire request is: + +```json +{ + "merchant_id": "acme", + "payer_addr": "0xBuyer", + "chain_id": 4217, + "token_contract": "0xToken", + "amount_wei": "12500000", + "memo": "invoice-123", + "idempotency_key": "invoice-123:buyer" +} +``` + ## Security model — the host is the trust anchor The input link's **origin** (`scheme://host`) is the single trust anchor: @@ -53,31 +94,45 @@ The input link's **origin** (`scheme://host`) is the single trust anchor: closed**. - Every endpoint the CLI calls (session create/status, MPP challenge/verify) is **derived from the origin**. Absolute URLs embedded in the manifest are never - trusted, so a tampered manifest cannot redirect a payment to another host. + trusted, so a tampered manifest cannot redirect the buyer's transfer + instruction to another host. - The origin must be **`https://`** (plaintext `http://` is rejected except for loopback hosts in local dev), because an on-path attacker on plaintext could swap the session's `payTo` and redirect funds. -Share only links on a host you trust (e.g. `pay.goat.network`). +Share only links on a host you trust (e.g. `flow-quickpay.goat.network`). + +This same-origin rule is specific to QuickPay-driven MPP. A standalone +`goatflow-sdk` `MPPClient` instead uses the deployment's configured Core/API +`coreUrl`. + +Manifest validation is a discovery/preflight boundary, not the final transfer +instruction. Non-array token or route lists are normalized to empty lists, +`payX402()` does not require the manifest's `custom_amount` flag, and Product +limits remain server-authoritative. The returned session or MPP challenge +provides the current transfer terms. ## Retry safety (avoid double-paying) -`pay-x402`, `pay-product`, and `pay-mpp` are designed so a retry never silently pays twice: +QuickPay session terminal states are `PAYMENT_CONFIRMED`, `EXPIRED`, `FAILED`, +and `CANCELLED`. This is separate from the Server SDK order model, where +`INVOICED` is also a successful terminal state. -- The broadcast `tx_hash` is always returned (even if confirmation polling fails), - so you can resume by polling rather than re-sending. -- If a broadcast payment is reported `EXPIRED`, the client performs bounded - grace polls because a pre-expiry transfer can still confirm late. If it remains - expired, reconcile by `session_id` and `tx_hash`; **do not pay again**. - A **reused** session (same payment intent) is **not auto-paid** — the CLI resumes/polls it. Only pass `--force` to `pay-x402`/`pay-product` to broadcast on a reused session, and only when you are certain no payment was sent (e.g. the wallet rejected the first attempt). -- If `pay-mpp` fails after broadcasting, the JSON output preserves the `tx_hash` - and `challenge` so you can resume verification instead of paying again. -- A `503 receipt_unavailable` from MPP verification is retryable and may require - operator reconciliation; preserve the existing transaction and never - rebroadcast merely because the receipt response is temporarily unavailable. +- Polling uses `pollTimeoutMs` as a hard overall cap: sleeps and individual + status requests are bounded by the remaining time. +- A broadcast transaction hash is retained across status-fetch failures. For a + fresh payment, a server-confirmed fee-bump replacement can become the final + hash; a forced reused session does not replace its local hash with an + unrelated prior server value. +- If a known transaction is reported `EXPIRED`, the client performs five + bounded grace polls because a pre-expiry transfer can confirm late. +- If MPP fails after broadcast, preserve the reported transaction hash and + challenge context. Reconcile by `session_id` and `tx_hash`; never rebroadcast + merely because status polling or receipt verification failed. ## Configuration @@ -87,8 +142,9 @@ Share only links on a host you trust (e.g. `pay.goat.network`). and agent transcripts, so it warns — prefer the env var or `--wallet-file`. - Token: prefer `--token-contract
` from the manifest. `--token ` is accepted only when the symbol is unique on that chain. -- RPC URL (for the on-chain transfer): `--rpc `, or `QUICKPAY_RPC_`, - or `QUICKPAY_RPC`. +- RPC URL precedence (highest first): `QUICKPAY_RPC_`, `--rpc `, + then `QUICKPAY_RPC`. A per-chain environment variable intentionally overrides + the explicit flag. ## Architecture @@ -101,15 +157,19 @@ Share only links on a host you trust (e.g. `pay.goat.network`). - `backend-mpp-sdk.ts` — `pay-mpp` delegates to `goatflow-sdk`'s `MPPClient` (an **optional** dependency loaded at runtime). -> The live on-chain flows (the actual ERC20 transfer / MPP settlement) require a -> chain + wallet and are not exercised by the unit tests; the manifest handling, -> trust-anchor enforcement, and request orchestration are. +> The live on-chain flows (the ERC-20 transfer and MPP transfer/receipt +> verification) require a chain + wallet and are not exercised by the unit tests; +> the manifest handling, trust-anchor enforcement, and request orchestration are. ## Develop ```bash -pnpm install -pnpm typecheck # tsc --noEmit -pnpm test:run # vitest -pnpm build # emit dist/ +npm install +npm run typecheck # tsc --noEmit +npm run test:run # vitest +npm run build # emit dist/ ``` + +See the [QuickPay integration guide](../docs/goat-flow-integration.md#8-quickpay), +[GOAT Flow MPP integration profile](../docs/mpp.md), and +[Changelog](./CHANGELOG.md). diff --git a/goatx402-sdk-server-go/README.md b/goatx402-sdk-server-go/README.md index a979d1f..c908fd2 100644 --- a/goatx402-sdk-server-go/README.md +++ b/goatx402-sdk-server-go/README.md @@ -1,31 +1,136 @@ -# GOAT Flow Go server SDK +# GOAT Flow Go Server SDK -This Go module is distributed as source inside the canonical -[`GOATNetwork/x402`](https://github.com/GOATNetwork/x402) repository. It is not -published from a standalone repository, so a direct `go get` of its logical -module path will not work. +Server-side Go client for authenticated GOAT Flow merchant APIs. -Clone the source next to your application: +The client coordinates API records and reads reported results. It does not move +or control buyer funds; DIRECT transfers go from the buyer wallet to the +merchant receiving address. + +Module: + +```text +github.com/goatnetwork/goatflow-sdk-server +``` + +## Install + +This Go module is source-only in this repository, is outside the npm release +runbook, and is not published from a standalone source repository. Clone +`GOATNetwork/x402`, then bind the logical module path to the checked-in +directory: ```bash -git clone https://github.com/GOATNetwork/x402.git +go mod edit -require=github.com/goatnetwork/goatflow-sdk-server@v0.0.0 +go mod edit -replace=github.com/goatnetwork/goatflow-sdk-server=../x402/goatx402-sdk-server-go ``` -Then add the logical dependency and a local replacement to your application's -`go.mod`: +Add the import shown below, then run `go mod tidy`. The checked-in module +declares Go 1.25. + +## Configure ```go -require github.com/goatnetwork/goatflow-sdk-server v0.0.0 +import goatflow "github.com/goatnetwork/goatflow-sdk-server" -replace github.com/goatnetwork/goatflow-sdk-server => ../x402/goatx402-sdk-server-go +client := goatflow.NewClient(goatflow.Config{ + BaseURL: os.Getenv("GOATX402_API_URL"), + APIKey: os.Getenv("GOATX402_API_KEY"), + APISecret: os.Getenv("GOATX402_API_SECRET"), +}) ``` -Adjust the relative path for your checkout, run `go mod tidy`, and import the -package with its GOAT Flow name: +Keep the API Secret on the server. The client signs authenticated requests with +`X-API-Key`, `X-Timestamp`, `X-Nonce`, and `X-Sign`. + +## Create an Order ```go -import goatflow "github.com/goatnetwork/goatflow-sdk-server" +order, err := client.CreateOrder(ctx, goatflow.CreateOrderParams{ + DappOrderID: "order-123", + ChainID: 48816, + TokenSymbol: "USDC", + FromAddress: "0xPayer", + AmountWei: "100000", +}) +if err != nil { + return err +} + +fmt.Println(order.OrderID, order.PayToAddress, order.AmountWei) +``` + +`CreateOrder` accepts the API's normal HTTP `402 Payment Required` response and +normalizes the x402 challenge into `Order`. Use `CreateOrderRaw` when the complete +challenge object is required. + +For an explicitly operator-provisioned compatibility flow, +`CallbackCalldata` may return a `CalldataSignRequest` and optional +`SignatureEndpoint`. Submit the browser signature with +`SubmitCalldataSignature`. This is not part of the current public DIRECT +onboarding path; see the API Reference for the complete field contract. + +## Hosted Checkout + +```go +session, err := client.CreateCheckoutSession(ctx, goatflow.CreateCheckoutSessionParams{ + CheckoutType: "DIRECT", + Price: "9.99", + SuccessURL: "https://merchant.example/pay/success", + CancelURL: "https://merchant.example/pay/cancel", +}) +if err != nil { + return err +} + +http.Redirect(w, r, session.URL, http.StatusFound) ``` -The local `replace` directive is required until this module has an independently -published source location. +Nested checkout values are JSON-stringified by the client before HMAC signing. +`CheckoutSession.CheckoutType` is a string; the current public merchant path +uses `DIRECT`. The types retain compatibility-only fields and +`CreateDelegateCheckoutSession` as a deprecated wrapper for explicitly +operator-provisioned deployments. Do not infer merchant availability from these +exports. The complete compatibility field mapping and callback trust boundary +live in the +[API Reference appendix](../docs/goat-flow-api-reference.md#appendix-a-operator-provisioned-callback-compatibility). + +## Other Operations + +| Method | Purpose | +| --- | --- | +| `GetOrderStatus` | Read the current order state | +| `GetOrderProof` | Retrieve the server-issued payment record | +| `SubmitCalldataSignature` | Submit an operator-provisioned EIP-712 callback signature | +| `CancelOrder` | Request cancellation of an eligible order | +| `GetMerchant` | Public merchant lookup; no HMAC credentials required | +| `SetHTTPClient` | Supply a custom `http.Client` | + +`WaitForConfirmation` returns on successful `PAYMENT_CONFIRMED` or `INVOICED`, +and on `FAILED`, `EXPIRED`, or `CANCELLED`. Core can move a DIRECT order from +`PAYMENT_CONFIRMED` to `INVOICED` in one watcher transaction, so a poller may +observe only `INVOICED`. The helper waits one interval before its first read and +suppresses transient `GetOrderStatus` errors until a later poll, timeout, or +context cancellation. + +Every request uses the configured HTTP client's deadline; the default client +timeout is 30 seconds. HTTP `402` is accepted as the expected response only for +order creation. Checkout, status, proof, signature, and cancellation fail +closed on an unexpected `402`. + +`GetOrderProof` returns a server-issued payment record whose historical +`Signature` field is not a signature or attestation. It is the Keccak256 digest +of `order_id`, `tx_hash`, `log_index`, `from_addr`, `to_addr`, `amount_wei`, and +`from_chain_id`, concatenated in that exact order without separators. It does +not cover `status`. Verify `Payload.TxHash` on-chain when independent proof is +required. + +## Runtime Configuration + +Do not hardcode chain, token, fee, expiry, or confirmation assumptions. Read the +active API response, x402 challenge, and merchant configuration. + +See: + +- [API Reference](../docs/goat-flow-api-reference.md) +- [Developer Quick Start](../docs/goat-flow-developer-quickstart.md) +- [Integration Guide](../docs/goat-flow-integration.md) diff --git a/goatx402-sdk-server-ts/README.md b/goatx402-sdk-server-ts/README.md index 8291d11..5958680 100644 --- a/goatx402-sdk-server-ts/README.md +++ b/goatx402-sdk-server-ts/README.md @@ -1,13 +1,15 @@ # goatflow-sdk-server -Server-side TypeScript SDK for **GOAT Flow** payment integration. It signs -authenticated API requests with your merchant credentials (HMAC with -per-request nonce and replay protection), so the key and secret stay on your -backend — **never expose them to the frontend**. Public endpoints such as -`getMerchant` need no credentials. +Server-side TypeScript SDK for GOAT Flow. It creates orders and Hosted Checkout +Sessions, signs protected API requests with merchant HMAC credentials, polls +order status, and retrieves server-issued payment records. -Pair it with [`goatflow-sdk`](https://github.com/GOATNetwork/x402/tree/main/goatx402-sdk) -in the browser: this package creates the order, the frontend SDK pays it. +The SDK coordinates API records and reads reported results. It does not +independently verify payment records or on-chain events, and it +does not move or control buyer funds. DIRECT transfers go from the buyer wallet +to the merchant receiving address. + +Never expose the API secret to browser code. ## Install @@ -15,66 +17,143 @@ in the browser: this package creates the order, the frontend SDK pays it. npm install goatflow-sdk-server ``` -## Quick start +Requires Node.js >= 18. + +Install `goatflow-sdk` separately when the browser also uses the mapped +`Order` with `PaymentHelper`. + +## Create an order -```typescript -import { GoatFlowClient } from 'goatflow-sdk-server' +```ts +import { GoatFlowClient, type Order as ServerOrder } from 'goatflow-sdk-server' +import type { Order as ClientOrder } from 'goatflow-sdk' const client = new GoatFlowClient({ - baseUrl: 'https://flow-api.goat.network', + baseUrl: process.env.GOATX402_API_URL ?? 'https://flow-api.goat.network', apiKey: process.env.GOATX402_API_KEY!, apiSecret: process.env.GOATX402_API_SECRET!, }) -// Create an order -const order = await client.createOrder({ +function toClientOrder(order: ServerOrder, fromAddress: string): ClientOrder { + return { + orderId: order.orderId, + flow: order.flow, + tokenSymbol: order.tokenSymbol, + tokenContract: order.tokenContract, + fromAddress, + payToAddress: order.payToAddress, + chainId: order.fromChainId, + amountWei: order.amountWei, + expiresAt: order.expiresAt, + calldataSignRequest: order.calldataSignRequest, + } +} + +const fromAddress = '0xUser' +const serverOrder = await client.createOrder({ dappOrderId: 'my-order-123', - chainId: 97, + chainId: 2345, tokenSymbol: 'USDC', - tokenContract: '0x...', - fromAddress: userWalletAddress, + fromAddress, amountWei: '1000000', }) -// Return the order to the frontend for payment -res.json(order) +res.json(toClientOrder(serverOrder, fromAddress)) ``` +Core returns HTTP `402 Payment Required` for successful order creation. The SDK +treats it as success. Use `createOrderRaw()` when the literal x402 challenge is +needed. + +For an explicitly operator-provisioned compatibility flow, `callbackCalldata` +may produce `calldata_sign_request` and a signature endpoint. Submit the returned +EIP-712 signature with `submitCalldataSignature(orderId, signature)`. This is not +part of the current public DIRECT onboarding path; see the API Reference for the +complete field contract. + +The server and browser `Order` types differ. The server type has +`fromChainId` / `payToChainId` and no `fromAddress`; map it explicitly as shown. + +## Hosted Checkout + +```ts +const session = await client.createCheckoutSession({ + checkoutType: 'DIRECT', + price: '9.99', + lineItems: [{ name: 'Mug', amount: '9.99', quantity: 1 }], + clientReferenceId: 'cart-123', +}) +``` + +The returned `checkoutType` is typed as `string`; the current public merchant +path uses `DIRECT`. The types retain compatibility-only fields and +`createDelegateCheckoutSession()` as a deprecated wrapper for explicitly +operator-provisioned deployments. Do not infer merchant availability from these +exports. + +Arrays and objects are JSON-stringified into signed scalar fields by the SDK. + +The complete compatibility field mapping and callback trust boundary live in +the [API Reference appendix](../docs/goat-flow-api-reference.md#appendix-a-operator-provisioned-callback-compatibility). + +## HMAC + +Every protected request includes: + +- `X-API-Key` +- `X-Timestamp` +- `X-Nonce` +- `X-Sign` + +The signature covers top-level body fields plus `api_key`, `timestamp`, and +`nonce`, sorted and joined as `key=value&...`, then HMAC-SHA256 hex encoded. + ## API surface | Method | Purpose | -|--------|---------| -| `createOrder(params)` | Create a payment order | -| `createOrderRaw(params)` | Create an order and return the raw x402 `402 Payment Required` payload | -| `createCheckoutSession(params)` | Create a hosted checkout session (DIRECT and DELEGATE flows) | -| `createDelegateCheckoutSession(params)` | Deprecated DELEGATE-only wrapper, kept for compatibility | -| `getOrderStatus(orderId)` | Fetch current order status and details | -| `getOrderProof(orderId)` | Fetch the payment record; `signature` is an unsigned hash of a subset of payload fields, not an attestation | -| `submitCalldataSignature(orderId, signature)` | Submit the buyer's EIP-712 calldata signature | -| `cancelOrder(orderId)` | Cancel an order | -| `getMerchant(merchantId)` | Fetch public merchant info (tokens, receiving config) | -| `waitForConfirmation(orderId, ...)` | Poll until the order reaches a terminal state (confirmed or invoiced on success; failed, expired, or cancelled otherwise) or polling times out | - -Helpers: `calculateSignature` / `signRequest` (request signing), -`toCAIP2` / `fromCAIP2` / `parseX402Header` (x402 header and chain-id utilities). - -API failures throw the runtime-exported `GoatFlowError` class. Its `code` and -`status` fields carry structured server details when available; authenticated -request failures also retain the raw `responseBody` for diagnostics. - -All requests have a 30-second hard deadline. `waitForConfirmation` additionally -honors its overall timeout, retries transient failures, and immediately surfaces -deterministic 4xx errors other than `408` and `429`. - -## Requirements - -Node.js >= 18. - -## Documentation - -Release notes: [`CHANGELOG.md`](./CHANGELOG.md). Backend integration examples -live in the repository's demo server -([`goatx402-demo`](https://github.com/GOATNetwork/x402/tree/main/goatx402-demo)). +| --- | --- | +| `createOrder(params)` | Create and normalize an x402 order | +| `createOrderRaw(params)` | Return the raw x402 response | +| `createCheckoutSession(params)` | Create a Hosted Checkout Session | +| `createDelegateCheckoutSession(params)` | Deprecated compatibility wrapper | +| `getOrderStatus(orderId)` | Read order status/details | +| `getOrderProof(orderId)` | Read the server-issued payment record | +| `submitCalldataSignature(orderId, signature)` | Submit an operator-provisioned EIP-712 callback signature | +| `cancelOrder(orderId)` | Cancel a `CHECKOUT_VERIFIED` order | +| `getMerchant(merchantId)` | Read public merchant information | +| `waitForConfirmation(orderId, options)` | Poll status | + +`waitForConfirmation()` returns on successful `PAYMENT_CONFIRMED` or +`INVOICED`, and on `FAILED`, `EXPIRED`, or `CANCELLED`. Core can move a DIRECT +order from `PAYMENT_CONFIRMED` to `INVOICED` in one watcher transaction, so a +poller may observe only `INVOICED`. The helper polls immediately, retries +network failures, request timeouts, `408`, `429`, and server errors within the +overall deadline, and surfaces other deterministic 4xx errors immediately. + +Every request has a 30-second hard deadline, further bounded by the remaining +`waitForConfirmation()` timeout. HTTP `402` is accepted as the expected response +only for order creation. Checkout, status, proof, signature, and cancellation +fail closed on an unexpected `402`. + +API failures throw the runtime-exported `GoatFlowError`. Its `code`, `status`, +and authenticated-request `responseBody` fields preserve server diagnostics +when available. + +`getOrderProof()` returns a server-issued payment record whose historical +`signature` field is not a signature or attestation. It is the Keccak256 digest +of `order_id`, `tx_hash`, `log_index`, `from_addr`, `to_addr`, `amount_wei`, and +`from_chain_id`, concatenated in that exact order without separators. It does +not cover `status`. Verify `payload.tx_hash` on-chain when independent proof is +required. + +Helpers: + +- `calculateSignature`, `signRequest` +- `toCAIP2`, `fromCAIP2`, `parseX402Header` + +See the canonical [API contract](../docs/goat-flow-api-reference.md), +[integration guide](../docs/goat-flow-integration.md), [demo server](../goatx402-demo/server/index.ts), +and [Changelog](./CHANGELOG.md). ## License diff --git a/goatx402-sdk/README.md b/goatx402-sdk/README.md index 3e68f0a..0d05f34 100644 --- a/goatx402-sdk/README.md +++ b/goatx402-sdk/README.md @@ -1,95 +1,194 @@ # goatflow-sdk -Frontend TypeScript SDK for **GOAT Flow** payment integration, built on -[ethers v6](https://docs.ethers.org/v6/). It handles the wallet side of a -payment: ERC20 operations, EIP-712 signing, payment execution, and the MPP -(Machine Payments Protocol) buyer flow. +Frontend TypeScript SDK for GOAT Flow, built on ethers v6. It provides: -> **This SDK does NOT handle API authentication.** Order creation requires an -> API key/secret that must never reach the browser — use -> [`goatflow-sdk-server`](https://github.com/GOATNetwork/x402/tree/main/goatx402-sdk-server-ts) -> on your backend to create orders, then hand the order to this SDK for payment. +- `PaymentHelper` for buyer-authorized browser ERC-20 transfers +- `ERC20Token` approval and transfer helpers +- EIP-712 callback-signing utilities +- `MPPClient` for challenge, buyer transfer, verification, and receipt recovery + +This package does not hold merchant API credentials or create authenticated +orders. Use `goatflow-sdk-server` or the Go server SDK on your backend. +Buyer-wallet transfers go directly to the instructed recipient; GOAT Flow and +this SDK do not act as an intermediary for merchant customer funds. ## Install ```bash -npm install goatflow-sdk +npm install goatflow-sdk ethers ``` -## Quick start +The package declares Node.js >= 18 for non-browser use. + +## Pay a server-created order + +The server and browser SDK `Order` types are different. Your backend must map: -```typescript -import { PaymentHelper } from 'goatflow-sdk' +- server `fromChainId` -> browser `chainId` +- the create-order payer -> browser `fromAddress` + +```ts +import { PaymentHelper, type Order } from 'goatflow-sdk' import { ethers } from 'ethers' -// Connect wallet +const order: Order = await fetch('/api/orders', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ productId: 'mug' }), +}).then((response) => response.json()) + const provider = new ethers.BrowserProvider(window.ethereum) const signer = await provider.getSigner() + +const network = await provider.getNetwork() +if (Number(network.chainId) !== order.chainId) { + throw new Error(`Switch wallet to chain ${order.chainId}`) +} + +const payer = await signer.getAddress() +if (payer.toLowerCase() !== order.fromAddress.toLowerCase()) { + throw new Error('Connected wallet does not match the order payer') +} + +if (Math.floor(Date.now() / 1000) >= order.expiresAt) { + throw new Error('Order expired') +} + const payment = new PaymentHelper(signer) -// Get an order from YOUR backend (created there with goatflow-sdk-server) -const order = await fetch('/api/orders', { - method: 'POST', - body: JSON.stringify({ /* ... */ }), -}).then((r) => r.json()) +if (order.calldataSignRequest) { + throw new Error( + 'Use the operator-provisioned callback flow before paying this order', + ) +} -// Execute the payment const result = await payment.pay(order) -if (result.success) { - console.log('Payment successful:', result.txHash) -} +if (!result.success) throw new Error(result.error ?? 'Payment failed') ``` -## MPP (Machine Payments Protocol) +This basic example covers DIRECT. In an explicitly operator-provisioned callback +environment, map `calldataSignRequest`, sign its exact EIP-712 payload on the +returned domain chain, submit it through the merchant backend, and switch back +to the transfer source chain before paying. See the +[full integration guide](../docs/goat-flow-integration.md#51-typescript-client). + +`PaymentHelper.pay()` checks the token balance, sends +`transfer(order.payToAddress, order.amountWei)`, waits for a successful receipt, +and returns a `PaymentResult`. The connected buyer signer sends tokens directly +to the instructed recipient; the SDK does not hold, route, or disburse merchant +customer funds. It catches transfer errors and returns +`{ success: false, error }`; it does not perform chain, payer, or expiry checks. +It also does not classify `TRANSACTION_REPLACED`, so reconcile a wallet speed-up +and backend order before retrying a result reported as failed. + +All supported order flows still use a user-side ERC-20 transfer to +`payToAddress`: + +- `ERC20_DIRECT`: merchant recipient +- `ERC20_3009`: operator-provisioned compatibility recipient +- `ERC20_APPROVE_XFER`: operator-provisioned compatibility recipient + +DIRECT is the standard/default public path. The other values are retained for +explicitly provisioned environments and are not part of public onboarding. + +## MPP + +[Machine Payments Protocol (MPP)](https://mpp.dev/overview) is an independent +open protocol. `MPPClient` implements GOAT Flow's current adapter, not the +standard MPP HTTP Challenge/Credential/Receipt exchange. Its JSON +challenge/verify endpoints and signed three-segment receipt are GOAT-specific, +and this repository has no official-SDK interoperability test. + +```ts +import { MPPClient, MPPError } from 'goatflow-sdk' + +const mpp = new MPPClient({ + coreUrl: 'https://flow-api.goat.network', // must not end with "/" + signer, +}) -```typescript -import { MPPClient } from 'goatflow-sdk' +async function payForRoute() { + try { + return await mpp.pay({ + merchantId: 'merchant_123', + routeCanonical: 'GET:api:data', + onPhase: (phase) => console.log(phase), + }) + } catch (error) { + if (error instanceof MPPError && error.recoverable) { + // The transfer was already broadcast. Resume verification; do not pay again. + return mpp.verifyChallenge(error.recoverable) + } + throw error + } +} -const mpp = new MPPClient({ coreUrl: 'https://core.example.com', signer }) -const result = await mpp.pay({ - merchantId: 'acme', - routeCanonical: 'GET:api:data', +const result = await payForRoute() +await fetch('/api/data', { + headers: { 'Payment-Receipt': result.receiptHeader }, }) ``` -The client runs the challenge → pay → verify flow, retries safely, and -recovers from wallet fee-bump replacements. Failures carry an `MPPError` with -a machine-readable code and, where possible, the transaction context needed to -resume verification without paying twice. - -## ERC20 approvals - -`PaymentHelper.approveToken` / `ERC20Token.setApproval` / `ERC20Token.ensureApproval`: - -- **Exact amounts by default.** Unlimited approval is an explicit opt-in via - `{ unlimited: true }` — a truthy non-boolean is rejected at runtime. -- **No wasted transactions.** Nothing is sent when the allowance already - equals the requested value; `ensureApproval` leaves a sufficient allowance - untouched. -- **USDT-style compatibility, probe-first.** Changing a non-zero allowance - first simulates the direct write with a free `eth_call`: standard ERC20s - get a single approval — no reset, no transient zero-allowance window. Only - tokens that reject non-zero → non-zero transitions (USDT-style, or - providers without simulation support) fall back to a confirmed `approve(0)` - reset first; if the final approval afterwards fails or is rejected in the - wallet, the allowance remains zero — re-run the call to re-submit. -- **Fee-bump safe.** A wallet-repriced replacement of the same approve call is - followed and accepted; a cancellation or a same-nonce transaction with - different calldata stays a failure. -- **Validated inputs.** Amounts must be non-negative `bigint`s within uint256 - and options must be an object, checked before any transaction — so - plain-JavaScript callers can't zero an allowance and then fail to encode. - -## Requirements - -- Node.js >= 18 (when used outside a browser). -- Browsers: Chrome 80+, Firefox 75+, Safari 14+, Edge 80+. - -## Documentation - -Full integration guide (Chinese + English), API tables, and flow diagrams: -[`docs/Integration.md`](https://github.com/GOATNetwork/x402/blob/main/goatx402-sdk/docs/Integration.md). -Release notes: [`CHANGELOG.md`](./CHANGELOG.md). +This is the standalone GOAT Flow MPP adapter, so `coreUrl` is the Core/API origin +configured for the deployment. QuickPay `pay-mpp` derives its adapter origin +from the trusted QuickPay link instead. The returned challenge is authoritative +for amount, chain, token, recipient, expiry, MAC, and pricing version. + +Behavior verified by tests: + +- `POST /mpp/v1/challenge`: HTTP `402` is success +- chain and challenge expiry are checked before broadcasting +- `payChallenge()` returns `{ txHash, tx }` without waiting locally for mining +- `POST /mpp/v1/verify`: `202`, `429`, network errors, and eligible `5xx` + responses are retried with bounded backoff +- successful verification requires the GOAT Flow profile's three-segment + `Payment-Receipt` extension: + `base64url(JSON(receipt)).base64url(signature).algorithm` +- matching fee-bump replacements are followed +- post-broadcast failures include `MPPError.recoverable` + +Keep `onPhase` non-throwing or catch its errors locally; application callback +errors can replace the SDK's expected `MPPError`. + +For browser use, Core must allow the DApp origin and expose +`Payment-Receipt`; the protected resource must allow that origin and the +`Payment-Receipt` request header. Otherwise use a server-side buyer client. + +## ERC-20 approvals + +`PaymentHelper.approveToken`, `ERC20Token.setApproval`, and +`ERC20Token.ensureApproval`: + +- approve exact amounts by default; `{ unlimited: true }` is explicit +- avoid a transaction when the existing allowance already satisfies the request +- probe non-zero allowance replacement with `eth_call` +- use confirmed `approve(0)` only for USDT-style/no-simulation fallback +- follow matching wallet fee-bump replacements +- validate bigint range and option types before submitting a transaction + +`setApproval()` returns `{ tx?, resetTx? }`. +`PaymentHelper.approveToken()` returns only the final `tx` (or `undefined`). + +## Exports + +```ts +import { + PaymentHelper, + MPPClient, + MPPError, + PaymentError, + ERC20Token, + parseUnits, + formatUnits, + signTypedData, + hashCalldata, + verifySignature, +} from 'goatflow-sdk' +``` + +See [the package integration guide](./docs/Integration.md), +[the repository integration guide](../docs/goat-flow-integration.md), and the +[Changelog](./CHANGELOG.md). ## License diff --git a/goatx402-sdk/docs/Integration.md b/goatx402-sdk/docs/Integration.md index 31d2ef0..e1d41a1 100644 --- a/goatx402-sdk/docs/Integration.md +++ b/goatx402-sdk/docs/Integration.md @@ -1,1312 +1,510 @@ -# GOAT Flow SDK Integration Guide +# goatflow-sdk Integration Guide -## Table of Contents +This guide covers the public contract of the browser-oriented +`goatflow-sdk` package. Merchant API authentication and order creation belong in +`goatflow-sdk-server` or the Go server SDK. -1. [Overview](#1-overview) -2. [Quick Start (10 minutes)](#2-quick-start-10-minutes) -3. [Architecture & System Structure](#3-architecture--system-structure) -4. [Two Payment Modes Explained](#4-two-payment-modes-explained) -5. [Fee Model](#5-fee-model) -6. [Backend Integration (Server SDK)](#6-backend-integration-server-sdk) -7. [Frontend Integration (Client SDK)](#7-frontend-integration-client-sdk) -8. [Security & Authentication Model](#8-security--authentication-model) -9. [API Reference](#9-api-reference) -10. [Error Handling & Troubleshooting](#10-error-handling--troubleshooting) -11. [Versioning & Compatibility](#11-versioning--compatibility) -12. [Best Practices](#12-best-practices) -13. [Appendix](#13-appendix) -14. [Gaps & TODOs](#14-gaps--todos) +For the complete GOAT Flow API guide, see +[GOAT Flow Integration](../../docs/goat-flow-integration.md). ---- - -## 1. Overview - -### 1.1 What is GOAT Flow - -GOAT Flow is a comprehensive EVM cryptocurrency payment processing platform. It provides two payment receiving modes to meet different merchant needs. - -### 1.2 SDK Components - -| SDK | Purpose | Package | -|-----|---------|---------| -| **goatflow-sdk** | Frontend client SDK | `npm install goatflow-sdk` | -| **goatflow-sdk-server** | TypeScript backend SDK | `npm install goatflow-sdk-server` | -| **goatflow-sdk-server** (Go) | Go backend SDK (source-only) | [Clone this repo and use a local `replace`](../../goatx402-sdk-server-go/README.md) | -| **goatflow-checkout** | Drop-in hosted browser checkout | `npm install goatflow-checkout` | -| **goatflow-quickpay** | QuickPay public payer / agent library and CLI | `npm install goatflow-quickpay` | - -### 1.3 Two Payment Modes - -| Mode | Identifier | Receiving Method | Fixed Fee | Use Case | -|------|------------|------------------|-----------|----------| -| **Direct Mode** | `DIRECT` | User transfers directly to merchant wallet | Lower (e.g., $0.10/tx) | Simple payments, no callbacks | -| **Delegate Mode** | `DELEGATE` | User transfers to TSS wallet; system settles on the merchant chain | Higher (e.g., $0.20/tx) | Callbacks, complex business logic | - -### 1.4 Supported Blockchain Networks - -| Network | Chain ID | DIRECT | DELEGATE | Explorer | -|---------|---------:|:------:|:--------:|----------| -| Ethereum | 1 | Yes | Yes | etherscan.io | -| Polygon | 137 | Yes | Yes | polygonscan.com | -| BSC | 56 | Yes | Yes | bscscan.com | -| Arbitrum | 42161 | Yes | Yes | arbiscan.io | -| Optimism | 10 | Yes | Yes | optimistic.etherscan.io | -| Avalanche | 43114 | Yes | Yes | snowtrace.io | -| Base | 8453 | Yes | Yes | basescan.org | -| Berachain | 80094 | Yes | Yes | berascan.com | -| X Layer | 196 | Yes | Yes | web3.okx.com/explorer/x-layer/evm | -| GOAT | 2345 | Yes | Yes | explorer.goat.network | -| Metis | 1088 | Yes | No | andromeda-explorer.metis.io | -| Tempo | 4217 | Yes | No | explore.tempo.xyz | - - - ---- - -## 2. Quick Start (10 minutes) - -### 2.1 Prerequisites - -1. **Merchant Account**: Contact GOAT Flow to obtain -2. **API Credentials**: `API_KEY` and `API_SECRET` -3. **Fee Balance**: Ensure sufficient USD fee balance - -### 2.2 Installation +## 1. Install and runtime boundary ```bash -# Backend SDK -npm install goatflow-sdk-server - -# Frontend SDK npm install goatflow-sdk ethers - -# QuickPay public payer / agent CLI -npm install goatflow-quickpay - -# Drop-in hosted browser checkout -npm install goatflow-checkout ``` -### 2.3 Backend: Create Order +The package manifest declares: -```typescript -import { GoatFlowClient } from 'goatflow-sdk-server' -import type { Order as ServerOrder } from 'goatflow-sdk-server' -import type { Order as ClientOrder } from 'goatflow-sdk' +- package version `0.2.1` +- Node.js >= 18 for non-browser use +- ethers `^6.9.0` -// Initialize client -const client = new GoatFlowClient({ - baseUrl: 'https://flow-api.goat.network', - apiKey: process.env.GOATX402_API_KEY, - apiSecret: process.env.GOATX402_API_SECRET, -}) - -function toClientOrder(order: ServerOrder, fromAddress: string): ClientOrder { - // Server SDK uses fromChainId/payToChainId; browser SDK expects chainId. - return { ...order, fromAddress, chainId: order.fromChainId } -} - -// Create payment order and return the browser-SDK shape to your frontend -async function createOrder(userAddress: string, amount: string) { - const order = await client.createOrder({ - dappOrderId: `order_${Date.now()}`, // Merchant order ID - chainId: 137, // Polygon - tokenSymbol: 'USDC', - tokenContract: '0x3c499c542cef5e3811e1192ce70d8cc03d5c3359', - fromAddress: userAddress, // User wallet address - amountWei: '10000000', // 10 USDC (6 decimals) - // callbackCalldata: '0x...', // Optional: callback data (delegate mode only) - }) - - return toClientOrder(order, userAddress) -} -``` - -### 2.4 Frontend: Execute Payment +Browser use requires an ethers-compatible signer, normally from an EIP-1193 +wallet provider. -```typescript -import { PaymentHelper, type Order as ClientOrder } from 'goatflow-sdk' -import { ethers } from 'ethers' +## 2. Exports -async function executePayment(order: ClientOrder) { - // Connect wallet - const provider = new ethers.BrowserProvider(window.ethereum) - const signer = await provider.getSigner() - const payment = new PaymentHelper(signer) - - // If callback sign request exists (delegate mode), sign first - if (order.calldataSignRequest) { - const signature = await payment.signCalldata(order) - await submitSignatureToBackend(order.orderId, signature) - } - - // Execute payment - const result = await payment.pay(order) - - if (result.success) { - console.log('Payment successful:', result.txHash) - } -} -``` - -### 2.5 Verify Integration - -```typescript -// Query order status -const status = await client.getOrderStatus(orderId) - -// Order status flow -// CHECKOUT_VERIFIED → PAYMENT_CONFIRMED → INVOICED -``` - ---- - -## 3. Architecture & System Structure - -### 3.1 System Architecture - -``` -┌─────────────────────────────────────────────────────────────────┐ -│ Merchant System │ -│ ┌──────────────────┐ ┌──────────────────┐ │ -│ │ Merchant Backend │ │ Merchant Frontend │ │ -│ │ (Server SDK) │ │ (Client SDK) │ │ -│ └────────┬─────────┘ └────────┬─────────┘ │ -└───────────┼────────────────────────────┼────────────────────────┘ - │ │ - │ HMAC Signature Auth │ Wallet Interaction - ▼ ▼ -┌───────────────────────────────────────────────────────────────────┐ -│ GOAT Flow Platform │ -│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ -│ │ API Gateway │ │ Payment Eng │ │ TSS Gateway │ │ -│ └─────────────┘ └─────────────┘ └─────────────┘ │ -│ │ -│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ -│ │ EVM Watcher │ │ Orchestrator│ │ Fee System │ │ -│ └─────────────┘ └─────────────┘ └─────────────┘ │ -└───────────────────────────────────────────────────────────────────┘ - │ │ - ▼ ▼ -┌───────────────────────────────────────────────────────────────────┐ -│ Blockchain Networks │ -│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ -│ │ Ethereum │ │ Polygon │ │ BSC │ │ GOAT │ │ -│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │ -└───────────────────────────────────────────────────────────────────┘ -``` - -### 3.2 Data Flow Overview - -```mermaid -sequenceDiagram - participant User as User - participant MFE as Merchant Frontend - participant MBE as Merchant Backend - participant API as GOAT Flow API - participant Chain as Blockchain - - MBE->>API: 1. Create Order (Server SDK) - API-->>MBE: 2. Return Order (with payToAddress) - MBE-->>MFE: 3. Pass order info - MFE->>User: 4. Display payment UI - User->>MFE: 5. Confirm payment - MFE->>Chain: 6. Send token transfer (Client SDK) - Chain-->>API: 7. Transfer event detected - API->>API: 8. Process payment confirmation - API-->>MBE: 9. Webhook notification (optional) -``` - ---- - -## 4. Two Payment Modes Explained - -### 4.1 Direct Mode (DIRECT) - -**Overview**: User directly transfers tokens to merchant's wallet address, without GOAT Flow intermediation. - -**Features**: -- Simplest payment method -- Lowest fixed fee -- No TSS wallet involvement -- No callback support - -**Payment Flow**: - -```mermaid -sequenceDiagram - participant User as User Wallet - participant SDK as Client SDK - participant Token as Token Contract - participant Merchant as Merchant Wallet - participant API as GOAT Flow API - - SDK->>Token: 1. Check balance - Token-->>SDK: Balance sufficient - SDK->>User: 2. Request signature - User->>Token: 3. transfer(merchant, amount) - Token-->>Merchant: 4. Tokens arrive directly - Token-->>SDK: 5. Transaction receipt - API->>API: 6. Watcher detects transfer - API->>API: 7. Update order status -``` - -**Flow Types**: `ERC20_DIRECT` - -**Code Example**: - -```typescript -// Backend creates order -const order = await client.createOrder({ - dappOrderId: 'order_001', - chainId: 137, - tokenSymbol: 'USDC', - tokenContract: '0x3c499c542cef5e3811e1192ce70d8cc03d5c3359', - fromAddress: userWallet, - amountWei: '10000000', - // No callbackCalldata - Direct mode -}) - -// order.flow = 'ERC20_DIRECT' -// order.payToAddress = Merchant wallet address -``` - ---- - -### 4.2 Delegate Mode (DELEGATE) - -**Overview**: The user transfers tokens to a TSS wallet on the selected source -chain. GOAT Flow then pays out and optionally executes the callback on the -merchant's configured callback/settlement chain, which may be the same chain. - -**Features**: -- Supports callback functionality (execute merchant contracts) -- Higher fixed fee (includes TSS payout gas cost) -- TSS multi-sig wallet ensures fund security -- Supports complex business logic -- Requires Permit2 / EIP-3009 support and an approved callback contract on the merchant chain -- Supports eligible cross-chain source payments while the merchant callback chain remains fixed - -**Payment Flow**: - -```mermaid -sequenceDiagram - participant User as User Wallet - participant SDK as Client SDK - participant Token as Token Contract - participant TSS as TSS Wallet - participant API as GOAT Flow API - participant Merchant as Merchant Wallet - - SDK->>Token: 1. Check balance - Token-->>SDK: Balance sufficient - - Note over SDK,User: If callback sign request exists - SDK->>User: 2. Request EIP-712 signature - User-->>SDK: 3. Return signature - SDK->>API: 4. Submit signature - - SDK->>User: 5. Request transfer signature - User->>Token: 6. transfer(TSS, amount) - Token-->>TSS: 7. Tokens arrive at TSS - Token-->>SDK: 8. Transaction receipt - - API->>API: 9. Watcher detects transfer - API->>API: 10. Create Payout task - API->>TSS: 11. Request TSS signature - TSS-->>API: 12. Return signature - API->>Token: 13. Execute Payout (with callback) - Token-->>Merchant: 14. Tokens arrive -``` - -**Flow Types**: `ERC20_3009`, `ERC20_APPROVE_XFER` - -**Code Example**: - -```typescript -// Backend creates order (with callback) -const order = await client.createOrder({ - dappOrderId: 'order_002', - chainId: 137, - tokenSymbol: 'USDC', - tokenContract: '0x3c499c542cef5e3811e1192ce70d8cc03d5c3359', - fromAddress: userWallet, - amountWei: '10000000', - callbackCalldata: '0x...', // Callback data (optional) -}) - -// order.flow = 'ERC20_3009' or 'ERC20_APPROVE_XFER' -// order.payToAddress = TSS wallet address -// order.calldataSignRequest = EIP-712 sign request (if callback exists) +```ts +import { + PaymentHelper, + MPPClient, + MPPError, + PaymentError, + ERC20Token, + parseUnits, + formatUnits, + signTypedData, + hashCalldata, + verifySignature, +} from 'goatflow-sdk' ``` - - ---- - -### 4.3 Mode Comparison +The package also exports order, EIP-712, approval, and MPP types. -| Feature | Direct Mode (DIRECT) | Delegate Mode (DELEGATE) | -|---------|----------------------|--------------------------| -| **Receiving Address** | Merchant wallet | TSS wallet | -| **Fixed Fee** | Lower (e.g., $0.10) | Higher (e.g., $0.20) | -| **Funds Arrival** | Immediate after user transfer | After TSS Payout | -| **Callback Support** | ❌ Not supported | ✅ Supported | -| **Use Case** | Simple payments | Complex business logic | -| **Gas Cost** | User only | User + TSS both need Gas | -| **Chain relation** | Payment and receiving chain are the same | Source chain may differ from the single merchant callback chain | +## 3. Order boundary ---- +The server and browser SDK `Order` interfaces are not interchangeable. -## 5. Fee Model +Server SDK: -### 5.1 Fee Structure - -GOAT Flow uses a **fixed fee** model (not percentage), charged per order. - -| Fee Type | Direct Mode | Delegate Mode | Description | -|----------|-------------|---------------|-------------| -| Order Fee | `fee_usd_direct` | `fee_usd_delegate` | Fixed USD fee configured per chain | -| Default Fee | $0.10/tx | $0.20/tx | Customizable per chain | - -**Why is Delegate Mode fee higher?** -- TSS needs to execute Payout transaction -- Additional on-chain gas cost -- Multi-signature security mechanism cost - -### 5.2 Fee Balance System - -Merchants need to **pre-fund a USD fee balance**, deducted when orders are created. - -``` -┌─────────────────────────────────────────────────────┐ -│ Fee Balance Flow │ -├─────────────────────────────────────────────────────┤ -│ │ -│ 1. Operator topup → merchant_fee_balance += $100 │ -│ │ -│ 2. Create order → Check balance │ -│ ├─ Insufficient → Return HTTP 400 error │ -│ └─ Sufficient → Charge fee, create order │ -│ │ -│ 3. Payment successful → Fee consumed (no refund) │ -│ │ -│ 4. Order expired → Fee refunded → balance += fee │ -│ │ -└─────────────────────────────────────────────────────┘ -``` - -### 5.3 Fee Calculation Examples - -```typescript -// Direct mode order -const directOrder = { - chainId: 137, // Polygon - mode: 'DIRECT', - fee: '$0.10', // Fixed fee -} - -// Delegate mode order -const delegateOrder = { - chainId: 137, // Polygon - mode: 'DELEGATE', - fee: '$0.20', // Fixed fee (includes Payout Gas) +```ts +interface ServerOrder { + orderId: string + flow: PaymentFlow + tokenSymbol: string + tokenContract: string + payToAddress: string + fromChainId: number + payToChainId: number + amountWei: string + expiresAt: number + calldataSignRequest?: CalldataSignRequest + x402?: X402PaymentRequired } - -// Whether order amount is $10 or $10,000, fee is fixed ``` -### 5.4 Insufficient Balance Handling +Browser SDK: -```typescript -try { - const order = await client.createOrder(orderParams) -} catch (error) { - if (error.status === 400 && error.message?.includes('insufficient fee balance')) { - // Fee balance insufficient - console.error('Insufficient fee balance, please contact operator to topup') - // error.message: "Insufficient fee balance" - } +```ts +interface ClientOrder { + orderId: string + flow: PaymentFlow + tokenSymbol: string + tokenContract: string + fromAddress: string + payToAddress: string + chainId: number + amountWei: string + expiresAt: number + calldataSignRequest?: CalldataSignRequest } ``` ---- - -## 6. Backend Integration (Server SDK) - -### 6.1 Initialization - -```typescript -import { GoatFlowClient } from 'goatflow-sdk-server' - -const client = new GoatFlowClient({ - baseUrl: process.env.GOATX402_API_URL, // API URL - apiKey: process.env.GOATX402_API_KEY, // API Key - apiSecret: process.env.GOATX402_API_SECRET, // API Secret -}) -``` - -### 6.2 Create Order - -```typescript -interface CreateOrderParams { - dappOrderId: string // Merchant order ID (unique) - chainId: number // Chain ID - tokenSymbol: string // Token symbol - tokenContract: string // Token contract address - fromAddress: string // Payer address - amountWei: string // Amount (smallest unit) - callbackCalldata?: string // Callback data (delegate mode optional) -} +Map it on the backend: -const order = await client.createOrder({ - dappOrderId: `order_${Date.now()}`, - chainId: 137, - tokenSymbol: 'USDC', - tokenContract: '0x3c499c542cef5e3811e1192ce70d8cc03d5c3359', - fromAddress: '0x742d35Cc6634C0532925a3b844Bc...', - amountWei: '10000000', // 10 USDC -}) -``` +```ts +import type { Order as ServerOrder } from 'goatflow-sdk-server' +import type { Order as ClientOrder } from 'goatflow-sdk' -### 6.3 Order Response - -```typescript -interface OrderResponse { - orderId: string // GOAT Flow order ID - flow: PaymentFlow // Payment flow type - payToAddress: string // Receiving address - expiresAt: number // Expiration time (Unix timestamp) - calldataSignRequest?: { // EIP-712 sign request (if callback exists) - domain: EIP712Domain - types: Record - primaryType: string - message: SignMessage +function toClientOrder( + order: ServerOrder, + fromAddress: string, +): ClientOrder { + return { + orderId: order.orderId, + flow: order.flow, + tokenSymbol: order.tokenSymbol, + tokenContract: order.tokenContract, + fromAddress, + payToAddress: order.payToAddress, + chainId: order.fromChainId, + amountWei: order.amountWei, + expiresAt: order.expiresAt, + calldataSignRequest: order.calldataSignRequest, } } ``` -The raw create-order response is an x402 challenge. `HTTP 402` is the expected success path for order creation, and the `PAYMENT-REQUIRED` header contains the base64-encoded JSON challenge body. - -```http -HTTP/1.1 402 Payment Required -PAYMENT-REQUIRED: eyJ4NDAyVmVyc2lvbiI6Miwi... -Content-Type: application/json -``` - -```json -{ - "x402Version": 2, - "resource": { - "url": "https://flow-api.goat.network/api/v1/orders/{order_id}", - "description": "Payment: 10000000 USDC", - "mimeType": "application/json" - }, - "accepts": [ - { - "scheme": "exact", - "network": "eip155:137", - "amount": "10000000", - "asset": "0x3c499c542cef5e3811e1192ce70d8cc03d5c3359", - "payTo": "0xMerchantOrTssAddress", - "maxTimeoutSeconds": 585, - "extra": { - "flow": "ERC20_DIRECT", - "tokenSymbol": "USDC" - } - } - ], - "extensions": { - "goatx402": { - "destinationChain": "eip155:137", - "expiresAt": 1760000600, - "paymentMethod": "transfer", - "receiveType": "DIRECT" - } - }, - "order_id": "{order_id}", - "flow": "ERC20_DIRECT", - "token_symbol": "USDC" -} -``` - -`maxTimeoutSeconds` is the order's remaining lifetime when the challenge is generated (clamped to at least 1 second), not a fixed 600-second window. - -For `ERC20_3009`, `accepts[0].scheme` is `exact-eip3009`. Whenever the order carries a `calldata_sign_request`—including Permit2-style DELEGATE callbacks—`extensions.goatx402.signatureEndpoint` points to `POST /api/v1/orders/{order_id}/calldata-signature`. - - - -### 6.4 Query Order Status - -```typescript -const status = await client.getOrderStatus(orderId) - -// status.status possible values: -// - 'CHECKOUT_VERIFIED' : Awaiting payment -// - 'PAYMENT_CONFIRMED' : Payment confirmed -// - 'INVOICED' : Complete (invoiced) -// - 'EXPIRED' : Expired -// - 'FAILED' : Failed -// - 'CANCELLED' : Cancelled before payment -``` - -### 6.5 Submit Callback Signature +Do not expose merchant API credentials or the raw server object to the browser. -```typescript -// After user signs on frontend, submit to backend -await client.submitCalldataSignature(orderId, '0x...') -``` +## 4. `PaymentHelper` -### 6.6 Get Order Proof +### 4.1 Setup -```typescript -// Get proof after payment completion -const proof = await client.getOrderProof(orderId) - -// proof contains the on-chain tx hash plus an unsigned checksum over a -// subset of the payload fields (not the whole record). -// Verify proof.payload.tx_hash on-chain when independent proof is required. -``` - ---- - -## 7. Frontend Integration (Client SDK) - -### 7.1 Installation & Initialization - -```typescript -import { - PaymentHelper, - ERC20Token, - parseUnits, - formatUnits, - type Order as ClientOrder, -} from 'goatflow-sdk' +```ts +import { PaymentHelper } from 'goatflow-sdk' import { ethers } from 'ethers' -// Connect wallet const provider = new ethers.BrowserProvider(window.ethereum) await provider.send('eth_requestAccounts', []) const signer = await provider.getSigner() - -// Create PaymentHelper const payment = new PaymentHelper(signer) ``` -The server SDK returns `fromChainId` and `payToChainId`. Before passing an order to the browser SDK, map `chainId` from `fromChainId` and include the payer address used at order creation. - -```typescript -import type { Order as ServerOrder } from 'goatflow-sdk-server' - -function toClientOrder(serverOrder: ServerOrder, fromAddress: string): ClientOrder { - return { ...serverOrder, fromAddress, chainId: serverOrder.fromChainId } -} -``` - -### 7.2 Complete Payment Flow +### 4.2 Application validation -```typescript -async function processPayment(order: ClientOrder) { - const provider = new ethers.BrowserProvider(window.ethereum) - const signer = await provider.getSigner() - const payment = new PaymentHelper(signer) +`PaymentHelper.pay()` does not check order chain, payer, or expiry. - // 1. Verify network +```ts +async function validateOrder(order: ClientOrder): Promise { const network = await provider.getNetwork() if (Number(network.chainId) !== order.chainId) { - await provider.send('wallet_switchEthereumChain', [ - { chainId: `0x${order.chainId.toString(16)}` }, - ]) + throw new Error(`Switch wallet to chain ${order.chainId}`) } - // 2. Check balance - const balance = await payment.getTokenBalance(order.tokenContract) - const required = BigInt(order.amountWei) - if (balance < required) { - throw new Error('Insufficient balance') + const payer = await signer.getAddress() + if (payer.toLowerCase() !== order.fromAddress.toLowerCase()) { + throw new Error('Connected wallet does not match the order payer') } - // 3. If callback sign request exists (delegate mode), sign first - if (order.calldataSignRequest) { - const signature = await payment.signCalldata(order) - // Submit signature to backend - await fetch('/api/orders/sign', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ orderId: order.orderId, signature }), - }) + if (Math.floor(Date.now() / 1000) >= order.expiresAt) { + throw new Error('Order expired') } - - // 4. Execute payment - const result = await payment.pay(order) - - if (result.success) { - console.log('Transaction hash:', result.txHash) - // Notify backend payment initiated - await fetch('/api/orders/notify', { - method: 'POST', - body: JSON.stringify({ orderId: order.orderId, txHash: result.txHash }), - }) - } - - return result } ``` -### 7.3 React Hook Example +### 4.3 Pay -```typescript -// hooks/useGoatFlowPayment.ts -import { useState, useCallback } from 'react' -import { PaymentHelper, type Order as ClientOrder, type PaymentResult } from 'goatflow-sdk' -import { ethers } from 'ethers' +```ts +await validateOrder(order) -export function useGoatFlowPayment() { - const [loading, setLoading] = useState(false) - const [error, setError] = useState(null) - - const pay = useCallback(async (order: ClientOrder): Promise => { - setLoading(true) - setError(null) - - try { - const provider = new ethers.BrowserProvider(window.ethereum) - const signer = await provider.getSigner() - const payment = new PaymentHelper(signer) - - // Handle callback signature - if (order.calldataSignRequest) { - const sig = await payment.signCalldata(order) - await submitSignature(order.orderId, sig) - } - - const result = await payment.pay(order) - - if (!result.success) { - setError(result.error || 'Payment failed') - } - - return result - } catch (err: any) { - const msg = err.code === 'ACTION_REJECTED' - ? 'User cancelled operation' - : err.message - setError(msg) - return null - } finally { - setLoading(false) - } - }, []) - - return { pay, loading, error } +const result = await payment.pay(order) +if (!result.success || !result.txHash) { + throw new Error(result.error ?? 'Payment failed') } ``` -### 7.4 Pay Button Component - -```tsx -// components/PayButton.tsx -import { useGoatFlowPayment } from '../hooks/useGoatFlowPayment' -import type { Order as ClientOrder } from 'goatflow-sdk' - -interface PayButtonProps { - order: ClientOrder - onSuccess: (txHash: string) => void - onError: (error: string) => void -} +Actual implementation: -export function PayButton({ order, onSuccess, onError }: PayButtonProps) { - const { pay, loading, error } = useGoatFlowPayment() +1. Builds an `ERC20Token` with the signer. +2. Reads the signer's token balance. +3. Converts `amountWei` with `BigInt`. +4. Submits `transfer(payToAddress, amount)`. +5. Waits for the transaction receipt. +6. Requires receipt status `1`. +7. Returns `PaymentResult`. - const handleClick = async () => { - const result = await pay(order) - if (result?.success && result.txHash) { - onSuccess(result.txHash) - } else if (result?.error) { - onError(result.error) - } - } +Errors are caught and returned: - return ( - - ) +```ts +interface PaymentResult { + success: boolean + txHash?: string + error?: string } ``` ---- +`PaymentError` is exported, but the current `pay()` implementation does not wrap +failures in it. -## 8. Security & Authentication Model +The helper also treats every `tx.wait()` exception as failure and does not +classify `TRANSACTION_REPLACED`. A successful wallet speed-up can therefore be +reported as failed. Reconcile the original/replacement transaction and backend +order before submitting another transfer. -### 8.1 Backend API Authentication (HMAC-SHA256) +### 4.4 Supported flow values -```typescript -// Server SDK handles signature automatically -// Signature algorithm: -// 1. Build signature params from request body/query plus api_key, timestamp, and nonce -// 2. Drop empty values and `sign` -// 3. Sort params by ASCII key -// 4. Concatenate: key1=value1&key2=value2 -// 5. HMAC-SHA256(secret, payload) - -// Request headers: -// X-API-Key: {apiKey} -// X-Timestamp: {unixSeconds} -// X-Nonce: {uniqueRequestNonce} -// X-Sign: {hmacSignature} +```ts +type PaymentFlow = + | 'ERC20_DIRECT' + | 'ERC20_3009' + | 'ERC20_APPROVE_XFER' ``` - +All three browser paths transfer to the returned `payToAddress`. DIRECT is the +standard/default merchant path. The other values belong to an explicitly +operator-provisioned compatibility integration and are not part of public +onboarding. -### 8.2 EIP-712 Signature (Callback Authorization) +## 5. Callback EIP-712 signing -```typescript -// User signature authorizes callback execution. -// Sign the calldataSignRequest returned by createOrder; do not rebuild it. -if (!order.calldataSignRequest) { - throw new Error('Order does not require calldata signature') -} +When `calldataSignRequest` is present: -const { EIP712Domain, ...types } = order.calldataSignRequest.types -const signature = await signer.signTypedData( - order.calldataSignRequest.domain, - types, - order.calldataSignRequest.message -) +```ts +const signature = await payment.signCalldata(order) -// order.calldataSignRequest.domain is returned by GOAT Flow and must match -// the deployed MerchantCallback EIP-712 domain. MerchantCallback.initialize() -// defaults to name "GoatX402 Pay Callback" and version "1". -``` - -Returned callback message shapes: - -```typescript -type Eip3009CallbackData = { - token: string - owner: string - payer: string - amount: string - orderId: string // bytes32 keccak256(orderId) - calldataNonce: string // replay protection nonce - deadline: string - calldataHash: string // keccak256(callback calldata) -} - -type Permit2CallbackData = Eip3009CallbackData & { - permit2: string -} +await fetch(`/api/orders/${order.orderId}/signature`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ signature }), +}) ``` - - -### 8.3 Security Mechanisms +The merchant backend forwards it using +`submitCalldataSignature(orderId, signature)`. -| Mechanism | Description | -|-----------|-------------| -| **Nonce** | Each signature unique, prevents replay attacks | -| **Deadline** | Signature validity period, invalid after expiry | -| **Chain ID** | Bound to chain ID, prevents cross-chain replay | -| **Contract Binding** | Bound to contract address, prevents cross-contract replay | -| **Calldata Hash** | Callback data hash, prevents tampering | +That method sends `{ signature }` to the HMAC-authenticated +`POST /api/v1/orders/{order_id}/calldata-signature` endpoint. The returned +`CalldataSignRequest` supplies: ---- +- `domain`: `name`, `version`, `chainId`, `verifyingContract` +- `types`: the full EIP-712 field definitions +- `primaryType`: `Eip3009CallbackData` or `Permit2CallbackData` +- `message`: `token`, `owner`, `payer`, `amount`, `orderId`, + `calldataNonce`, `deadline`, `calldataHash`, and optional `permit2` -## 9. API Reference +The SDK removes `EIP712Domain` from the types object because ethers v6 handles +the domain separately. Never rebuild the domain/types/message in application +code. -### 9.1 PaymentHelper Class +Utilities: -| Method | Parameters | Returns | Description | -|--------|------------|---------|-------------| -| `constructor(signer)` | `ethers.Signer` | - | Initialize | -| `getAddress()` | - | `Promise` | Get wallet address | -| `pay(order)` | `Order` | `Promise` | Execute payment | -| `signCalldata(order)` | `Order` | `Promise` | Sign callback data | -| `getTokenBalance(token)` | `string` | `Promise` | Query token balance | -| `getTokenAllowance(token, spender)` | `string, string` | `Promise` | Query allowance | -| `approveToken(token, spender, amount, options?)` | `string, string, bigint, ApprovalOptions?` | `Promise` | Exact approval by default; `unlimited: true` opts into unlimited approval; resolves after confirmation, or with `undefined` when the allowance already equals the requested value (including revoking an already-zero allowance); changing a non-zero allowance first simulates the direct write via eth_call — standard ERC20s get a single approval with no reset window, only USDT-style tokens fall back to a confirmed approve(0) reset first | +```ts +const calldataHash = hashCalldata('0x...') +const valid = verifySignature(signRequest, signature, expectedSigner) +``` -### 9.2 ERC20Token Class +## 6. Token helpers -| Method | Parameters | Returns | Description | -|--------|------------|---------|-------------| -| `constructor(address, signerOrProvider)` | `string, Signer|Provider` | - | Initialize | -| `balanceOf(address)` | `string` | `Promise` | Query balance | -| `allowance(owner, spender)` | `string, string` | `Promise` | Query allowance | -| `decimals()` | - | `Promise` | Get decimals | -| `symbol()` | - | `Promise` | Get symbol | -| `approve(spender, amount)` | `string, bigint` | `Promise` | Approve | -| `transfer(to, amount)` | `string, bigint` | `Promise` | Transfer | -| `ensureApproval(owner, spender, amount, options?)` | `string, string, bigint, ApprovalOptions?` | `Promise<{needed, tx?, resetTx?}>` | Judge sufficiency against the requested amount; otherwise simulate the direct write first and only use a confirmed approve(0) reset for USDT-style tokens; `unlimited` only changes the value written | -| `setApproval(owner, spender, amount, options?)` | `string, string, bigint, ApprovalOptions?` | `Promise` | Set an allowance explicitly; no transaction when it already equals the target; simulate non-zero direct writes first, fall back to approve(0) only when needed, and safely follow wallet fee-bumps | +```ts +const balance = await payment.getTokenBalance(tokenAddress) +const allowance = await payment.getTokenAllowance(tokenAddress, spender) +const tx = await payment.transferToken(tokenAddress, recipient, amount) +``` -### 9.3 Utility Functions +`transferToken()` returns after broadcast; unlike `pay()`, it does not wait for +the receipt. -```typescript -// Amount format conversion -import { parseUnits, formatUnits } from 'goatflow-sdk' +### 6.1 Amount conversion -// Human readable → Wei +```ts const amountWei = parseUnits('100.5', 6) // 100500000n - -// Wei → Human readable const amount = formatUnits(100500000n, 6) // "100.5" ``` -### 9.4 QuickPay Public API and CLI - -QuickPay exposes public, credential-less endpoints for browser payers, CLIs, and agents. - -| Surface | Endpoint | Purpose | -|---------|----------|---------| -| Discovery | `GET /quickpay/v1/merchants/{merchant_id}` | Public merchant payment capability | -| Agent instructions | `GET /quickpay/{merchant_id}/agent.md` | Prompt-injection-hardened agent guide | -| Machine manifest | `GET /quickpay/{merchant_id}/manifest.json` | `goatx402.quickpay.v1` manifest | -| Create x402 session | `POST /quickpay/v1/x402/sessions` | Create or reuse a payable QuickPay session | -| Query x402 session | `GET /quickpay/v1/x402/sessions/{session_id}` | Poll public session status | +### 6.2 Approval behavior -Session creation request: - -```json -{ - "merchant_id": "merchant_123", - "payer_addr": "0xUserWalletAddress", - "chain_id": 137, - "token_contract": "0x3c499c542cef5e3811e1192ce70d8cc03d5c3359", - "amount_wei": "10000000", - "memo": "invoice-123", - "idempotency_key": "invoice-123:user-456" -} +```ts +const finalTx = await payment.approveToken( + tokenAddress, + spender, + 1000000n, +) ``` -When the session is payable, the response includes an embedded `x402` object with the same `x402Version: 2`, `accepts[0].network = eip155:`, `scheme = exact`, `amount`, `asset`, and `payTo` fields described in Section 6.3. - -For a fixed-price product, send `product_key` instead of `amount_wei`. The -server computes the atomic amount from the product's token-agnostic decimal price -and the selected token decimals. +Exact approval is the default. Unlimited approval is explicit: -QuickPay package and CLI: - -```bash -npx goatflow-quickpay inspect https://flow-api.goat.network/quickpay/merchant_123/agent.md -npx goatflow-quickpay pay-x402 https://flow-api.goat.network/quickpay/merchant_123/agent.md \ - --amount 10 --token-contract 0xToken --chain 137 --idempotency-key invoice-123 -npx goatflow-quickpay pay-product https://flow-api.goat.network/quickpay/merchant_123/agent.md \ - --product mug --token-contract 0xToken --chain 137 -npx goatflow-quickpay pay-mpp https://flow-api.goat.network/quickpay/merchant_123/agent.md \ - --route +```ts +await payment.approveToken( + tokenAddress, + spender, + 1000000n, + { unlimited: true }, +) ``` -The library exports `QuickPayClient`, `inspect`, `payX402`, `payProduct`, -`payMpp`, `loadManifest`, and `EthersPaymentBackend`. +The implementation: - +- validates amount is a non-negative `bigint` <= `MaxUint256` +- validates options before any transaction +- skips a write when the allowance already equals the target +- lets `ensureApproval()` keep any allowance already sufficient for the amount +- probes direct non-zero replacement with `eth_call` +- uses a confirmed zero reset only when the direct write cannot be proven +- checks zero allowance after reset/revoke +- follows matching fee-bump replacements +- rejects cancellation or unrelated same-nonce replacement -### 9.5 Hosted Checkout +`PaymentHelper.approveToken()` returns only the final transaction or +`undefined`. Use `ERC20Token.setApproval()` for `{ tx?, resetTx? }`. -For a platform-hosted payment page, use `goatflow-checkout`: +## 7. MPP -```typescript -import { GoatCheckout } from 'goatflow-checkout' +[Machine Payments Protocol (MPP)](https://mpp.dev/overview) is an independent +open protocol. This section documents GOAT Flow's current adapter, not the +standard MPP HTTP Challenge/Credential/Receipt wire exchange. Its JSON +challenge/verify endpoints and signed three-segment receipt are GOAT-specific; +the repository contains no official-SDK interoperability test. -const goat = GoatCheckout({ origin: 'https://pay.goat.network' }) -goat.open({ merchant: 'merchant_123', productKey: 'mug' }) -``` +### 7.1 Setup -Dynamic DIRECT and every DELEGATE checkout start on the backend: +```ts +import { MPPClient } from 'goatflow-sdk' -```typescript -const session = await client.createCheckoutSession({ - checkoutType: 'DIRECT', - price: '19.95', - clientReferenceId: 'cart_123', +const mpp = new MPPClient({ + coreUrl: 'https://flow-api.goat.network', + signer, }) - -// Browser: -goat.open({ checkoutId: session.checkoutId }) -``` - -DELEGATE supports cross-chain decimal `price` mode and the compatibility -single-chain `fixedAmountWei` mode. Fulfill from -`quickpay.checkout.completed`, never from the browser callback alone. See -[Hosted Checkout](../../docs/x402-checkout.md). - -### 9.6 Type Definitions - -```typescript -// Payment flow types -type PaymentFlow = - | 'ERC20_DIRECT' // Direct mode - | 'ERC20_3009' // Delegate mode (EIP-3009) - | 'ERC20_APPROVE_XFER' // Delegate mode (Permit2) - -// Order status -type OrderStatus = - | 'CHECKOUT_VERIFIED' // Awaiting payment - | 'PAYMENT_CONFIRMED' // Payment confirmed - | 'INVOICED' // Complete - | 'EXPIRED' // Expired - | 'FAILED' // Failed - | 'CANCELLED' // Cancelled before payment - -// Server SDK order interface -interface ServerOrder { - orderId: string - flow: PaymentFlow - tokenSymbol: string - tokenContract: string - payToAddress: string - fromChainId: number - payToChainId: number - amountWei: string - expiresAt: number - calldataSignRequest?: CalldataSignRequest -} - -// Frontend SDK order interface -interface Order extends Omit { - fromAddress: string - chainId: number -} - -function toClientOrder(order: ServerOrder, fromAddress: string): Order { - return { ...order, fromAddress, chainId: order.fromChainId } -} - -// Payment result -interface PaymentResult { - success: boolean - txHash?: string - error?: string -} ``` ---- - -## 10. Error Handling & Troubleshooting - -### 10.1 Common Error Codes +`coreUrl` must be non-empty and must not end with `/`. +For the standalone GOAT Flow MPP adapter it is the configured Core/API origin. QuickPay's +`pay-mpp` adapter instead derives `coreUrl` from the trusted QuickPay link +origin so discovery, challenge, and verify stay same-origin. -| Error Code | Description | Solution | -|------------|-------------|----------| -| `400` | Request parameter or business rule error, including insufficient fee balance or duplicate `dappOrderId` | Check parameter format, fee balance, and uniqueness | -| `401` | Authentication failed | Check API Key/Secret and signature | -| `402` | Payment Required x402 challenge from successful order creation | Treat as expected order creation response | -| `404` | Order not found | Check order ID | -| `503` | QuickPay session creation temporarily unavailable | Retry after the merchant restores fee/config availability | +### 7.2 High-level payment -### 10.2 Frontend Common Issues - -#### Issue 1: Fee Balance Insufficient (HTTP 400) +```ts +const result = await mpp.pay({ + merchantId: 'merchant_123', + routeCanonical: 'GET:api:data', + requestCanonical: 'GET:api:data:user-42', + onPhase(phase, detail) { + console.log(phase, detail) + }, +}) -```typescript -try { - const order = await client.createOrder(params) -} catch (error) { - if (error.status === 400 && error.message?.includes('insufficient fee balance')) { - // Display prompt - alert('Merchant fee balance insufficient, please contact admin to topup') - } -} +await fetch('/api/data', { + headers: { 'Payment-Receipt': result.receiptHeader }, +}) ``` -#### Issue 2: User Rejected Transaction +Phases: -```typescript -try { - const result = await payment.pay(order) -} catch (error) { - if (error.code === 'ACTION_REJECTED') { - console.log('User cancelled transaction') - } -} +```ts +type MPPPhase = + | 'requesting_challenge' + | 'challenge_received' + | 'sending_transaction' + | 'transaction_sent' + | 'transaction_replaced' + | 'verifying' + | 'verify_pending' + | 'verified' + | 'failed' ``` -#### Issue 3: Token Balance Insufficient +### 7.3 Low-level methods -```typescript -const balance = await payment.getTokenBalance(order.tokenContract) -const required = BigInt(order.amountWei) +- `requestChallenge()` +- `payChallenge()` +- `verifyChallenge()` +- `pay()` -if (balance < required) { - const token = new ERC20Token(order.tokenContract, provider) - const symbol = await token.symbol() - const decimals = await token.decimals() +`requestChallenge()` posts: - alert(`Insufficient balance: need ${formatUnits(required, decimals)} ${symbol}`) +```json +{ + "merchant_id": "merchant_123", + "route_canonical": "GET:api:data", + "request_canonical": "GET:api:data:user-42", + "payer_addr": "0xPayer" } ``` -#### Issue 4: Network Mismatch - -```typescript -const network = await provider.getNetwork() -if (Number(network.chainId) !== order.chainId) { - try { - await provider.send('wallet_switchEthereumChain', [ - { chainId: `0x${order.chainId.toString(16)}` }, - ]) - } catch (error) { - alert('Please switch to correct network') - } -} -``` +HTTP `402` is success. -#### Issue 5: Order Expired +The returned challenge is authoritative for amount, chain, token contract, +recipient, expiry, MAC, and pricing version. Do not reconstruct those fields +from manifest discovery data. -```typescript -if (Date.now() / 1000 > order.expiresAt) { - alert('Order expired, please create a new one') - // Create new order -} -``` - -### 10.3 Debug Checklist +`payChallenge()`: -```typescript -async function debugPayment(order: Order) { - console.log('=== Payment Debug ===') +- rejects an expired challenge +- rejects a signer on the wrong chain +- broadcasts the ERC-20 transfer +- returns `{ txHash, tx }` without waiting for mining - // 1. Wallet connection - const address = await payment.getAddress() - console.log('Wallet address:', address) +`verifyChallenge()` posts: - // 2. Network check - const network = await provider.getNetwork() - console.log('Current network:', network.chainId) - console.log('Order network:', order.chainId) - console.log('Network match:', Number(network.chainId) === order.chainId) - - // 3. Order validity - const now = Date.now() / 1000 - console.log('Current time:', now) - console.log('Expiration:', order.expiresAt) - console.log('Order valid:', now < order.expiresAt) - - // 4. Token balance - const balance = await payment.getTokenBalance(order.tokenContract) - console.log('Token balance:', balance.toString()) - console.log('Required amount:', order.amountWei) - console.log('Sufficient:', balance >= BigInt(order.amountWei)) - - // 5. Payment mode - console.log('Payment mode:', order.flow) - console.log('Pay to address:', order.payToAddress) - console.log('Signature required:', !!order.calldataSignRequest) - - console.log('=== Debug Complete ===') +```json +{ + "challenge_id": "ch_...", + "tx_hash": "0x...", + "payer_addr": "0xPayer", + "mac": "..." } ``` ---- - -## 11. Versioning & Compatibility - -### 11.1 SDK Versions - -Use package manifests as the version source of truth: - -| Package | Source | -|---------|--------| -| `goatflow-sdk` | `goatx402-sdk/package.json` | -| `goatflow-sdk-server` | `goatx402-sdk-server-ts/package.json` | -| `goatflow-checkout` | `goatx402-checkout/package.json` | -| `goatflow-quickpay` | `goatx402-quickpay/package.json` | +Verify response handling: -### 11.2 Dependency Versions +| Status | Behavior | +| --- | --- | +| `200` | Requires the GOAT Flow profile's `Payment-Receipt` extension; decodes its first base64url segment as receipt JSON | +| `202` | Retry with `Retry-After` | +| `429` | Retry with `Retry-After` | +| other `4xx` | Throw terminal `MPPError` | +| `5xx` | Retry with bounded backoff | +| fetch rejection | Retry with bounded backoff | -| Dependency | Version Requirement | -|------------|---------------------| -| ethers | ^6.9.0 | -| typescript | ^5.3.0 | -| node | >=18 | +Defaults: -### 11.3 Browser Compatibility +- 16 verify attempts +- 2-second fallback delay when `Retry-After` is missing +- 30-second maximum delay per attempt -| Browser | Minimum Version | -|---------|-----------------| -| Chrome | 80+ | -| Firefox | 75+ | -| Safari | 14+ | -| Edge | 80+ | +The header format is: +`base64url(JSON(receipt)).base64url(raw-signature).algorithm`, where +`algorithm` is `ed25519` or `hmac-sha256`. It is not a JWT. ---- +### 7.4 Recovery -## 12. Best Practices +After `payChallenge()` broadcasts, `pay()` attaches recovery context to failures: -### 12.1 Backend Best Practices +```ts +import { MPPError } from 'goatflow-sdk' -```typescript -// ✅ Use environment variables -const client = new GoatFlowClient({ - baseUrl: process.env.GOATX402_API_URL, - apiKey: process.env.GOATX402_API_KEY, - apiSecret: process.env.GOATX402_API_SECRET, -}) - -// ✅ Validate order amount -function validateAmount(amount: string, minUsd: number, maxUsd: number) { - const value = parseFloat(amount) - if (value < minUsd || value > maxUsd) { - throw new Error(`Amount must be between $${minUsd} - $${maxUsd}`) - } -} - -// ✅ Handle Webhook notifications -app.post('/webhook/goatx402', async (req, res) => { - const { orderId, status, txHash } = req.body - - // Verify signature - if (!verifyWebhookSignature(req)) { - return res.status(401).send('Invalid signature') - } - - // Update order status - await updateOrderStatus(orderId, status) - - res.status(200).send('OK') -}) -``` - -### 12.2 Frontend Best Practices - -```typescript -// ✅ Cache Provider and Signer -let cachedPayment: PaymentHelper | null = null - -async function getPaymentHelper() { - if (!cachedPayment) { - const provider = new ethers.BrowserProvider(window.ethereum) - const signer = await provider.getSigner() - cachedPayment = new PaymentHelper(signer) +try { + await mpp.pay(params) +} catch (error) { + if (error instanceof MPPError && error.recoverable) { + const result = await mpp.verifyChallenge(error.recoverable) + console.log(result.receiptHeader) + return } - return cachedPayment -} - -// ✅ Display user-friendly errors -function getErrorMessage(error: any): string { - if (error.code === 'ACTION_REJECTED') return 'You cancelled the transaction' - if (error.message?.includes('insufficient')) return 'Insufficient balance' - if (error.status === 400 && error.message?.includes('insufficient fee balance')) return 'Merchant fee balance insufficient' - return 'Payment failed, please try again' -} - -// ✅ Transaction status tracking -function PaymentStatus({ txHash, chainId }: { txHash: string, chainId: number }) { - const explorerUrl = getExplorerUrl(chainId, txHash) - return ( - - View transaction details - - ) + throw error } ``` -### 12.3 Security Best Practices +Do not call `pay()` again when `recoverable` exists; that could prompt a second +transfer. -```typescript -// ✅ Get order from backend, don't construct on frontend -const order = await fetch('/api/orders', { - method: 'POST', - body: JSON.stringify({ productId }), -}).then(r => r.json()) +The replacement watcher follows only a transaction with matching destination +and calldata. It ignores user cancellation and unrelated same-nonce +transactions. -// ✅ Validate order expiration -if (Date.now() / 1000 > order.expiresAt) { - throw new Error('Order expired') -} - -// ✅ Validate payer address -if (order.fromAddress.toLowerCase() !== userAddress.toLowerCase()) { - throw new Error('Order address mismatch') -} +### 7.5 Error codes -// ❌ Don't hardcode amounts on frontend -const order = { amountWei: '1000000' } // Dangerous! +Stable codes include: -// ❌ Don't store sensitive info -localStorage.setItem('apiSecret', secret) // Dangerous! -``` +- `network_error` +- `parse_error` +- `invalid_request` +- `route_not_found` +- `chain_mismatch` +- `user_rejected` +- `payment_failed` +- `challenge_expired` +- `challenge_already_consumed` +- `challenge_tx_hash_mismatch` +- `payer_mismatch` +- `bad_request` +- `verify_timeout` +- `service_unavailable` +- `receipt_missing` +- `receipt_malformed` ---- +Branch on `MPPError.code`, not its message. -## 13. Appendix +For this browser adapter, Core must allow the DApp origin, `POST`, and `Content-Type` and +expose `Payment-Receipt` through CORS. The protected resource must separately +allow the DApp origin and the `Payment-Receipt` request header. Otherwise use a +server-side buyer client. -### 13.1 Example Project Structure +## 8. Order status -``` -my-payment-app/ -├── backend/ -│ ├── src/ -│ │ ├── routes/ -│ │ │ └── orders.ts # Order API -│ │ ├── services/ -│ │ │ └── goatx402.ts # GOAT Flow service -│ │ └── index.ts -│ └── package.json -├── frontend/ -│ ├── src/ -│ │ ├── hooks/ -│ │ │ └── usePayment.ts # Payment Hook -│ │ ├── components/ -│ │ │ └── PayButton.tsx # Pay Button -│ │ └── App.tsx -│ └── package.json -└── README.md -``` +The package exports: -### 13.2 Token Contract Addresses - -| Token | Chain | Contract Address | -|-------|-------|------------------| -| USDC | Ethereum | `0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48` | -| USDC | Polygon | `0x3c499c542cef5e3811e1192ce70d8cc03d5c3359` | -| USDT | Ethereum | `0xdAC17F958D2ee523a2206206994597C13D831ec7` | -| USDT | Polygon | `0xc2132D05D31c914a87C6611C10748AEb04B58e8F` | - -### 13.3 Chain RPC Configuration - -```typescript -const CHAIN_RPCS: Record = { - 1: process.env.RPC_ETHEREUM!, - 137: process.env.RPC_POLYGON!, - 56: process.env.RPC_BSC!, - 42161: process.env.RPC_ARBITRUM!, - 10: process.env.RPC_OPTIMISM!, - 43114: process.env.RPC_AVALANCHE!, - 8453: process.env.RPC_BASE!, - 80094: process.env.RPC_BERACHAIN!, - 196: process.env.RPC_X_LAYER!, - 2345: process.env.RPC_GOAT!, - 1088: process.env.RPC_METIS!, - 4217: process.env.RPC_TEMPO!, -} -``` - ---- - -## 14. Gaps & TODOs - -### 14.1 Documentation Gaps - -| Content | Priority | -|---------|----------| -| Complete Demo Project | High | -| Webhook Integration Guide | High | -| Go SDK Detailed Docs | Medium | -| Production Chain Configuration | Medium | - -### 14.2 SDK Features TODO - -| Feature | Status | -|---------|--------| -| Order Status Polling | Available through `getOrderStatus()` and `waitForConfirmation()` | -| WebSocket Real-time Notifications | ⏳ | -| Batch Payments | ⏳ | - ---- - -*Last verified against the repository implementation: 2026-06-26* +```ts +type OrderStatus = + | 'CHECKOUT_VERIFIED' + | 'PAYMENT_CONFIRMED' + | 'INVOICED' + | 'FAILED' + | 'EXPIRED' + | 'CANCELLED' +``` + +The browser SDK does not poll or classify order status itself. Use the backend +Server SDK, whose current waiters treat `PAYMENT_CONFIRMED` and `INVOICED` as +successful terminal states. This order model is separate from QuickPay session +status, whose terminal set does not include `INVOICED`. + +## 9. Security checklist + +1. Create orders on the backend. +2. Map only required server-order fields to the browser. +3. Validate wallet chain, payer, and expiry before payment. +4. Use returned `payToAddress`, token, and amount; do not construct terms in the + frontend. +5. Sign only the returned EIP-712 request. +6. Treat a wallet receipt as payment submission evidence, not final fulfillment. +7. Use exact approvals by default. +8. Preserve MPP recovery context after a broadcast. +9. Allow the DApp origin on Core and the protected resource, expose the + `Payment-Receipt` response header, and allow that request header. + +## 10. Verification sources + +Behavior in this guide was checked against: + +- `src/index.ts` +- `src/types.ts` +- `src/payment.ts` +- `src/contracts/erc20.ts` +- `src/eip712/index.ts` +- `src/mpp.ts` +- `src/__tests__/*` +- `package.json` + +Last verified: July 23, 2026.