uvd-x402-sdk 2.76.0 → 2.78.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/dist/backend/index.d.mts +85 -2
- package/dist/backend/index.d.ts +85 -2
- package/dist/backend/index.js +65 -2
- package/dist/backend/index.js.map +1 -1
- package/dist/backend/index.mjs +61 -3
- package/dist/backend/index.mjs.map +1 -1
- package/dist/index.d.mts +1 -3
- package/dist/index.d.ts +1 -3
- package/dist/index.js +71 -2
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +67 -3
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/backend/index.ts +187 -2
- package/src/dx402.ts +7 -2
- package/src/index.ts +7 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "uvd-x402-sdk",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.78.0",
|
|
4
4
|
"description": "x402 Payment SDK - Gasless crypto payments across 25 blockchains via Ultravioleta facilitator. Supports EVM (including Scroll, SKALE Base, Robinhood Chain), Solana, Fogo, Stellar, NEAR, Algorand, Sui, and XRP Ledger. Features: ERC-8004 Trustless Agents, Escrow/Refunds, multi-stablecoin (USDC, EURC, AUSD, PYUSD, USDT, USDG).",
|
|
5
5
|
"author": "Ultravioleta DAO <ultravioletadao@gmail.com>",
|
|
6
6
|
"license": "MIT",
|
package/src/backend/index.ts
CHANGED
|
@@ -579,6 +579,167 @@ export function buildSettleRequest(
|
|
|
579
579
|
};
|
|
580
580
|
}
|
|
581
581
|
|
|
582
|
+
// ----------------------------------------------------------------------------
|
|
583
|
+
// CHOOSING THE ENVELOPE
|
|
584
|
+
// ----------------------------------------------------------------------------
|
|
585
|
+
//
|
|
586
|
+
// Everything above emits ONE envelope and makes the caller pick. That is the
|
|
587
|
+
// whole defect: `FacilitatorClient` picked v1 unconditionally, so a seller whose
|
|
588
|
+
// 402 advertised v2 -- which `createHonoMiddleware` does on its own the moment
|
|
589
|
+
// the accepts carry CAIP-2 ids -- could not call the facilitator at all, and
|
|
590
|
+
// every consumer had to port the v2 body by hand. The functions below make the
|
|
591
|
+
// version a decision rather than a constant.
|
|
592
|
+
|
|
593
|
+
/**
|
|
594
|
+
* Networks are CAIP-2 in v2 (`eip155:8453`) and plain names in v1 (`base`).
|
|
595
|
+
*
|
|
596
|
+
* The colon is the whole test, and it is the same one `create402Response` and
|
|
597
|
+
* `normalizeRequirementForVersion` already use. Note `xrpl-mainnet` has no
|
|
598
|
+
* CAIP-2 form -- the v1 string IS its network id -- so XRPL stays on v1 here,
|
|
599
|
+
* which is correct.
|
|
600
|
+
*/
|
|
601
|
+
function isCaip2Network(network: string): boolean {
|
|
602
|
+
return network.includes(':');
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
/**
|
|
606
|
+
* Derive the v2 `resource` object from v1-shaped requirements.
|
|
607
|
+
*
|
|
608
|
+
* v2 moved `resource` / `description` / `mimeType` out of the requirements and
|
|
609
|
+
* into an object of their own, and the facilitator requires ALL THREE keys:
|
|
610
|
+
* measured 2026-09-03, a `resource` carrying only `url` is a 400.
|
|
611
|
+
*
|
|
612
|
+
* The `??` defaults are not decoration. `PaymentRequirements` types these as
|
|
613
|
+
* required, but a JavaScript caller can still hand over an object without them,
|
|
614
|
+
* and a missing key does not fail with "description is missing" -- it fails with
|
|
615
|
+
* `data did not match any variant of untagged enum VerifyRequestEnvelope`, which
|
|
616
|
+
* names no field. That error is what cost two teams a day.
|
|
617
|
+
*/
|
|
618
|
+
export function toResourceInfoV2(requirements: PaymentRequirements): ResourceInfoV2 {
|
|
619
|
+
return {
|
|
620
|
+
url: requirements.resource,
|
|
621
|
+
description: requirements.description ?? DEFAULT_PAYMENT_DESCRIPTION,
|
|
622
|
+
mimeType: requirements.mimeType ?? DEFAULT_PAYMENT_MIME_TYPE,
|
|
623
|
+
};
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
/**
|
|
627
|
+
* Derive v2 `accepted` requirements from v1-shaped requirements.
|
|
628
|
+
*
|
|
629
|
+
* Two renames do the damage, and neither is reported by name when it is wrong:
|
|
630
|
+
* - `maxAmountRequired` is spelled `amount` in v2.
|
|
631
|
+
* - `network` must be CAIP-2; a plain name inside a v2 body is a 400.
|
|
632
|
+
*
|
|
633
|
+
* `extra` is carried through when present -- it is where the EIP-712 domain
|
|
634
|
+
* `name`/`version` live for tokens the facilitator does not know by address, so
|
|
635
|
+
* dropping it breaks EURC and the bridged USDCs.
|
|
636
|
+
*/
|
|
637
|
+
export function toPaymentRequirementsV2(
|
|
638
|
+
requirements: PaymentRequirements
|
|
639
|
+
): PaymentRequirementsV2 {
|
|
640
|
+
return {
|
|
641
|
+
scheme: requirements.scheme,
|
|
642
|
+
network: isCaip2Network(requirements.network)
|
|
643
|
+
? requirements.network
|
|
644
|
+
: chainToCAIP2(requirements.network),
|
|
645
|
+
asset: requirements.asset,
|
|
646
|
+
amount: requirements.maxAmountRequired,
|
|
647
|
+
payTo: requirements.payTo,
|
|
648
|
+
// Required by the facilitator: omitting it is a 400, measured the same day.
|
|
649
|
+
maxTimeoutSeconds: requirements.maxTimeoutSeconds ?? DEFAULT_PAYMENT_TIMEOUT_SECONDS,
|
|
650
|
+
...(requirements.extra !== undefined ? { extra: requirements.extra } : {}),
|
|
651
|
+
};
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
/**
|
|
655
|
+
* Decide which envelope this (payment, requirements) pair has to travel in.
|
|
656
|
+
*
|
|
657
|
+
* `requested` wins when it names a version; `'auto'` (the default) reads the
|
|
658
|
+
* wire.
|
|
659
|
+
*
|
|
660
|
+
* **Auto keys off CAIP-2, NOT off `paymentHeader.x402Version`,** and that is a
|
|
661
|
+
* measured decision rather than a stylistic one. The facilitator's envelope enum
|
|
662
|
+
* is untagged: it matches on SHAPE and ignores the version marker. Measured
|
|
663
|
+
* against production on 2026-09-03:
|
|
664
|
+
*
|
|
665
|
+
* | payload network | requirements network | v1 envelope today |
|
|
666
|
+
* |-----------------|----------------------|-------------------|
|
|
667
|
+
* | `base` | `base` | **200** |
|
|
668
|
+
* | `base` (header says `x402Version: 2`) | `base` | **200** |
|
|
669
|
+
* | `eip155:8453` | `base` | 400 |
|
|
670
|
+
* | `base` | `eip155:8453` | 400 |
|
|
671
|
+
* | `eip155:8453` | `eip155:8453` | 400 (`unknown variant \`eip155:8453\``) |
|
|
672
|
+
*
|
|
673
|
+
* So a header that merely *declares* version 2 while carrying plain names is
|
|
674
|
+
* being served correctly today. Upgrading it on the strength of the marker would
|
|
675
|
+
* change a call that works -- the one thing this must not do. Every CAIP-2
|
|
676
|
+
* combination, by contrast, is already a hard 400, so switching those to v2
|
|
677
|
+
* cannot regress anyone: it can only turn a failure into a payment.
|
|
678
|
+
*/
|
|
679
|
+
export function resolveEnvelopeVersion(
|
|
680
|
+
paymentHeader: X402Header,
|
|
681
|
+
requirements: PaymentRequirements,
|
|
682
|
+
requested: X402Version | 'auto' = 'auto'
|
|
683
|
+
): X402Version {
|
|
684
|
+
if (requested !== 'auto') {
|
|
685
|
+
return requested;
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
return isCaip2Network(paymentHeader.network) || isCaip2Network(requirements.network)
|
|
689
|
+
? 2
|
|
690
|
+
: 1;
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
/**
|
|
694
|
+
* Build a `/verify` body in whichever envelope `version` names.
|
|
695
|
+
*
|
|
696
|
+
* The v1 return is byte-for-byte what {@link buildVerifyRequest} produces, so
|
|
697
|
+
* pinning `1` is exactly today's behaviour.
|
|
698
|
+
*
|
|
699
|
+
* @example
|
|
700
|
+
* ```ts
|
|
701
|
+
* const version = resolveEnvelopeVersion(payment, requirements);
|
|
702
|
+
* const body = buildVerifyRequestForVersion(payment, requirements, version);
|
|
703
|
+
* ```
|
|
704
|
+
*/
|
|
705
|
+
export function buildVerifyRequestForVersion(
|
|
706
|
+
paymentHeader: X402Header,
|
|
707
|
+
requirements: PaymentRequirements,
|
|
708
|
+
version: X402Version
|
|
709
|
+
): VerifyRequest | VerifyRequestV2 {
|
|
710
|
+
if (version === 2) {
|
|
711
|
+
return buildVerifyRequestV2(
|
|
712
|
+
paymentHeader.payload,
|
|
713
|
+
toResourceInfoV2(requirements),
|
|
714
|
+
toPaymentRequirementsV2(requirements)
|
|
715
|
+
);
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
return buildVerifyRequest(paymentHeader, requirements);
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
/**
|
|
722
|
+
* Build a `/settle` body in whichever envelope `version` names.
|
|
723
|
+
*
|
|
724
|
+
* See {@link buildVerifyRequestForVersion} -- `/settle` takes the same body as
|
|
725
|
+
* `/verify` in both versions.
|
|
726
|
+
*/
|
|
727
|
+
export function buildSettleRequestForVersion(
|
|
728
|
+
paymentHeader: X402Header,
|
|
729
|
+
requirements: PaymentRequirements,
|
|
730
|
+
version: X402Version
|
|
731
|
+
): SettleRequest | SettleRequestV2 {
|
|
732
|
+
if (version === 2) {
|
|
733
|
+
return buildSettleRequestV2(
|
|
734
|
+
paymentHeader.payload,
|
|
735
|
+
toResourceInfoV2(requirements),
|
|
736
|
+
toPaymentRequirementsV2(requirements)
|
|
737
|
+
);
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
return buildSettleRequest(paymentHeader, requirements);
|
|
741
|
+
}
|
|
742
|
+
|
|
582
743
|
// ============================================================================
|
|
583
744
|
// CORS CONFIGURATION
|
|
584
745
|
// ============================================================================
|
|
@@ -661,6 +822,16 @@ export interface FacilitatorClientOptions {
|
|
|
661
822
|
* -- is never replayed here, at any setting.
|
|
662
823
|
*/
|
|
663
824
|
retries?: number;
|
|
825
|
+
/**
|
|
826
|
+
* Which envelope to send to `/verify` and `/settle`. Default `'auto'`.
|
|
827
|
+
*
|
|
828
|
+
* `'auto'` reads the wire: CAIP-2 networks get the v2 envelope, plain names
|
|
829
|
+
* get v1. See {@link resolveEnvelopeVersion} for the measurements behind that
|
|
830
|
+
* rule. Pin `1` or `2` to take the decision yourself -- a pin is honoured
|
|
831
|
+
* even when it contradicts the wire, because choosing the version is the
|
|
832
|
+
* point of the option.
|
|
833
|
+
*/
|
|
834
|
+
x402Version?: X402Version | 'auto';
|
|
664
835
|
}
|
|
665
836
|
|
|
666
837
|
/**
|
|
@@ -688,12 +859,14 @@ export class FacilitatorClient {
|
|
|
688
859
|
private readonly timeout: number;
|
|
689
860
|
private readonly explicitTimeout: boolean;
|
|
690
861
|
private readonly retries: number | undefined;
|
|
862
|
+
private readonly x402Version: X402Version | 'auto';
|
|
691
863
|
|
|
692
864
|
constructor(options: FacilitatorClientOptions = {}) {
|
|
693
865
|
this.baseUrl = options.baseUrl || 'https://facilitator.ultravioletadao.xyz';
|
|
694
866
|
this.explicitTimeout = options.timeout !== undefined;
|
|
695
867
|
this.timeout = options.timeout || 30000;
|
|
696
868
|
this.retries = options.retries;
|
|
869
|
+
this.x402Version = options.x402Version ?? 'auto';
|
|
697
870
|
}
|
|
698
871
|
|
|
699
872
|
/**
|
|
@@ -726,7 +899,15 @@ export class FacilitatorClient {
|
|
|
726
899
|
paymentHeader: X402Header,
|
|
727
900
|
requirements: PaymentRequirements
|
|
728
901
|
): Promise<VerifyResponse> {
|
|
729
|
-
|
|
902
|
+
// Not `buildVerifyRequest` any more. That one only speaks v1, so a seller
|
|
903
|
+
// advertising CAIP-2 -- which this SDK's own Hono middleware does by itself
|
|
904
|
+
// -- could not reach the facilitator at all: the body came back 400 with
|
|
905
|
+
// `unknown variant \`eip155:8453\``, and the buyer saw a broken checkout.
|
|
906
|
+
const body = buildVerifyRequestForVersion(
|
|
907
|
+
paymentHeader,
|
|
908
|
+
requirements,
|
|
909
|
+
resolveEnvelopeVersion(paymentHeader, requirements, this.x402Version)
|
|
910
|
+
);
|
|
730
911
|
|
|
731
912
|
try {
|
|
732
913
|
const { response, error } = await facilitatorFetch(
|
|
@@ -780,7 +961,11 @@ export class FacilitatorClient {
|
|
|
780
961
|
paymentHeader: X402Header,
|
|
781
962
|
requirements: PaymentRequirements
|
|
782
963
|
): Promise<SettleResponse> {
|
|
783
|
-
const body =
|
|
964
|
+
const body = buildSettleRequestForVersion(
|
|
965
|
+
paymentHeader,
|
|
966
|
+
requirements,
|
|
967
|
+
resolveEnvelopeVersion(paymentHeader, requirements, this.x402Version)
|
|
968
|
+
);
|
|
784
969
|
const settleTimeout = this.getTimeout(requirements.network);
|
|
785
970
|
|
|
786
971
|
try {
|
package/src/dx402.ts
CHANGED
|
@@ -1000,11 +1000,10 @@ export function sellerDigestFor(
|
|
|
1000
1000
|
/**
|
|
1001
1001
|
* Largest `POST /dx402/anchor` request the facilitator accepts, mirroring its
|
|
1002
1002
|
* `MAX_REQUEST_BODY_BYTES` (default 64 KiB, an anti-OOM bound on every route).
|
|
1003
|
-
* After base64 inflation and ~600 bytes of metadata this leaves ~47 KB of
|
|
1004
|
-
* plaintext.
|
|
1005
1003
|
*/
|
|
1006
1004
|
export const ANCHOR_MAX_REQUEST_BYTES = 64 * 1024;
|
|
1007
1005
|
|
|
1006
|
+
|
|
1008
1007
|
/** base64 without spreading the array into arguments -- a large blob would
|
|
1009
1008
|
* otherwise overflow the call stack and surface as a generic failure. */
|
|
1010
1009
|
function toBase64(bytes: Uint8Array): string {
|
|
@@ -1075,6 +1074,12 @@ export async function anchorEvidence(
|
|
|
1075
1074
|
// `sealed` -> it hosts and derives the pointer; `pointer` alone -> it uses
|
|
1076
1075
|
// yours; neither -> error (x402-rs `dx402/service.rs`).
|
|
1077
1076
|
...(pointer !== undefined ? { pointer } : { sealed: toBase64(blob) }),
|
|
1077
|
+
// Declared, not measured. Since x402-rs 2.3.0 the facilitator records
|
|
1078
|
+
// the store that ACTUALLY took the bytes and returns that, so a response
|
|
1079
|
+
// may name a different backend than the request did -- `ipfs` where you
|
|
1080
|
+
// said `s3`, because a deployment whose primary is Pinata wrote there.
|
|
1081
|
+
// That is the facilitator correcting the record, not a mismatch to
|
|
1082
|
+
// assert on.
|
|
1078
1083
|
backend: opts.backend ?? (pointer !== undefined ? backendForPointer(pointer) : 's3'),
|
|
1079
1084
|
contentHash: hash,
|
|
1080
1085
|
keyAlg: opts.payerKey.length === 32 ? 'ECIES-X25519' : 'ECIES-secp256k1',
|
package/src/index.ts
CHANGED
|
@@ -376,6 +376,13 @@ export {
|
|
|
376
376
|
buildSettleRequest,
|
|
377
377
|
buildVerifyRequestV2,
|
|
378
378
|
buildSettleRequestV2,
|
|
379
|
+
// Choosing the envelope instead of assuming v1. `FacilitatorClient` now does
|
|
380
|
+
// this on its own; these are for callers that build the body themselves.
|
|
381
|
+
resolveEnvelopeVersion,
|
|
382
|
+
buildVerifyRequestForVersion,
|
|
383
|
+
buildSettleRequestForVersion,
|
|
384
|
+
toResourceInfoV2,
|
|
385
|
+
toPaymentRequirementsV2,
|
|
379
386
|
getCorsHeaders,
|
|
380
387
|
X402_CORS_HEADERS,
|
|
381
388
|
X402_HEADER_NAMES,
|