@shroud-fi/x402 0.1.0
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/LICENSE +21 -0
- package/README.md +77 -0
- package/dist/cjs/client.d.ts +43 -0
- package/dist/cjs/client.d.ts.map +1 -0
- package/dist/cjs/client.js +199 -0
- package/dist/cjs/client.js.map +1 -0
- package/dist/cjs/constants.d.ts +16 -0
- package/dist/cjs/constants.d.ts.map +1 -0
- package/dist/cjs/constants.js +19 -0
- package/dist/cjs/constants.js.map +1 -0
- package/dist/cjs/errors.d.ts +61 -0
- package/dist/cjs/errors.d.ts.map +1 -0
- package/dist/cjs/errors.js +82 -0
- package/dist/cjs/errors.js.map +1 -0
- package/dist/cjs/facilitator.d.ts +50 -0
- package/dist/cjs/facilitator.d.ts.map +1 -0
- package/dist/cjs/facilitator.js +106 -0
- package/dist/cjs/facilitator.js.map +1 -0
- package/dist/cjs/index.d.ts +8 -0
- package/dist/cjs/index.d.ts.map +1 -0
- package/dist/cjs/index.js +29 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/package.json +1 -0
- package/dist/cjs/protocol.d.ts +117 -0
- package/dist/cjs/protocol.d.ts.map +1 -0
- package/dist/cjs/protocol.js +39 -0
- package/dist/cjs/protocol.js.map +1 -0
- package/dist/cjs/server.d.ts +49 -0
- package/dist/cjs/server.d.ts.map +1 -0
- package/dist/cjs/server.js +237 -0
- package/dist/cjs/server.js.map +1 -0
- package/dist/cjs/signing.d.ts +93 -0
- package/dist/cjs/signing.d.ts.map +1 -0
- package/dist/cjs/signing.js +170 -0
- package/dist/cjs/signing.js.map +1 -0
- package/dist/cjs/types.d.ts +93 -0
- package/dist/cjs/types.d.ts.map +1 -0
- package/dist/cjs/types.js +6 -0
- package/dist/cjs/types.js.map +1 -0
- package/dist/esm/client.d.ts +43 -0
- package/dist/esm/client.d.ts.map +1 -0
- package/dist/esm/client.js +196 -0
- package/dist/esm/client.js.map +1 -0
- package/dist/esm/constants.d.ts +16 -0
- package/dist/esm/constants.d.ts.map +1 -0
- package/dist/esm/constants.js +16 -0
- package/dist/esm/constants.js.map +1 -0
- package/dist/esm/errors.d.ts +61 -0
- package/dist/esm/errors.d.ts.map +1 -0
- package/dist/esm/errors.js +73 -0
- package/dist/esm/errors.js.map +1 -0
- package/dist/esm/facilitator.d.ts +50 -0
- package/dist/esm/facilitator.d.ts.map +1 -0
- package/dist/esm/facilitator.js +100 -0
- package/dist/esm/facilitator.js.map +1 -0
- package/dist/esm/index.d.ts +8 -0
- package/dist/esm/index.d.ts.map +1 -0
- package/dist/esm/index.js +12 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/protocol.d.ts +117 -0
- package/dist/esm/protocol.d.ts.map +1 -0
- package/dist/esm/protocol.js +36 -0
- package/dist/esm/protocol.js.map +1 -0
- package/dist/esm/server.d.ts +49 -0
- package/dist/esm/server.d.ts.map +1 -0
- package/dist/esm/server.js +234 -0
- package/dist/esm/server.js.map +1 -0
- package/dist/esm/signing.d.ts +93 -0
- package/dist/esm/signing.d.ts.map +1 -0
- package/dist/esm/signing.js +131 -0
- package/dist/esm/signing.js.map +1 -0
- package/dist/esm/types.d.ts +93 -0
- package/dist/esm/types.d.ts.map +1 -0
- package/dist/esm/types.js +5 -0
- package/dist/esm/types.js.map +1 -0
- package/dist/tsconfig.cjs.tsbuildinfo +1 -0
- package/dist/tsconfig.esm.tsbuildinfo +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/package.json +64 -0
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* x402 server — challenge generation + signed-payment verification.
|
|
3
|
+
*
|
|
4
|
+
* Privacy invariants enforced here:
|
|
5
|
+
* - Every challenge derives a FRESH stealth address from the recipient's
|
|
6
|
+
* meta-address. The recipient's main wallet NEVER appears in the
|
|
7
|
+
* PaymentRequired body.
|
|
8
|
+
* - The facilitator is only ever shown the stealth `payTo` — same property.
|
|
9
|
+
* - Error paths never include amount values, signature bytes, or the
|
|
10
|
+
* server's spend key.
|
|
11
|
+
*/
|
|
12
|
+
import { decodeMetaAddress } from '@shroud-fi/core';
|
|
13
|
+
import { prepareStealthPayment } from '@shroud-fi/payments';
|
|
14
|
+
import { getUSDC, getUSDCDomain } from '@shroud-fi/transport';
|
|
15
|
+
import { X402_PAYMENT_REQUIRED_HEADER, X402_SCHEME_EXACT, } from './protocol.js';
|
|
16
|
+
import { X402_VERSION, DEFAULT_MAX_TIMEOUT_SECS } from './constants.js';
|
|
17
|
+
import { X402AssetNotSupportedError, X402InvalidChallengeError, } from './errors.js';
|
|
18
|
+
import { facilitatorSettle, facilitatorVerify, resolveFacilitator, } from './facilitator.js';
|
|
19
|
+
import { verifyTransferWithAuthorizationSignature, } from './signing.js';
|
|
20
|
+
/**
|
|
21
|
+
* Encode a CAIP-2 network id from a chain id. EVM only.
|
|
22
|
+
*/
|
|
23
|
+
function caip2(chainId) {
|
|
24
|
+
return `eip155:${chainId}`;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Robust bigint coercion that accepts decimal strings, hex strings,
|
|
28
|
+
* numbers, and bigints. Throws X402InvalidChallengeError on anything else.
|
|
29
|
+
*/
|
|
30
|
+
function toBigInt(v) {
|
|
31
|
+
if (typeof v === 'bigint')
|
|
32
|
+
return v;
|
|
33
|
+
if (typeof v === 'number')
|
|
34
|
+
return BigInt(v);
|
|
35
|
+
if (typeof v === 'string') {
|
|
36
|
+
try {
|
|
37
|
+
return BigInt(v);
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
throw new X402InvalidChallengeError();
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
throw new X402InvalidChallengeError();
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Build an X402Server bound to a single recipient meta-address + asset.
|
|
47
|
+
*/
|
|
48
|
+
export function createX402Server(config) {
|
|
49
|
+
// Validate asset is canonical USDC for the chain. Fail-fast at construction.
|
|
50
|
+
const canonicalUsdc = getUSDC(config.chainId);
|
|
51
|
+
const usdcDomain = getUSDCDomain(config.chainId);
|
|
52
|
+
if (canonicalUsdc === undefined || usdcDomain === undefined) {
|
|
53
|
+
throw new X402AssetNotSupportedError();
|
|
54
|
+
}
|
|
55
|
+
if (config.asset.toLowerCase() !== canonicalUsdc.toLowerCase()) {
|
|
56
|
+
throw new X402AssetNotSupportedError();
|
|
57
|
+
}
|
|
58
|
+
// Pre-decode the meta-address ONCE — repeated decodes would waste cycles.
|
|
59
|
+
// The decode validates curve membership; downstream errors become
|
|
60
|
+
// X402InvalidChallengeError.
|
|
61
|
+
let decoded;
|
|
62
|
+
try {
|
|
63
|
+
decoded = decodeMetaAddress(config.recipientMetaAddress);
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
throw new X402InvalidChallengeError();
|
|
67
|
+
}
|
|
68
|
+
const facilitator = resolveFacilitator(config.facilitator);
|
|
69
|
+
return {
|
|
70
|
+
async challenge({ resource, description, priceAtomic }) {
|
|
71
|
+
const price = priceAtomic ?? config.defaultPriceAtomic;
|
|
72
|
+
// Derive a fresh stealth address per call. prepareStealthPayment hashes
|
|
73
|
+
// a fresh ephemeral key internally — uniqueness is built in.
|
|
74
|
+
const prepared = prepareStealthPayment(decoded);
|
|
75
|
+
const requirements = {
|
|
76
|
+
scheme: X402_SCHEME_EXACT,
|
|
77
|
+
network: caip2(config.chainId),
|
|
78
|
+
maxAmountRequired: price.toString(),
|
|
79
|
+
asset: config.asset,
|
|
80
|
+
payTo: prepared.stealthAddress,
|
|
81
|
+
maxTimeoutSeconds: DEFAULT_MAX_TIMEOUT_SECS,
|
|
82
|
+
resource,
|
|
83
|
+
...(description !== undefined ? { description } : {}),
|
|
84
|
+
extra: {
|
|
85
|
+
name: usdcDomain.name,
|
|
86
|
+
version: usdcDomain.version,
|
|
87
|
+
shroudfiAnnouncement: {
|
|
88
|
+
ephemeralPubKey: prepared.ephemeralPubKey,
|
|
89
|
+
viewTag: prepared.viewTag,
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
const body = {
|
|
94
|
+
x402Version: X402_VERSION,
|
|
95
|
+
error: 'Payment required',
|
|
96
|
+
accepts: [requirements],
|
|
97
|
+
};
|
|
98
|
+
return {
|
|
99
|
+
status: 402,
|
|
100
|
+
headers: {
|
|
101
|
+
[X402_PAYMENT_REQUIRED_HEADER]: JSON.stringify(body),
|
|
102
|
+
'content-type': 'application/json',
|
|
103
|
+
},
|
|
104
|
+
body,
|
|
105
|
+
announcement: {
|
|
106
|
+
ephemeralPubKey: prepared.ephemeralPubKey,
|
|
107
|
+
viewTag: prepared.viewTag,
|
|
108
|
+
stealthAddress: prepared.stealthAddress,
|
|
109
|
+
},
|
|
110
|
+
};
|
|
111
|
+
},
|
|
112
|
+
async verify({ signedPayload, challenge, skipFacilitator }) {
|
|
113
|
+
// 1. Decode incoming payload.
|
|
114
|
+
let payload;
|
|
115
|
+
if (typeof signedPayload === 'string') {
|
|
116
|
+
try {
|
|
117
|
+
// Spec: header value is base64-encoded JSON.
|
|
118
|
+
const decodedJson = Buffer.from(signedPayload, 'base64').toString('utf-8');
|
|
119
|
+
payload = JSON.parse(decodedJson);
|
|
120
|
+
}
|
|
121
|
+
catch {
|
|
122
|
+
return { valid: false, error: 'malformed_payload' };
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
payload = signedPayload;
|
|
127
|
+
}
|
|
128
|
+
// 2. Structural checks. Tagged failures only — no field values in errors.
|
|
129
|
+
if (payload.x402Version !== 2 ||
|
|
130
|
+
payload.scheme !== X402_SCHEME_EXACT ||
|
|
131
|
+
payload.network !== caip2(config.chainId)) {
|
|
132
|
+
return { valid: false, error: 'scheme_mismatch' };
|
|
133
|
+
}
|
|
134
|
+
const auth = payload.payload?.authorization;
|
|
135
|
+
const sig = payload.payload?.signature;
|
|
136
|
+
if (auth === undefined || sig === undefined) {
|
|
137
|
+
return { valid: false, error: 'malformed_payload' };
|
|
138
|
+
}
|
|
139
|
+
const accepted = challenge.body.accepts[0];
|
|
140
|
+
if (accepted === undefined) {
|
|
141
|
+
return { valid: false, error: 'malformed_challenge' };
|
|
142
|
+
}
|
|
143
|
+
// 3. Authorization fields must address the same stealth `payTo` and the
|
|
144
|
+
// exact required amount.
|
|
145
|
+
let value;
|
|
146
|
+
let validAfter;
|
|
147
|
+
let validBefore;
|
|
148
|
+
try {
|
|
149
|
+
value = toBigInt(auth.value);
|
|
150
|
+
validAfter = toBigInt(auth.validAfter);
|
|
151
|
+
validBefore = toBigInt(auth.validBefore);
|
|
152
|
+
}
|
|
153
|
+
catch {
|
|
154
|
+
return { valid: false, error: 'malformed_payload' };
|
|
155
|
+
}
|
|
156
|
+
if (auth.to.toLowerCase() !== accepted.payTo.toLowerCase()) {
|
|
157
|
+
return { valid: false, error: 'recipient_mismatch' };
|
|
158
|
+
}
|
|
159
|
+
let requiredAmount;
|
|
160
|
+
try {
|
|
161
|
+
requiredAmount = toBigInt(accepted.maxAmountRequired);
|
|
162
|
+
}
|
|
163
|
+
catch {
|
|
164
|
+
return { valid: false, error: 'malformed_challenge' };
|
|
165
|
+
}
|
|
166
|
+
if (value !== requiredAmount) {
|
|
167
|
+
return { valid: false, error: 'amount_mismatch' };
|
|
168
|
+
}
|
|
169
|
+
// 4. Time window.
|
|
170
|
+
const nowSecs = BigInt(Math.floor(Date.now() / 1000));
|
|
171
|
+
if (validBefore <= nowSecs) {
|
|
172
|
+
return { valid: false, error: 'expired' };
|
|
173
|
+
}
|
|
174
|
+
if (validAfter > nowSecs) {
|
|
175
|
+
return { valid: false, error: 'not_yet_valid' };
|
|
176
|
+
}
|
|
177
|
+
// 5. Signature recovery.
|
|
178
|
+
const authInput = {
|
|
179
|
+
from: auth.from,
|
|
180
|
+
to: auth.to,
|
|
181
|
+
value,
|
|
182
|
+
validAfter,
|
|
183
|
+
validBefore,
|
|
184
|
+
nonce: auth.nonce,
|
|
185
|
+
};
|
|
186
|
+
const recovered = await verifyTransferWithAuthorizationSignature({
|
|
187
|
+
chainId: config.chainId,
|
|
188
|
+
verifyingContract: config.asset,
|
|
189
|
+
authorization: authInput,
|
|
190
|
+
signature: sig,
|
|
191
|
+
});
|
|
192
|
+
if (recovered === null ||
|
|
193
|
+
recovered.toLowerCase() !== auth.from.toLowerCase()) {
|
|
194
|
+
return { valid: false, error: 'signature_invalid' };
|
|
195
|
+
}
|
|
196
|
+
// 6a. Self-settle mode: caller handles on-chain settlement, we return
|
|
197
|
+
// valid:true after signature recovery. No facilitator contact.
|
|
198
|
+
if (skipFacilitator === true) {
|
|
199
|
+
return { valid: true };
|
|
200
|
+
}
|
|
201
|
+
// 6b. Facilitator submission. Caller gets the settled tx hash.
|
|
202
|
+
// We send the facilitator only what it needs: the requirements + the
|
|
203
|
+
// signed payload. The recipient's main wallet is never in this body
|
|
204
|
+
// because `accepted.payTo` is the freshly derived stealth address.
|
|
205
|
+
try {
|
|
206
|
+
const verifyResult = await facilitatorVerify(facilitator, {
|
|
207
|
+
x402Version: X402_VERSION,
|
|
208
|
+
paymentRequirements: accepted,
|
|
209
|
+
paymentPayload: payload,
|
|
210
|
+
});
|
|
211
|
+
if (!verifyResult.isValid) {
|
|
212
|
+
return { valid: false, error: 'facilitator_rejected' };
|
|
213
|
+
}
|
|
214
|
+
const settle = await facilitatorSettle(facilitator, {
|
|
215
|
+
x402Version: X402_VERSION,
|
|
216
|
+
paymentRequirements: accepted,
|
|
217
|
+
paymentPayload: payload,
|
|
218
|
+
});
|
|
219
|
+
if (!settle.success || settle.txHash === undefined) {
|
|
220
|
+
return { valid: false, error: 'facilitator_settle_failed' };
|
|
221
|
+
}
|
|
222
|
+
return { valid: true, settledTxHash: settle.txHash };
|
|
223
|
+
}
|
|
224
|
+
catch {
|
|
225
|
+
// Facilitator unreachable / non-2xx. Return signature-verified=true
|
|
226
|
+
// so the caller can decide whether to retry or fall back. The signed
|
|
227
|
+
// payload remains cryptographically valid even when the facilitator
|
|
228
|
+
// path failed.
|
|
229
|
+
return { valid: false, error: 'facilitator_unreachable' };
|
|
230
|
+
}
|
|
231
|
+
},
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/server.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* EIP-3009 `TransferWithAuthorization` typed-data signing for x402.
|
|
3
|
+
*
|
|
4
|
+
* Privacy invariants:
|
|
5
|
+
* - The private key never leaves the viem `account` object. We do not
|
|
6
|
+
* extract it, log it, or attach it to errors.
|
|
7
|
+
* - The signed payload (`signature`, `nonce`, `value`) is never logged,
|
|
8
|
+
* stringified into errors, or stored. Caller is responsible for sending
|
|
9
|
+
* it directly to the server in the `X-PAYMENT` header.
|
|
10
|
+
* - `nonce` is a fresh 32 bytes per call via `crypto.getRandomValues`,
|
|
11
|
+
* never reused.
|
|
12
|
+
*/
|
|
13
|
+
import type { Account, Address, Chain, Hex } from 'viem';
|
|
14
|
+
/**
|
|
15
|
+
* EIP-3009 `TransferWithAuthorization` typed-data types.
|
|
16
|
+
* Exported for tests; the runtime signer constructs the same object inline.
|
|
17
|
+
*/
|
|
18
|
+
export declare const TransferWithAuthorizationTypes: {
|
|
19
|
+
readonly TransferWithAuthorization: readonly [{
|
|
20
|
+
readonly name: "from";
|
|
21
|
+
readonly type: "address";
|
|
22
|
+
}, {
|
|
23
|
+
readonly name: "to";
|
|
24
|
+
readonly type: "address";
|
|
25
|
+
}, {
|
|
26
|
+
readonly name: "value";
|
|
27
|
+
readonly type: "uint256";
|
|
28
|
+
}, {
|
|
29
|
+
readonly name: "validAfter";
|
|
30
|
+
readonly type: "uint256";
|
|
31
|
+
}, {
|
|
32
|
+
readonly name: "validBefore";
|
|
33
|
+
readonly type: "uint256";
|
|
34
|
+
}, {
|
|
35
|
+
readonly name: "nonce";
|
|
36
|
+
readonly type: "bytes32";
|
|
37
|
+
}];
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* The cleartext authorization fields. Caller passes them in; signer hashes,
|
|
41
|
+
* signs, and returns both signature + the authorization (so the caller can
|
|
42
|
+
* forward the unsigned data alongside the signature).
|
|
43
|
+
*/
|
|
44
|
+
export interface TransferWithAuthorizationInput {
|
|
45
|
+
readonly from: Address;
|
|
46
|
+
readonly to: Address;
|
|
47
|
+
readonly value: bigint;
|
|
48
|
+
readonly validAfter: bigint;
|
|
49
|
+
readonly validBefore: bigint;
|
|
50
|
+
readonly nonce: Hex;
|
|
51
|
+
}
|
|
52
|
+
export interface SignedTransferWithAuthorization {
|
|
53
|
+
readonly signature: Hex;
|
|
54
|
+
readonly nonce: Hex;
|
|
55
|
+
readonly from: Address;
|
|
56
|
+
readonly to: Address;
|
|
57
|
+
readonly value: bigint;
|
|
58
|
+
readonly validAfter: bigint;
|
|
59
|
+
readonly validBefore: bigint;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Generate a fresh 32-byte nonce for EIP-3009. Uses Web Crypto (available in
|
|
63
|
+
* Node 20+ and all browsers). Never reuse — each authorization needs a
|
|
64
|
+
* unique nonce to avoid replay.
|
|
65
|
+
*/
|
|
66
|
+
export declare function generateAuthorizationNonce(): Hex;
|
|
67
|
+
/**
|
|
68
|
+
* Sign an EIP-3009 `TransferWithAuthorization` over USDC. Returns the
|
|
69
|
+
* 65-byte signature + the authorization fields the server will replay.
|
|
70
|
+
*
|
|
71
|
+
* @throws X402AssetNotSupportedError if USDC is not configured for this chain.
|
|
72
|
+
*/
|
|
73
|
+
export declare function signTransferWithAuthorization(args: {
|
|
74
|
+
readonly account: Account;
|
|
75
|
+
readonly chain: Chain;
|
|
76
|
+
readonly verifyingContract: Address;
|
|
77
|
+
readonly input: TransferWithAuthorizationInput;
|
|
78
|
+
}): Promise<SignedTransferWithAuthorization>;
|
|
79
|
+
/**
|
|
80
|
+
* Verify a `TransferWithAuthorization` signature on the server side.
|
|
81
|
+
* Returns the recovered signer address (`from`) or `null` on failure.
|
|
82
|
+
*
|
|
83
|
+
* NOTE: we do NOT throw on failure — caller maps `null` → verify.valid=false
|
|
84
|
+
* with an error tag. Throwing here would risk leaking signature bytes via
|
|
85
|
+
* thrown error stacks.
|
|
86
|
+
*/
|
|
87
|
+
export declare function verifyTransferWithAuthorizationSignature(args: {
|
|
88
|
+
readonly chainId: number;
|
|
89
|
+
readonly verifyingContract: Address;
|
|
90
|
+
readonly authorization: TransferWithAuthorizationInput;
|
|
91
|
+
readonly signature: Hex;
|
|
92
|
+
}): Promise<Address | null>;
|
|
93
|
+
//# sourceMappingURL=signing.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"signing.d.ts","sourceRoot":"","sources":["../../src/signing.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,GAAG,EAAE,MAAM,MAAM,CAAC;AAIzD;;;GAGG;AACH,eAAO,MAAM,8BAA8B;;;;;;;;;;;;;;;;;;;;CASjC,CAAC;AAEX;;;;GAIG;AACH,MAAM,WAAW,8BAA8B;IAC7C,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IACvB,QAAQ,CAAC,EAAE,EAAE,OAAO,CAAC;IACrB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,KAAK,EAAE,GAAG,CAAC;CACrB;AAED,MAAM,WAAW,+BAA+B;IAC9C,QAAQ,CAAC,SAAS,EAAE,GAAG,CAAC;IACxB,QAAQ,CAAC,KAAK,EAAE,GAAG,CAAC;IACpB,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IACvB,QAAQ,CAAC,EAAE,EAAE,OAAO,CAAC;IACrB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;CAC9B;AAED;;;;GAIG;AACH,wBAAgB,0BAA0B,IAAI,GAAG,CAUhD;AAED;;;;;GAKG;AACH,wBAAsB,6BAA6B,CAAC,IAAI,EAAE;IACxD,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC;IACtB,QAAQ,CAAC,iBAAiB,EAAE,OAAO,CAAC;IACpC,QAAQ,CAAC,KAAK,EAAE,8BAA8B,CAAC;CAChD,GAAG,OAAO,CAAC,+BAA+B,CAAC,CA4C3C;AAED;;;;;;;GAOG;AACH,wBAAsB,wCAAwC,CAAC,IAAI,EAAE;IACnE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,iBAAiB,EAAE,OAAO,CAAC;IACpC,QAAQ,CAAC,aAAa,EAAE,8BAA8B,CAAC;IACvD,QAAQ,CAAC,SAAS,EAAE,GAAG,CAAC;CACzB,GAAG,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC,CA6B1B"}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* EIP-3009 `TransferWithAuthorization` typed-data signing for x402.
|
|
3
|
+
*
|
|
4
|
+
* Privacy invariants:
|
|
5
|
+
* - The private key never leaves the viem `account` object. We do not
|
|
6
|
+
* extract it, log it, or attach it to errors.
|
|
7
|
+
* - The signed payload (`signature`, `nonce`, `value`) is never logged,
|
|
8
|
+
* stringified into errors, or stored. Caller is responsible for sending
|
|
9
|
+
* it directly to the server in the `X-PAYMENT` header.
|
|
10
|
+
* - `nonce` is a fresh 32 bytes per call via `crypto.getRandomValues`,
|
|
11
|
+
* never reused.
|
|
12
|
+
*/
|
|
13
|
+
import { getUSDCDomain } from '@shroud-fi/transport';
|
|
14
|
+
import { X402AssetNotSupportedError } from './errors.js';
|
|
15
|
+
/**
|
|
16
|
+
* EIP-3009 `TransferWithAuthorization` typed-data types.
|
|
17
|
+
* Exported for tests; the runtime signer constructs the same object inline.
|
|
18
|
+
*/
|
|
19
|
+
export const TransferWithAuthorizationTypes = {
|
|
20
|
+
TransferWithAuthorization: [
|
|
21
|
+
{ name: 'from', type: 'address' },
|
|
22
|
+
{ name: 'to', type: 'address' },
|
|
23
|
+
{ name: 'value', type: 'uint256' },
|
|
24
|
+
{ name: 'validAfter', type: 'uint256' },
|
|
25
|
+
{ name: 'validBefore', type: 'uint256' },
|
|
26
|
+
{ name: 'nonce', type: 'bytes32' },
|
|
27
|
+
],
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* Generate a fresh 32-byte nonce for EIP-3009. Uses Web Crypto (available in
|
|
31
|
+
* Node 20+ and all browsers). Never reuse — each authorization needs a
|
|
32
|
+
* unique nonce to avoid replay.
|
|
33
|
+
*/
|
|
34
|
+
export function generateAuthorizationNonce() {
|
|
35
|
+
const bytes = new Uint8Array(32);
|
|
36
|
+
// Web Crypto is globally available on Node 20+; deliberately not using
|
|
37
|
+
// `node:crypto.randomBytes` so the module stays runtime-agnostic.
|
|
38
|
+
globalThis.crypto.getRandomValues(bytes);
|
|
39
|
+
let hex = '0x';
|
|
40
|
+
for (const b of bytes) {
|
|
41
|
+
hex += b.toString(16).padStart(2, '0');
|
|
42
|
+
}
|
|
43
|
+
return hex;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Sign an EIP-3009 `TransferWithAuthorization` over USDC. Returns the
|
|
47
|
+
* 65-byte signature + the authorization fields the server will replay.
|
|
48
|
+
*
|
|
49
|
+
* @throws X402AssetNotSupportedError if USDC is not configured for this chain.
|
|
50
|
+
*/
|
|
51
|
+
export async function signTransferWithAuthorization(args) {
|
|
52
|
+
const { account, chain, verifyingContract, input } = args;
|
|
53
|
+
const usdcDomain = getUSDCDomain(chain.id);
|
|
54
|
+
if (usdcDomain === undefined) {
|
|
55
|
+
throw new X402AssetNotSupportedError();
|
|
56
|
+
}
|
|
57
|
+
const domain = {
|
|
58
|
+
name: usdcDomain.name,
|
|
59
|
+
version: usdcDomain.version,
|
|
60
|
+
chainId: chain.id,
|
|
61
|
+
verifyingContract,
|
|
62
|
+
};
|
|
63
|
+
if (account.signTypedData === undefined) {
|
|
64
|
+
// Local account; viem provides .signTypedData for privateKeyToAccount.
|
|
65
|
+
// For JSON-RPC accounts the caller must provide a wrapper. Treat as
|
|
66
|
+
// unsupported configuration rather than leaking the underlying error.
|
|
67
|
+
throw new X402AssetNotSupportedError();
|
|
68
|
+
}
|
|
69
|
+
const signature = await account.signTypedData({
|
|
70
|
+
domain,
|
|
71
|
+
types: TransferWithAuthorizationTypes,
|
|
72
|
+
primaryType: 'TransferWithAuthorization',
|
|
73
|
+
message: {
|
|
74
|
+
from: input.from,
|
|
75
|
+
to: input.to,
|
|
76
|
+
value: input.value,
|
|
77
|
+
validAfter: input.validAfter,
|
|
78
|
+
validBefore: input.validBefore,
|
|
79
|
+
nonce: input.nonce,
|
|
80
|
+
},
|
|
81
|
+
});
|
|
82
|
+
return {
|
|
83
|
+
signature,
|
|
84
|
+
nonce: input.nonce,
|
|
85
|
+
from: input.from,
|
|
86
|
+
to: input.to,
|
|
87
|
+
value: input.value,
|
|
88
|
+
validAfter: input.validAfter,
|
|
89
|
+
validBefore: input.validBefore,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Verify a `TransferWithAuthorization` signature on the server side.
|
|
94
|
+
* Returns the recovered signer address (`from`) or `null` on failure.
|
|
95
|
+
*
|
|
96
|
+
* NOTE: we do NOT throw on failure — caller maps `null` → verify.valid=false
|
|
97
|
+
* with an error tag. Throwing here would risk leaking signature bytes via
|
|
98
|
+
* thrown error stacks.
|
|
99
|
+
*/
|
|
100
|
+
export async function verifyTransferWithAuthorizationSignature(args) {
|
|
101
|
+
const { chainId, verifyingContract, authorization, signature } = args;
|
|
102
|
+
const usdcDomain = getUSDCDomain(chainId);
|
|
103
|
+
if (usdcDomain === undefined)
|
|
104
|
+
return null;
|
|
105
|
+
const { recoverTypedDataAddress } = await import('viem');
|
|
106
|
+
try {
|
|
107
|
+
return await recoverTypedDataAddress({
|
|
108
|
+
domain: {
|
|
109
|
+
name: usdcDomain.name,
|
|
110
|
+
version: usdcDomain.version,
|
|
111
|
+
chainId,
|
|
112
|
+
verifyingContract,
|
|
113
|
+
},
|
|
114
|
+
types: TransferWithAuthorizationTypes,
|
|
115
|
+
primaryType: 'TransferWithAuthorization',
|
|
116
|
+
message: {
|
|
117
|
+
from: authorization.from,
|
|
118
|
+
to: authorization.to,
|
|
119
|
+
value: authorization.value,
|
|
120
|
+
validAfter: authorization.validAfter,
|
|
121
|
+
validBefore: authorization.validBefore,
|
|
122
|
+
nonce: authorization.nonce,
|
|
123
|
+
},
|
|
124
|
+
signature,
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
catch {
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
//# sourceMappingURL=signing.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"signing.js","sourceRoot":"","sources":["../../src/signing.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared types used by both server and client.
|
|
3
|
+
*/
|
|
4
|
+
import type { Address, Hex } from 'viem';
|
|
5
|
+
import type { X402FacilitatorConfig, X402PaymentRequirements } from './protocol.js';
|
|
6
|
+
/**
|
|
7
|
+
* Hint object returned alongside a freshly generated challenge.
|
|
8
|
+
*
|
|
9
|
+
* The server uses these values to emit an ERC-5564 Announcement event AFTER
|
|
10
|
+
* the payment settles (out-of-scope for this milestone — operator wires the
|
|
11
|
+
* on-chain emit). Carrying it on the challenge object lets the server
|
|
12
|
+
* correlate "this challenge → this announcement" without re-derivation.
|
|
13
|
+
*
|
|
14
|
+
* Privacy: contains only the ephemeral public key + view tag + the freshly
|
|
15
|
+
* derived stealth address. None of these leak the recipient's main wallet.
|
|
16
|
+
*/
|
|
17
|
+
export interface X402StealthAnnouncementHint {
|
|
18
|
+
readonly ephemeralPubKey: Hex;
|
|
19
|
+
readonly viewTag: number;
|
|
20
|
+
readonly stealthAddress: Address;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* The full server-side challenge object. Comes out of `x402.challenge(...)`.
|
|
24
|
+
*
|
|
25
|
+
* `status` + `headers` + `body` are wire-shaped — operator's HTTP framework
|
|
26
|
+
* adapter simply spreads them onto its response. `announcement` is internal
|
|
27
|
+
* metadata for the operator's scanner integration.
|
|
28
|
+
*/
|
|
29
|
+
export interface X402Challenge {
|
|
30
|
+
readonly status: 402;
|
|
31
|
+
readonly headers: Readonly<Record<string, string>>;
|
|
32
|
+
readonly body: {
|
|
33
|
+
readonly x402Version: 2;
|
|
34
|
+
readonly error: string;
|
|
35
|
+
readonly accepts: readonly X402PaymentRequirements[];
|
|
36
|
+
};
|
|
37
|
+
readonly announcement: X402StealthAnnouncementHint;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Result of `x402.verify(...)`.
|
|
41
|
+
*
|
|
42
|
+
* Privacy: no amount, no signature, no nonce. `error` is a short tag string
|
|
43
|
+
* only ('signature_invalid', 'amount_mismatch', 'expired', etc) — never the
|
|
44
|
+
* full underlying error or any bytes.
|
|
45
|
+
*/
|
|
46
|
+
export interface X402PaymentVerification {
|
|
47
|
+
readonly valid: boolean;
|
|
48
|
+
readonly settledTxHash?: Hex;
|
|
49
|
+
readonly error?: string;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* `createX402Server` config.
|
|
53
|
+
*
|
|
54
|
+
* Privacy: the `recipientMetaAddress` is the server agent's PUBLIC meta-address
|
|
55
|
+
* (`st:base:0x...`). No private keys ever appear in this config.
|
|
56
|
+
*/
|
|
57
|
+
export interface X402ServerConfig {
|
|
58
|
+
/** Transport for reads + chain id lookups. */
|
|
59
|
+
readonly transport: import('@shroud-fi/transport').ShroudFiTransport;
|
|
60
|
+
/** ERC-6538 meta-address string (`st:base:0x...`) for the receiving agent. */
|
|
61
|
+
readonly recipientMetaAddress: string;
|
|
62
|
+
/** ERC-20 asset address (must be the canonical USDC for the chain). */
|
|
63
|
+
readonly asset: Address;
|
|
64
|
+
/** EVM chain id (must match the asset's deployment). */
|
|
65
|
+
readonly chainId: number;
|
|
66
|
+
/** Default price in token base units (uint256). */
|
|
67
|
+
readonly defaultPriceAtomic: bigint;
|
|
68
|
+
/** Optional facilitator override; defaults to PayAI free-tier. */
|
|
69
|
+
readonly facilitator?: X402FacilitatorConfig;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* `createX402Client` config.
|
|
73
|
+
*
|
|
74
|
+
* Privacy: the client uses the transport's `walletClient.account` for
|
|
75
|
+
* signing. The private key never appears here directly — it lives inside
|
|
76
|
+
* the viem account, scoped to the transport's lifetime.
|
|
77
|
+
*/
|
|
78
|
+
export interface X402ClientConfig {
|
|
79
|
+
/** Transport with a configured `walletClient` (account required). */
|
|
80
|
+
readonly transport: import('@shroud-fi/transport').ShroudFiTransport;
|
|
81
|
+
/** Optional facilitator override; defaults to PayAI free-tier. */
|
|
82
|
+
readonly facilitator?: X402FacilitatorConfig;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* What a client `.fetch(...)` call surfaces beyond the standard Response.
|
|
86
|
+
* Non-standard `x402Settlement` property is attached when the server returned
|
|
87
|
+
* `X-PAYMENT-RESPONSE`. Optional — present only on successful auto-pay.
|
|
88
|
+
*/
|
|
89
|
+
export interface X402PaymentResult {
|
|
90
|
+
readonly txHash: Hex;
|
|
91
|
+
readonly settledAt: number;
|
|
92
|
+
}
|
|
93
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,MAAM,CAAC;AACzC,OAAO,KAAK,EACV,qBAAqB,EACrB,uBAAuB,EACxB,MAAM,eAAe,CAAC;AAEvB;;;;;;;;;;GAUG;AACH,MAAM,WAAW,2BAA2B;IAC1C,QAAQ,CAAC,eAAe,EAAE,GAAG,CAAC;IAC9B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAC;CAClC;AAED;;;;;;GAMG;AACH,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,MAAM,EAAE,GAAG,CAAC;IACrB,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACnD,QAAQ,CAAC,IAAI,EAAE;QACb,QAAQ,CAAC,WAAW,EAAE,CAAC,CAAC;QACxB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;QACvB,QAAQ,CAAC,OAAO,EAAE,SAAS,uBAAuB,EAAE,CAAC;KACtD,CAAC;IACF,QAAQ,CAAC,YAAY,EAAE,2BAA2B,CAAC;CACpD;AAED;;;;;;GAMG;AACH,MAAM,WAAW,uBAAuB;IACtC,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB,QAAQ,CAAC,aAAa,CAAC,EAAE,GAAG,CAAC;IAC7B,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;CACzB;AAED;;;;;GAKG;AACH,MAAM,WAAW,gBAAgB;IAC/B,8CAA8C;IAC9C,QAAQ,CAAC,SAAS,EAAE,OAAO,sBAAsB,EAAE,iBAAiB,CAAC;IACrE,8EAA8E;IAC9E,QAAQ,CAAC,oBAAoB,EAAE,MAAM,CAAC;IACtC,uEAAuE;IACvE,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB,wDAAwD;IACxD,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,mDAAmD;IACnD,QAAQ,CAAC,kBAAkB,EAAE,MAAM,CAAC;IACpC,kEAAkE;IAClE,QAAQ,CAAC,WAAW,CAAC,EAAE,qBAAqB,CAAC;CAC9C;AAED;;;;;;GAMG;AACH,MAAM,WAAW,gBAAgB;IAC/B,qEAAqE;IACrE,QAAQ,CAAC,SAAS,EAAE,OAAO,sBAAsB,EAAE,iBAAiB,CAAC;IACrE,kEAAkE;IAClE,QAAQ,CAAC,WAAW,CAAC,EAAE,qBAAqB,CAAC;CAC9C;AAED;;;;GAIG;AACH,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,MAAM,EAAE,GAAG,CAAC;IACrB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG"}
|