diff --git a/cmd/obol/sell.go b/cmd/obol/sell.go index 83106627..379fb2d9 100644 --- a/cmd/obol/sell.go +++ b/cmd/obol/sell.go @@ -92,7 +92,7 @@ func sellInferenceCommand(cfg *config.Config) *cli.Command { Usage: "Sell local model inference with x402 payments", ArgsUsage: "", Description: `Starts an x402-gated reverse proxy in front of a local Ollama instance. -Buyers pay per-request in USDC to access inference endpoints. +Buyers pay per-request in the selected x402 token to access inference endpoints. Examples: obol sell inference my-qwen --model qwen3.5:4b --pay-to 0x... --price 0.001 @@ -102,7 +102,7 @@ Examples: Name: "model", Usage: "Model name to serve (e.g. qwen3.5:4b)", }, - payToFlag("USDC recipient address"), + payToFlag("Payment recipient address"), &cli.StringFlag{ Name: "price", Usage: "Per-request price (alias for --per-request)", @@ -113,7 +113,7 @@ Examples: }, &cli.StringFlag{ Name: "per-mtok", - Usage: "Per-million-tokens price in USDC (charged as an approximation at 1000 tok/request)", + Usage: "Per-million-tokens price in the selected payment token (charged as an approximation at 1000 tok/request)", }, &cli.StringFlag{ Name: "chain", @@ -240,7 +240,7 @@ Examples: u.Infof("Using wallet from remote-signer: %s", wallet) } else if u.IsTTY() { var inputErr error - wallet, inputErr = u.Input("Wallet address (USDC recipient)", "") + wallet, inputErr = u.Input("Wallet address (payment recipient)", "") if inputErr != nil || wallet == "" { return fmt.Errorf("recipient required: use --pay-to or set X402_WALLET") } @@ -518,7 +518,7 @@ Examples: obol sell http my-cool-api --upstream my-svc.my-namespace.svc.cluster.local --port 8080 --pay-to 0x... --price 0.01 --chain base obol sell http my-cool-api --upstream my-svc --port 8080 --pay-to 0x... --price 0.01 --chain base --no-register`, Flags: []cli.Flag{ - payToFlag("USDC recipient address"), + payToFlag("Payment recipient address"), &cli.StringFlag{ Name: "chain", Usage: "Payment chain (base, base-sepolia, ethereum)", @@ -531,19 +531,19 @@ Examples: }, &cli.StringFlag{ Name: "price", - Usage: "Per-request price in USDC (e.g. 0.001)", + Usage: "Per-request price in the selected payment token (e.g. 0.001 USDC or 1 OBOL)", }, &cli.StringFlag{ Name: "per-request", - Usage: "Per-request price in USDC (alias for --price)", + Usage: "Per-request price in the selected payment token (alias for --price)", }, &cli.StringFlag{ Name: "per-mtok", - Usage: "Per-million-tokens price in USDC (charged as an approximation at 1000 tok/request)", + Usage: "Per-million-tokens price in the selected payment token (charged as an approximation at 1000 tok/request)", }, &cli.StringFlag{ Name: "per-hour", - Usage: "Per-compute-hour price in USDC", + Usage: "Per-compute-hour price in the selected payment token", }, &cli.StringFlag{ Name: "namespace", @@ -689,7 +689,7 @@ Examples: u.Infof("Using wallet from remote-signer: %s", wallet) } else if u.IsTTY() { var inputErr error - wallet, inputErr = u.Input("Wallet address (USDC recipient)", "") + wallet, inputErr = u.Input("Wallet address (payment recipient)", "") if inputErr != nil || wallet == "" { return fmt.Errorf("recipient required: use --pay-to or set X402_WALLET") } @@ -2397,26 +2397,26 @@ Examples: Usage: "Namespace of the ServiceOffer", Required: true, }, - payToFlag("New USDC recipient address"), + payToFlag("New payment recipient address"), &cli.StringFlag{ Name: "chain", Usage: "New payment chain (base, base-sepolia, ethereum)", }, &cli.StringFlag{ Name: "price", - Usage: "New per-request price in USDC (alias for --per-request)", + Usage: "New per-request price in the selected payment token (alias for --per-request)", }, &cli.StringFlag{ Name: "per-request", - Usage: "New per-request price in USDC", + Usage: "New per-request price in the selected payment token", }, &cli.StringFlag{ Name: "per-mtok", - Usage: "New per-million-tokens price in USDC", + Usage: "New per-million-tokens price in the selected payment token", }, &cli.StringFlag{ Name: "per-hour", - Usage: "New per-compute-hour price in USDC", + Usage: "New per-compute-hour price in the selected payment token", }, }, Action: func(ctx context.Context, cmd *cli.Command) error { @@ -2576,7 +2576,7 @@ func sellPricingCommand(cfg *config.Config) *cli.Command { Description: `Sets the recipient address and chain for x402 payment collection. Reloads the payment verifier when configuration is changed.`, Flags: []cli.Flag{ - payToFlag("USDC recipient address"), + payToFlag("Payment recipient address"), &cli.StringFlag{ Name: "chain", Usage: "Payment chain (base, base-sepolia, ethereum)", diff --git a/docs/guides/monetize-inference.md b/docs/guides/monetize-inference.md index dec1ebc7..2fdf3680 100644 --- a/docs/guides/monetize-inference.md +++ b/docs/guides/monetize-inference.md @@ -188,12 +188,17 @@ obol sell http my-qwen \ When `--token OBOL` is used without `--chain`, the chain defaults to `ethereum`. -That stores both values in the pricing config: +For the USDC examples above, the pricing config stores both values: - source model: `perMTok = 1.25 USDC / 1M tokens` - enforced phase-1 charge: `price = 0.00125 USDC / request` - approximation input: `approxTokensPerRequest = 1000` +For OBOL release smokes, verify the generated payment config carries the selected +token metadata end to end instead of silently falling back to USDC wording or +USDC's ERC-3009 domain. The 402 requirement should expose the OBOL token address, +atomic OBOL amount, and Permit2/EIP-712 metadata needed by the buyer sidecar. + The stack now treats on-chain registration as part of the default selling flow: ``` @@ -284,7 +289,10 @@ A **402 Payment Required** response confirms the x402 gate is working. The respo } ``` -The `amount` is in USDC micro-units (6 decimals): `1000` = 0.001 USDC. +For this USDC example, `amount` is in USDC micro-units (6 decimals): +`1000` = 0.001 USDC. For an OBOL/Permit2 route, `asset` should be the OBOL +token address, `amount` should be atomic OBOL units, and `extra` should carry +the token/Permit2 metadata the buyer uses for signing. ### 1.7 Monitoring @@ -356,11 +364,12 @@ The SDK handles the full x402 flow: 1. Sends the request 2. Receives 402 with payment requirements -3. Signs an EIP-712 `TransferWithAuthorization` message (ERC-3009) +3. Signs the token-specific EIP-712 payment payload: ERC-3009 for USDC or + Permit2 for OBOL routes 4. Retries with the `X-PAYMENT` header (base64-encoded x402 envelope) 5. The seller-owned x402 gateway verifies the payment with the facilitator 6. The seller gateway forwards the request to the protected upstream -7. After upstream success, the seller gateway settles USDC on-chain +7. After upstream success, the seller gateway settles the selected token on-chain 8. Returns the inference response **Manual flow with curl** -- for debugging or custom integrations: @@ -373,8 +382,9 @@ curl -s -X POST "$TUNNEL_URL/services/my-qwen/v1/chat/completions" \ # Step 2: Sign the EIP-712 payment (requires SDK or custom code) # The 402 body contains: payTo, amount, asset, network, extra.name, extra.version -# Sign a TransferWithAuthorization (ERC-3009) message with: -# Domain: {name: "USDC", version: "2", chainId: 84532, verifyingContract: } +# USDC routes sign TransferWithAuthorization (ERC-3009) against the USDC domain. +# OBOL routes sign the configured Permit2 payload against the OBOL/token metadata +# published in the payment requirement. # Step 3: Retry with payment header curl -s -X POST "$TUNNEL_URL/services/my-qwen/v1/chat/completions" \ @@ -386,7 +396,8 @@ curl -s -X POST "$TUNNEL_URL/services/my-qwen/v1/chat/completions" \ ### 2.4 Verify Payment Settlement -After a successful paid request, verify the USDC transfer on-chain using Foundry's `cast`: +After a successful paid request, verify the selected token transfer on-chain using +Foundry's `cast`. For the default USDC example: ```bash USDC=0x036CbD53842c5426634e7929541eC2318f3dCF7e diff --git a/internal/embed/infrastructure/base/templates/purchaserequest-crd.yaml b/internal/embed/infrastructure/base/templates/purchaserequest-crd.yaml index d29f45ea..49bb7359 100644 --- a/internal/embed/infrastructure/base/templates/purchaserequest-crd.yaml +++ b/internal/embed/infrastructure/base/templates/purchaserequest-crd.yaml @@ -106,7 +106,7 @@ spec: type: string price: type: string - description: "Micro-USDC per request" + description: "Atomic token units per request" asset: type: string description: "ERC-20 contract address"