xytara 3.1.3 → 3.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/.env.example CHANGED
@@ -13,6 +13,10 @@ XYTARA_X402_SETTLE=true
13
13
  # credit units a single self-serve /x402/credits/topup grants (default 1000);
14
14
  # an accepts entry may override per-payment via its extra.credits
15
15
  XYTARA_X402_TOPUP_UNITS=1000
16
+ # Coinbase CDP auth for the CDP-hosted facilitator (Base mainnet); Ed25519 Secret API Key.
17
+ # The runtime mints a fresh CDP Bearer JWT per request when both are set.
18
+ CDP_API_KEY_ID=
19
+ CDP_API_KEY_SECRET=
16
20
  XYTARA_PAYMENT_MAX_AGE_SECONDS=300
17
21
  XYTARA_WALLET_REGISTRY_JSON=[]
18
22
  XYTARA_TREASURY_DESTINATION_ID=
@@ -65,6 +65,33 @@ mode with `XYTARA_X402_ACCEPTS_JSON` set, and credits persist only when runtime
65
65
  persistence (`XYTARA_SUPABASE_*` or `XYTARA_STATE_FILE`) is configured. A runnable buyer is in
66
66
  `services/mcp-execution-target/buyer-demo/x402-credits-topup-demo.js`.
67
67
 
68
+ ### Going to Base mainnet (real USDC)
69
+
70
+ Base Sepolia uses test USDC. To accept real value, advertise a Base mainnet profile and
71
+ settle it through a mainnet-capable facilitator.
72
+
73
+ Important: the default `https://x402.org/facilitator` settles **Base Sepolia only** (its
74
+ `/supported` list has no `eip155:8453`). Pointing a mainnet `accepts` entry at it fails
75
+ closed and the route is unpayable. For mainnet use a mainnet-capable facilitator, such as
76
+ Coinbase CDP (`https://api.cdp.coinbase.com/platform/v2/x402`), which is supported natively:
77
+ create a CDP Secret API Key (Ed25519) and set its id and secret, and the runtime mints a
78
+ fresh short-lived CDP Bearer JWT per facilitator call (CDP rejects static tokens).
79
+
80
+ ```env
81
+ # advertise mainnet (real USDC on Base); you can keep a Sepolia entry in the array too
82
+ XYTARA_X402_ACCEPTS_JSON=[{"scheme":"exact","network":"eip155:8453","asset":"0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913","amount":"10000","payTo":"<your-evm-address>","extra":{"name":"USDC","version":"2"},"maxTimeoutSeconds":60}]
83
+ # point at the CDP-hosted facilitator and supply the CDP Secret API Key:
84
+ XYTARA_X402_FACILITATOR_URL=https://api.cdp.coinbase.com/platform/v2/x402
85
+ CDP_API_KEY_ID=<your-cdp-key-id>
86
+ CDP_API_KEY_SECRET=<your-cdp-ed25519-secret-base64>
87
+ ```
88
+
89
+ The CDP key must use the **Ed25519** signature algorithm (not ECDSA). `CDP_API_KEY_ID` /
90
+ `CDP_API_KEY_SECRET` are the CDP-standard names; `XYTARA_X402_CDP_KEY_ID` /
91
+ `XYTARA_X402_CDP_KEY_SECRET` are accepted as overrides. `amount` is in the asset's base units
92
+ (USDC has 6 decimals, so `10000` = 0.01 USDC). Verify the CDP auth path locally with
93
+ `npm run verify:cdp-facilitator`. Once mainnet is live, buyers spend real USDC.
94
+
68
95
  ## Minimum Production-Oriented Configuration
69
96
 
70
97
  Set these environment variables:
package/RELEASE_NOTES.md CHANGED
@@ -1,16 +1,11 @@
1
- `xytara` 3.1.3 makes runtime credits self-serve: a buyer can turn a single x402 payment into spendable credit with no operator grant.
1
+ `xytara` 3.1.5 adds native Coinbase CDP facilitator support, unlocking Base mainnet settlement.
2
2
 
3
- This builds on the `x402_facilitator` on-chain settlement mode. Until now, credits could only be minted by an operator; a paying agent still needed someone to grant it a balance. This release closes that gap so the pay-and-use loop is fully autonomous.
3
+ The x402_facilitator mode now authenticates to facilitators that require a short-lived CDP Bearer token, so the runtime can settle real USDC on Base mainnet through the CDP-hosted facilitator, not just test USDC on Base Sepolia via the default facilitator.
4
4
 
5
5
  Highlights:
6
6
 
