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 +4 -0
- package/REAL_PAYMENT_SETUP.md +27 -0
- package/RELEASE_NOTES.md +7 -12
- package/examples/generated.carried-handoff.json +3 -3
- package/lib/cdp_jwt.js +57 -0
- package/lib/payment_config.js +253 -242
- package/lib/payment_verification.js +313 -312
- package/lib/x402_facilitator.js +175 -155
- package/lib/x402_lane_contract.js +16 -0
- package/package.json +4 -3
- package/scripts/verify_cdp_facilitator.js +110 -0
- package/server.js +11227 -11226
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=
|
package/REAL_PAYMENT_SETUP.md
CHANGED
|
@@ -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.
|
|
1
|
+
`xytara` 3.1.5 adds native Coinbase CDP facilitator support, unlocking Base mainnet settlement.
|
|
2
2
|
|
|
3
|
-
|
|
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
|
-
-
|
|
8
|
-
-
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
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": "
|
|
10
|
-
"receipt_id": "
|
|
11
|
-
"proof_ref": "
|
|
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 };
|