@primitivedotdev/sdk 1.10.0 → 1.12.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/README.md +13 -2
- package/dist/api/index.d.ts +2 -2
- package/dist/api/index.js +3 -3
- package/dist/{api-CwpE8_17.js → api-Dult2Jxz.js} +107 -1
- package/dist/{index-FvnZT9Be.d.ts → index-M8zf3TQG.d.ts} +351 -2
- package/dist/index.d.ts +3 -3
- package/dist/index.js +3 -3
- package/dist/openapi/index.js +1 -1
- package/dist/{operations.generated-BlW1vtGP.js → operations.generated-Bo_FEI2Q.js} +3889 -2947
- package/dist/x402/index.d.ts +69 -7
- package/dist/x402/index.js +119 -14
- package/package.json +1 -1
package/dist/x402/index.d.ts
CHANGED
|
@@ -197,12 +197,32 @@ declare function toPaymentPayload(network: string, auth: TransferAuthorization,
|
|
|
197
197
|
*/
|
|
198
198
|
declare const DEFAULT_MAX_WINDOW_SEC: number;
|
|
199
199
|
/**
|
|
200
|
-
*
|
|
201
|
-
*
|
|
202
|
-
*
|
|
203
|
-
*
|
|
204
|
-
*
|
|
205
|
-
|
|
200
|
+
* Minimum headroom between now and `validBefore`. The platform rejects a
|
|
201
|
+
* payment whose authorization is about to expire (it needs SMTP + DKIM + verify
|
|
202
|
+
* + settle latency to clear), so a `validBefore` less than this far in the
|
|
203
|
+
* future is a guaranteed-to-fail signature. The default window is minutes; this
|
|
204
|
+
* 60s floor is the absolute minimum the band tolerates.
|
|
205
|
+
*/
|
|
206
|
+
declare const DEFAULT_MIN_SETTLEMENT_HEADROOM_SEC = 60;
|
|
207
|
+
/**
|
|
208
|
+
* Compute the EIP-3009 validity window for a payment, landing inside the band
|
|
209
|
+
* the platform accepts. `validBefore` governs on-chain validity, so it MUST
|
|
210
|
+
* stay far enough in the future to settle (>= `minHeadroomSec`) yet not so far
|
|
211
|
+
* that the total window exceeds the `maxWindowSec` cap; `validAfter` is set
|
|
212
|
+
* generously in the past for clock skew.
|
|
213
|
+
*
|
|
214
|
+
* Both ends of that band are payer landmines: a too-tight `validBefore` (low
|
|
215
|
+
* headroom, e.g. a near-expired challenge) is rejected for being about to
|
|
216
|
+
* expire, and a too-wide window (far-future expiry) is rejected as
|
|
217
|
+
* "authorization window too wide". By default this clamps the computed window
|
|
218
|
+
* into the band so a caller who does not override always gets a signable
|
|
219
|
+
* window.
|
|
220
|
+
*
|
|
221
|
+
* If the caller passes an explicit `validBeforeSec` or `validAfterSec`, that is
|
|
222
|
+
* an intent to pin the bound: when it falls outside the band this throws a
|
|
223
|
+
* specific error naming which bound was violated (rather than silently signing
|
|
224
|
+
* a doomed authorization), unless `clamp` is left enabled, in which case the
|
|
225
|
+
* pinned value is clamped into the band like the computed one.
|
|
206
226
|
*/
|
|
207
227
|
declare function computePaymentValidityWindow(params: {
|
|
208
228
|
/** The challenge's expires_at, unix seconds. */challengeExpiresAtSec: number; /** Current time, unix seconds. */
|
|
@@ -210,6 +230,27 @@ declare function computePaymentValidityWindow(params: {
|
|
|
210
230
|
settlementMarginSec?: number; /** How far in the past to set validAfter for clock skew. Default 5 min. */
|
|
211
231
|
clockSkewSec?: number; /** Hard ceiling on validBefore - validAfter. Default 24h. */
|
|
212
232
|
maxWindowSec?: number;
|
|
233
|
+
/**
|
|
234
|
+
* Minimum `validBefore - nowSec`. Default 60s. A signature with less headroom
|
|
235
|
+
* cannot clear the SMTP+DKIM+settle latency and the platform rejects it.
|
|
236
|
+
*/
|
|
237
|
+
minHeadroomSec?: number;
|
|
238
|
+
/**
|
|
239
|
+
* Explicit override for `validBefore` (unix seconds). When omitted it is
|
|
240
|
+
* derived from `challengeExpiresAtSec + settlementMarginSec`.
|
|
241
|
+
*/
|
|
242
|
+
validBeforeSec?: number;
|
|
243
|
+
/**
|
|
244
|
+
* Explicit override for `validAfter` (unix seconds). When omitted it is
|
|
245
|
+
* derived from `nowSec - clockSkewSec`.
|
|
246
|
+
*/
|
|
247
|
+
validAfterSec?: number;
|
|
248
|
+
/**
|
|
249
|
+
* When true (the default), an out-of-band window is clamped into the accepted
|
|
250
|
+
* band instead of throwing. Set `false` to reject a caller-pinned override
|
|
251
|
+
* that is out of band with a specific error rather than silently moving it.
|
|
252
|
+
*/
|
|
253
|
+
clamp?: boolean;
|
|
213
254
|
}): {
|
|
214
255
|
validAfter: bigint;
|
|
215
256
|
validBefore: bigint;
|
|
@@ -401,6 +442,27 @@ declare class X402Error extends Error {
|
|
|
401
442
|
retryAfter?: string | null;
|
|
402
443
|
});
|
|
403
444
|
}
|
|
445
|
+
/**
|
|
446
|
+
* Parse the bytes of an inbound `interaction.json` MIME part into a typed
|
|
447
|
+
* {@link X402EmailChallenge} ready for {@link X402Client.payEmailChallenge}.
|
|
448
|
+
*
|
|
449
|
+
* A payer receives the x402 challenge as an `interaction.json` attachment on an
|
|
450
|
+
* inbound email (filename `interaction.json`, content type `application/json`).
|
|
451
|
+
* This validates the envelope (the strict snake_case wire shape, that it is the
|
|
452
|
+
* `x402.payment` `challenge` step, and that the embedded payload carries the
|
|
453
|
+
* fields a payer signs over) and re-assembles the nonce binding from the
|
|
454
|
+
* envelope's `interaction_id` + `step_id` + the payload's `challenge_nonce`, so
|
|
455
|
+
* the caller never has to hand-parse the part.
|
|
456
|
+
*
|
|
457
|
+
* Accepts the part body as a UTF-8 string, a `Uint8Array`/`Buffer`, or an
|
|
458
|
+
* already-parsed envelope object. Throws {@link X402Error} (status 0) on any
|
|
459
|
+
* malformed or non-challenge part.
|
|
460
|
+
*
|
|
461
|
+
* The resulting `challenge_id` is empty: the platform's private challenge id is
|
|
462
|
+
* not carried on the wire, and `payEmailChallenge` does not need it (it binds to
|
|
463
|
+
* the `interaction_id` and the challenge step id).
|
|
464
|
+
*/
|
|
465
|
+
declare function parseEmailChallengeFromPart(part: string | Uint8Array | Record<string, unknown>): X402EmailChallenge;
|
|
404
466
|
interface X402ClientOptions {
|
|
405
467
|
/** API key. Defaults to `process.env.PRIMITIVE_API_KEY`. */
|
|
406
468
|
apiKey?: string;
|
|
@@ -486,4 +548,4 @@ declare class X402Client {
|
|
|
486
548
|
}
|
|
487
549
|
declare function createX402Client(options?: X402ClientOptions): X402Client;
|
|
488
550
|
//#endregion
|
|
489
|
-
export { BuiltPaymentStep, DEFAULT_MAX_WINDOW_SEC, InteractionEnvelope, NonceBinding, PayoutRegistrationMessageInput, TRANSFER_WITH_AUTHORIZATION_TYPES, TokenDomain, TransferAuthorization, TransferWithAuthorizationTypedData, X402Challenge, X402ChargeInput, X402Client, X402ClientOptions, X402DeclinedPayment, X402EmailChallenge, X402EmailChallengeDetails, X402EmailChargeInput, X402Error, X402Network, X402NonceBinding, X402PaymentPayload, X402PaymentRequirements, X402PaymentStepPayload, X402PayoutAddress, X402Receipt, X402Signer, X402SpendPolicy, X402_INTERACTION_PROTOCOL, X402_INTERACTION_PROTOCOL_VERSION, buildExactEvmPaymentPayload, buildPaymentStepEnvelope, buildPayoutRegistrationMessage, computePaymentValidityWindow, createX402Client, deriveEip3009Nonce, signInteractionPayment, toPaymentPayload, transferWithAuthorizationTypedData };
|
|
551
|
+
export { BuiltPaymentStep, DEFAULT_MAX_WINDOW_SEC, DEFAULT_MIN_SETTLEMENT_HEADROOM_SEC, InteractionEnvelope, NonceBinding, PayoutRegistrationMessageInput, TRANSFER_WITH_AUTHORIZATION_TYPES, TokenDomain, TransferAuthorization, TransferWithAuthorizationTypedData, X402Challenge, X402ChargeInput, X402Client, X402ClientOptions, X402DeclinedPayment, X402EmailChallenge, X402EmailChallengeDetails, X402EmailChargeInput, X402Error, X402Network, X402NonceBinding, X402PaymentPayload, X402PaymentRequirements, X402PaymentStepPayload, X402PayoutAddress, X402Receipt, X402Signer, X402SpendPolicy, X402_INTERACTION_PROTOCOL, X402_INTERACTION_PROTOCOL_VERSION, buildExactEvmPaymentPayload, buildPaymentStepEnvelope, buildPayoutRegistrationMessage, computePaymentValidityWindow, createX402Client, deriveEip3009Nonce, parseEmailChallengeFromPart, signInteractionPayment, toPaymentPayload, transferWithAuthorizationTypedData };
|
package/dist/x402/index.js
CHANGED
|
@@ -164,24 +164,55 @@ function toPaymentPayload(network, auth, signature) {
|
|
|
164
164
|
*/
|
|
165
165
|
const DEFAULT_MAX_WINDOW_SEC = 1440 * 60;
|
|
166
166
|
/**
|
|
167
|
-
*
|
|
168
|
-
*
|
|
169
|
-
*
|
|
170
|
-
*
|
|
171
|
-
*
|
|
172
|
-
|
|
167
|
+
* Minimum headroom between now and `validBefore`. The platform rejects a
|
|
168
|
+
* payment whose authorization is about to expire (it needs SMTP + DKIM + verify
|
|
169
|
+
* + settle latency to clear), so a `validBefore` less than this far in the
|
|
170
|
+
* future is a guaranteed-to-fail signature. The default window is minutes; this
|
|
171
|
+
* 60s floor is the absolute minimum the band tolerates.
|
|
172
|
+
*/
|
|
173
|
+
const DEFAULT_MIN_SETTLEMENT_HEADROOM_SEC = 60;
|
|
174
|
+
/**
|
|
175
|
+
* Compute the EIP-3009 validity window for a payment, landing inside the band
|
|
176
|
+
* the platform accepts. `validBefore` governs on-chain validity, so it MUST
|
|
177
|
+
* stay far enough in the future to settle (>= `minHeadroomSec`) yet not so far
|
|
178
|
+
* that the total window exceeds the `maxWindowSec` cap; `validAfter` is set
|
|
179
|
+
* generously in the past for clock skew.
|
|
180
|
+
*
|
|
181
|
+
* Both ends of that band are payer landmines: a too-tight `validBefore` (low
|
|
182
|
+
* headroom, e.g. a near-expired challenge) is rejected for being about to
|
|
183
|
+
* expire, and a too-wide window (far-future expiry) is rejected as
|
|
184
|
+
* "authorization window too wide". By default this clamps the computed window
|
|
185
|
+
* into the band so a caller who does not override always gets a signable
|
|
186
|
+
* window.
|
|
187
|
+
*
|
|
188
|
+
* If the caller passes an explicit `validBeforeSec` or `validAfterSec`, that is
|
|
189
|
+
* an intent to pin the bound: when it falls outside the band this throws a
|
|
190
|
+
* specific error naming which bound was violated (rather than silently signing
|
|
191
|
+
* a doomed authorization), unless `clamp` is left enabled, in which case the
|
|
192
|
+
* pinned value is clamped into the band like the computed one.
|
|
173
193
|
*/
|
|
174
194
|
function computePaymentValidityWindow(params) {
|
|
175
195
|
const margin = params.settlementMarginSec ?? 300;
|
|
176
196
|
const skew = params.clockSkewSec ?? 300;
|
|
177
|
-
const
|
|
178
|
-
const
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
197
|
+
const maxWindow = params.maxWindowSec ?? 86400;
|
|
198
|
+
const minHeadroom = params.minHeadroomSec ?? 60;
|
|
199
|
+
const clamp = params.clamp ?? true;
|
|
200
|
+
if (maxWindow < minHeadroom) throw new Error(`invalid validity window config: maxWindowSec (${maxWindow}) is smaller than minHeadroomSec (${minHeadroom})`);
|
|
201
|
+
const validAfter = params.validAfterSec !== void 0 ? params.validAfterSec : params.nowSec - skew;
|
|
202
|
+
const rawValidBefore = params.validBeforeSec !== void 0 ? params.validBeforeSec : params.challengeExpiresAtSec + margin;
|
|
203
|
+
const floor = params.nowSec + minHeadroom;
|
|
204
|
+
const ceiling = validAfter + maxWindow;
|
|
205
|
+
const tooTight = rawValidBefore < floor;
|
|
206
|
+
const tooWide = rawValidBefore > ceiling;
|
|
207
|
+
if (!clamp && params.validBeforeSec !== void 0) {
|
|
208
|
+
if (tooTight) throw new Error(`invalid validity window: validBefore (${rawValidBefore}) is below the minimum settlement headroom (must be >= now + ${minHeadroom}s = ${floor}); the authorization would be rejected as about to expire`);
|
|
209
|
+
if (tooWide) throw new Error(`invalid validity window: validBefore (${rawValidBefore}) exceeds the ${maxWindow}s window cap (must be <= validAfter + ${maxWindow}s = ${ceiling}); the authorization window is too wide`);
|
|
210
|
+
}
|
|
211
|
+
const bandedValidBefore = tooTight ? floor : tooWide ? ceiling : rawValidBefore;
|
|
212
|
+
if (bandedValidBefore <= validAfter) throw new Error("invalid validity window: validBefore must be after validAfter (challenge already expired or validAfter pinned too late?)");
|
|
182
213
|
return {
|
|
183
|
-
validAfter,
|
|
184
|
-
validBefore
|
|
214
|
+
validAfter: BigInt(validAfter),
|
|
215
|
+
validBefore: BigInt(bandedValidBefore)
|
|
185
216
|
};
|
|
186
217
|
}
|
|
187
218
|
/**
|
|
@@ -280,6 +311,80 @@ var X402Error = class extends Error {
|
|
|
280
311
|
this.retryAfter = options?.retryAfter ?? null;
|
|
281
312
|
}
|
|
282
313
|
};
|
|
314
|
+
/** A UUID, as carried by `interaction_id`'s local part and the step ids. */
|
|
315
|
+
const EXTRACT_UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
316
|
+
/** An interaction id is `uuid@domain`. */
|
|
317
|
+
const EXTRACT_WIRE_ID_RE = new RegExp(`^${EXTRACT_UUID_RE.source.slice(1, -1)}@[^\\s@]+$`, "i");
|
|
318
|
+
/**
|
|
319
|
+
* Parse the bytes of an inbound `interaction.json` MIME part into a typed
|
|
320
|
+
* {@link X402EmailChallenge} ready for {@link X402Client.payEmailChallenge}.
|
|
321
|
+
*
|
|
322
|
+
* A payer receives the x402 challenge as an `interaction.json` attachment on an
|
|
323
|
+
* inbound email (filename `interaction.json`, content type `application/json`).
|
|
324
|
+
* This validates the envelope (the strict snake_case wire shape, that it is the
|
|
325
|
+
* `x402.payment` `challenge` step, and that the embedded payload carries the
|
|
326
|
+
* fields a payer signs over) and re-assembles the nonce binding from the
|
|
327
|
+
* envelope's `interaction_id` + `step_id` + the payload's `challenge_nonce`, so
|
|
328
|
+
* the caller never has to hand-parse the part.
|
|
329
|
+
*
|
|
330
|
+
* Accepts the part body as a UTF-8 string, a `Uint8Array`/`Buffer`, or an
|
|
331
|
+
* already-parsed envelope object. Throws {@link X402Error} (status 0) on any
|
|
332
|
+
* malformed or non-challenge part.
|
|
333
|
+
*
|
|
334
|
+
* The resulting `challenge_id` is empty: the platform's private challenge id is
|
|
335
|
+
* not carried on the wire, and `payEmailChallenge` does not need it (it binds to
|
|
336
|
+
* the `interaction_id` and the challenge step id).
|
|
337
|
+
*/
|
|
338
|
+
function parseEmailChallengeFromPart(part) {
|
|
339
|
+
const bad = (field) => {
|
|
340
|
+
throw new X402Error(`interaction.json part is not a valid x402 challenge: ${field}`, 0);
|
|
341
|
+
};
|
|
342
|
+
let envelope;
|
|
343
|
+
if (typeof part === "string" || part instanceof Uint8Array) {
|
|
344
|
+
const text = typeof part === "string" ? part : new TextDecoder().decode(part);
|
|
345
|
+
let parsed;
|
|
346
|
+
try {
|
|
347
|
+
parsed = JSON.parse(text);
|
|
348
|
+
} catch (cause) {
|
|
349
|
+
throw new X402Error(`interaction.json part is not valid JSON: ${cause instanceof Error ? cause.message : String(cause)}`, 0, void 0, { cause });
|
|
350
|
+
}
|
|
351
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) bad("envelope (expected a JSON object)");
|
|
352
|
+
envelope = parsed;
|
|
353
|
+
} else if (part && typeof part === "object" && !Array.isArray(part) && !(part instanceof Uint8Array)) envelope = part;
|
|
354
|
+
else return bad("envelope (expected JSON bytes, a string, or an object)");
|
|
355
|
+
if (envelope.interaction_version !== 1) bad("interaction_version (expected 1)");
|
|
356
|
+
const interactionId = envelope.interaction_id;
|
|
357
|
+
if (typeof interactionId !== "string" || !EXTRACT_WIRE_ID_RE.test(interactionId)) bad("interaction_id (expected uuid@domain)");
|
|
358
|
+
if (envelope.protocol !== "x402.payment") bad(`protocol (expected "${X402_INTERACTION_PROTOCOL}")`);
|
|
359
|
+
if (envelope.protocol_version !== 1) bad(`protocol_version (expected 1)`);
|
|
360
|
+
if (envelope.step !== "challenge") bad(`step (expected "challenge", got "${String(envelope.step)}")`);
|
|
361
|
+
const stepId = envelope.step_id;
|
|
362
|
+
if (typeof stepId !== "string" || !EXTRACT_UUID_RE.test(stepId)) bad("step_id (expected a uuid)");
|
|
363
|
+
const expiresAt = envelope.expires_at;
|
|
364
|
+
if (typeof expiresAt !== "string" || !expiresAt) bad("expires_at (expected an ISO-8601 timestamp on the challenge step)");
|
|
365
|
+
const payload = envelope.payload;
|
|
366
|
+
if (!payload || typeof payload !== "object" || Array.isArray(payload)) bad("payload (expected an object)");
|
|
367
|
+
const p = payload;
|
|
368
|
+
const challengeNonce = p.challenge_nonce;
|
|
369
|
+
if (typeof challengeNonce !== "string" || !/^[0-9a-f]{64}$/.test(challengeNonce)) bad("payload.challenge_nonce (expected 64 lowercase hex chars)");
|
|
370
|
+
const paymentRequirements = p.payment_requirements;
|
|
371
|
+
if (!paymentRequirements || typeof paymentRequirements !== "object" || Array.isArray(paymentRequirements)) bad("payload.payment_requirements (expected an object)");
|
|
372
|
+
const challenge = {
|
|
373
|
+
interaction_id: interactionId,
|
|
374
|
+
challenge_id: "",
|
|
375
|
+
challenge: {
|
|
376
|
+
payment_requirements: paymentRequirements,
|
|
377
|
+
nonce_binding: {
|
|
378
|
+
interaction_id: interactionId,
|
|
379
|
+
challenge_step_id: stepId,
|
|
380
|
+
challenge_nonce: challengeNonce
|
|
381
|
+
},
|
|
382
|
+
expires_at: expiresAt
|
|
383
|
+
}
|
|
384
|
+
};
|
|
385
|
+
validateEmailChallenge(challenge);
|
|
386
|
+
return challenge;
|
|
387
|
+
}
|
|
283
388
|
/**
|
|
284
389
|
* Assert a challenge is fully hydrated before signing, so a missing field fails
|
|
285
390
|
* with a named X402Error instead of an opaque viem/BigInt error mid-sign.
|
|
@@ -599,4 +704,4 @@ function createX402Client(options = {}) {
|
|
|
599
704
|
return new X402Client(options);
|
|
600
705
|
}
|
|
601
706
|
//#endregion
|
|
602
|
-
export { DEFAULT_MAX_WINDOW_SEC, TRANSFER_WITH_AUTHORIZATION_TYPES, X402Client, X402Error, X402_INTERACTION_PROTOCOL, X402_INTERACTION_PROTOCOL_VERSION, buildExactEvmPaymentPayload, buildPaymentStepEnvelope, buildPayoutRegistrationMessage, computePaymentValidityWindow, createX402Client, deriveEip3009Nonce, signInteractionPayment, toPaymentPayload, transferWithAuthorizationTypedData };
|
|
707
|
+
export { DEFAULT_MAX_WINDOW_SEC, DEFAULT_MIN_SETTLEMENT_HEADROOM_SEC, TRANSFER_WITH_AUTHORIZATION_TYPES, X402Client, X402Error, X402_INTERACTION_PROTOCOL, X402_INTERACTION_PROTOCOL_VERSION, buildExactEvmPaymentPayload, buildPaymentStepEnvelope, buildPayoutRegistrationMessage, computePaymentValidityWindow, createX402Client, deriveEip3009Nonce, parseEmailChallengeFromPart, signInteractionPayment, toPaymentPayload, transferWithAuthorizationTypedData };
|
package/package.json
CHANGED