7
- - adds `POST /x402/credits/topup`: pay once with x402 and the facilitator settles the payment on-chain, then the runtime mints the credits to the account and returns the grant, the new balance, and a `PAYMENT-RESPONSE` settlement receipt
8
- - without a payment the route returns a `402` advertising the same `accepts` plus `credits_per_topup`, so a buyer discovers the price and the credit it buys before paying
9
- - credits land on `account_id` from the request body, falling back to the paying wallet address
10
- - idempotent by the payment nonce: re-sending the same settled payment never mints credits twice
11
- - each top-up grants `XYTARA_X402_TOPUP_UNITS` credit units (default `1000`), unless the matched `accepts` entry sets its own `extra.credits`
12
- - live only in `x402_facilitator` mode with `accepts` configured; fails closed if the facilitator is unreachable or the payment does not settle
13
- - extends `npm run verify:x402-facilitator` with the unpaid-challenge, paid-mint, and replay-idempotency cases for the new route
14
- - adds a runnable buyer, `services/mcp-execution-target/buyer-demo/x402-credits-topup-demo.js`, and documents the route in `REAL_PAYMENT_SETUP.md`
15
-
16
- No existing path changes: the per-call x402 routes, native lanes, and operator credit grants all behave exactly as before.
7
+ - the facilitator client mints a fresh EdDSA (Ed25519) CDP Bearer JWT per `/verify` and `/settle` request, with the `uri` claim bound to each request and a 120-second expiry, because CDP rejects static tokens
8
+ - enable it by setting `CDP_API_KEY_ID` and `CDP_API_KEY_SECRET` (or `XYTARA_X402_CDP_KEY_ID` / `XYTARA_X402_CDP_KEY_SECRET`) and pointing `XYTARA_X402_FACILITATOR_URL` at the CDP facilitator; the Ed25519 secret is the base64 64-byte key CDP issues
9
+ - zero new dependencies: the JWT is signed with Node's built-in Ed25519
10
+ - when no CDP key is configured, behavior is unchanged: static-header facilitators and the default Base Sepolia path work exactly as before
11
+ - adds `npm run verify:cdp-facilitator`, which checks the per-request token minting, claim shape, and signature against a mock facilitator, and documents the Base mainnet setup in `REAL_PAYMENT_SETUP.md`
@@ -6,9 +6,9 @@
6
6
  "source_ref": "spine.job.commitment",
7
7
  "committed_ref": "transaction.job.commitment",
8
8
  "precommit_pack_ref": "precommit.job.commitment.readiness",
9
- "transaction_id": "txn_c3db568e78ec",
10
- "receipt_id": "rcpt_fc14a1bfa4b5",
11
- "proof_ref": "proof_rcpt_fc14a1bfa4b5",
9
+ "transaction_id": "txn_18742578ec28",
10
+ "receipt_id": "rcpt_fa3b4219d3c4",
11
+ "proof_ref": "proof_rcpt_fa3b4219d3c4",
12
12
  "payment_protocol": "x402",
13
13
  "settlement_mode": "evm_payment",
14
14
  "treasury_destination_id": "treasury_acct_demo",
package/lib/cdp_jwt.js ADDED
@@ -0,0 +1,57 @@
1
+ "use strict";
2
+
3
+ // Zero-dependency Coinbase CDP Bearer JWT signer, used to authenticate requests to the
4
+ // CDP-hosted x402 facilitator (https://api.cdp.coinbase.com/platform/v2/x402), which
5
+ // settles Base mainnet. CDP authenticates each request with a short-lived (<=120s) EdDSA
6
+ // (Ed25519) JWT generated from a CDP Secret API Key, so a static Authorization header is
7
+ // not usable; a fresh token is minted per request with a `uri` claim bound to that request.
8
+ //
9
+ // Secret format: base64-encoded Ed25519, 64 bytes (first 32 = seed, last 32 = public key).
10
+ // Reference: https://docs.cdp.coinbase.com/get-started/docs/cdp-api-keys
11
+
12
+ const crypto = require("crypto");
13
+
14
+ // DER PKCS#8 prefix for an Ed25519 private key carrying a 32-byte seed.
15
+ const ED25519_PKCS8_PREFIX = Buffer.from("302e020100300506032b657004220420", "hex");
16
+
17
+ function base64url(input) {
18
+ const buf = Buffer.isBuffer(input) ? input : Buffer.from(input);
19
+ return buf.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
20
+ }
21
+
22
+ function privateKeyFromSecret(keySecret) {
23
+ const decoded = Buffer.from(String(keySecret || "").trim(), "base64");
24
+ if (decoded.length !== 64) {
25
+ throw new Error("cdp_key_secret_invalid: expected a base64 Ed25519 key of 64 bytes");
26
+ }
27
+ const seed = decoded.subarray(0, 32);
28
+ const pkcs8 = Buffer.concat([ED25519_PKCS8_PREFIX, seed]);
29
+ return crypto.createPrivateKey({ key: pkcs8, format: "der", type: "pkcs8" });
30
+ }
31
+
32
+ // Build a CDP Bearer JWT for a single request.
33
+ // keyId CDP API key id (UUID)
34
+ // keySecret base64 Ed25519 secret (64 bytes)
35
+ // method HTTP method, e.g. "POST"
36
+ // host request host, e.g. "api.cdp.coinbase.com"
37
+ // path request path, e.g. "/platform/v2/x402/verify"
38
+ function buildCdpBearerToken({ keyId, keySecret, method, host, path }) {
39
+ const id = String(keyId || "").trim();
40
+ if (!id) throw new Error("cdp_key_id_missing");
41
+ const privateKey = privateKeyFromSecret(keySecret);
42
+ const now = Math.floor(Date.now() / 1000);
43
+ const header = { alg: "EdDSA", typ: "JWT", kid: id, nonce: crypto.randomBytes(16).toString("hex") };
44
+ const claims = {
45
+ sub: id,
46
+ iss: "cdp",
47
+ aud: ["cdp_service"],
48
+ nbf: now,
49
+ exp: now + 120,
50
+ uri: `${String(method || "POST").toUpperCase()} ${host}${path}`
51
+ };
52
+ const signingInput = `${base64url(JSON.stringify(header))}.${base64url(JSON.stringify(claims))}`;
53
+ const signature = crypto.sign(null, Buffer.from(signingInput), privateKey);
54
+ return `${signingInput}.${base64url(signature)}`;
55
+ }
56
+
57
+ module.exports = { buildCdpBearerToken, base64url };