@zeroxyz/sdk 0.13.0 → 0.14.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/CHANGELOG.md +10 -0
- package/README.md +4 -3
- package/dist/{chunk-2A2ZZ5XR.cjs → chunk-AM5RDERD.cjs} +98 -25
- package/dist/chunk-AM5RDERD.cjs.map +1 -0
- package/dist/{chunk-CYB4Y3ID.js → chunk-FWHVWIO2.js} +98 -25
- package/dist/chunk-FWHVWIO2.js.map +1 -0
- package/dist/{client-C23Vw9bv.d.cts → client-DY7QGg57.d.cts} +1 -0
- package/dist/{client-C23Vw9bv.d.ts → client-DY7QGg57.d.ts} +1 -0
- package/dist/index.cjs +44 -44
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/testing.cjs +2 -2
- package/dist/testing.d.cts +1 -1
- package/dist/testing.d.ts +1 -1
- package/dist/testing.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-2A2ZZ5XR.cjs.map +0 -1
- package/dist/chunk-CYB4Y3ID.js.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,16 @@ All notable changes to `@zeroxyz/sdk` will be documented in this file.
|
|
|
4
4
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html) — with the pre-1.0 caveat that minor versions may include breaking changes.
|
|
6
6
|
|
|
7
|
+
## 0.14.0
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- **Explicit payment-protocol selection** — `client.fetch(url, { paymentProtocol: "x402" | "mpp" })` forces one advertised rail instead of using the default x402-first negotiation order. Selection is strict: when the requested protocol is absent, the fetch fails without silently falling back. The CLI exposes the same control as `zero fetch --protocol x402|mpp`. (#785)
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
|
|
15
|
+
- **ZeroClick-style x402 identity-then-charge flows** — `client.fetch()` now handles one zero-value identity challenge before exactly one positive-value payment attempt, reparsing the fresh post-identity 402 instead of signing the stale `$0` challenge. `maxPay` still gates the real charge before signing, and any second positive-value challenge is refused before another payment authorization is created. (#785)
|
|
16
|
+
|
|
7
17
|
## 0.13.0
|
|
8
18
|
|
|
9
19
|
### Added
|
package/README.md
CHANGED
|
@@ -95,7 +95,7 @@ The SDK refreshes the access token automatically after a `401`, and every refres
|
|
|
95
95
|
`client.fetch()` is the call you'll reach for most. Given a URL, it:
|
|
96
96
|
|
|
97
97
|
1. Sends the request.
|
|
98
|
-
2. If the server answers `402 Payment Required` (x402 or MPP), reads the payment challenge and pays it.
|
|
98
|
+
2. If the server answers `402 Payment Required` (x402 or MPP), reads the payment challenge and pays it. For x402 endpoints that use a zero-dollar identity challenge before the real charge, it handles one free identity stage followed by exactly one positive-value payment attempt.
|
|
99
99
|
3. Replays the request with proof of payment.
|
|
100
100
|
4. Returns the body, status, and payment metadata together.
|
|
101
101
|
5. Records a run on Zero when you pass a `capabilityId`, so the capability's reliability signal stays current and the call stays reviewable.
|
|
@@ -131,7 +131,8 @@ const result = await client.fetch(url, {
|
|
|
131
131
|
method: "POST",
|
|
132
132
|
headers: { "content-type": "application/json" },
|
|
133
133
|
body: JSON.stringify({ city: "tokyo" }),
|
|
134
|
-
|
|
134
|
+
maxPay: "0.01", // USDC ceiling; an over-cap challenge aborts before signing
|
|
135
|
+
paymentProtocol: "mpp", // optional: force "x402" or "mpp" when both are offered
|
|
135
136
|
capabilityId: "cap_abc", // attribute the run to a known capability
|
|
136
137
|
walletAddress: "0x…", // session mode: pay from this profile wallet (default: primary)
|
|
137
138
|
signal: controller.signal,
|
|
@@ -195,7 +196,7 @@ for (const warning of result.warnings ?? []) {
|
|
|
195
196
|
|
|
196
197
|
### `maxPay`
|
|
197
198
|
|
|
198
|
-
`maxPay` is your hard per-call ceiling, in USDC. The challenge
|
|
199
|
+
`maxPay` is your hard per-call ceiling, in USDC. The single positive-value challenge is checked **before** it is signed; a preceding zero-value identity handshake does not consume the cap. An over-cap challenge aborts with `outcome: "payment_failed"`, and a second positive-value challenge is refused before signing. Set `maxPay` on every paid call — it's the guardrail that keeps a mispriced or hostile endpoint from draining the wallet.
|
|
199
200
|
|
|
200
201
|
To put a human in the loop before any spend, read the price up front, show it to your user, and only call `fetch()` with `maxPay` once they approve. Prefer `capabilities.get()`'s **`pricing`** object when present: `pricing.summary` is the server-resolved display string (render it verbatim — don't re-derive from raw fields), `pricing.kind` classifies the shape (`static | dynamic | session | metered | subscription | free | unknown`), and `pricing.primary.amountUsd` / `depositUsd` give the structured numbers for sizing `maxPay`. `cost.amount` remains on every search result as the raw advertised scalar, but it flattens usage-priced caps — a `dynamic` cap's real cost scales with input.
|
|
201
202
|
|
|
@@ -8,7 +8,6 @@ var client$1 = require('@x402/core/client');
|
|
|
8
8
|
var http = require('@x402/core/http');
|
|
9
9
|
var client = require('@x402/evm/exact/client');
|
|
10
10
|
var signInWithX = require('@x402/extensions/sign-in-with-x');
|
|
11
|
-
var fetch = require('@x402/fetch');
|
|
12
11
|
var mppx = require('mppx');
|
|
13
12
|
var client$2 = require('mppx/client');
|
|
14
13
|
var crypto$1 = require('crypto');
|
|
@@ -700,6 +699,9 @@ var Payments = class {
|
|
|
700
699
|
input.walletAddress
|
|
701
700
|
);
|
|
702
701
|
let capturedAmount = null;
|
|
702
|
+
let pendingAmountUnits = 0n;
|
|
703
|
+
let identityChallengeHandled = false;
|
|
704
|
+
let paidChallengeHandled = false;
|
|
703
705
|
let capturedNetwork;
|
|
704
706
|
let preflightError = null;
|
|
705
707
|
const x402 = client.registerExactEvmScheme(new client$1.x402Client(), { signer: account });
|
|
@@ -728,11 +730,25 @@ var Payments = class {
|
|
|
728
730
|
};
|
|
729
731
|
}
|
|
730
732
|
const rawAmountUnits = BigInt(rawAmount);
|
|
731
|
-
|
|
733
|
+
pendingAmountUnits = rawAmountUnits;
|
|
734
|
+
const challengeAmount = viem.formatUnits(rawAmountUnits, 6);
|
|
735
|
+
if (rawAmountUnits === 0n) {
|
|
736
|
+
if (identityChallengeHandled || paidChallengeHandled) {
|
|
737
|
+
return {
|
|
738
|
+
abort: true,
|
|
739
|
+
reason: "Only one zero-value identity challenge is allowed, and it must precede payment."
|
|
740
|
+
};
|
|
741
|
+
}
|
|
742
|
+
} else if (paidChallengeHandled) {
|
|
743
|
+
return {
|
|
744
|
+
abort: true,
|
|
745
|
+
reason: "A positive-value x402 payment was already attempted for this fetch; refusing a second charge."
|
|
746
|
+
};
|
|
747
|
+
}
|
|
732
748
|
if (maxPayUnits !== null && rawAmountUnits > maxPayUnits) {
|
|
733
749
|
return {
|
|
734
750
|
abort: true,
|
|
735
|
-
reason: `Payment of ${
|
|
751
|
+
reason: `Payment of ${challengeAmount} USDC exceeds maxPay ${input.maxPay}`
|
|
736
752
|
};
|
|
737
753
|
}
|
|
738
754
|
const detected = networkToChain(capturedNetwork);
|
|
@@ -752,9 +768,10 @@ var Payments = class {
|
|
|
752
768
|
}
|
|
753
769
|
if (balance !== null && balance < rawAmountUnits) {
|
|
754
770
|
const have = viem.formatUnits(balance, 6);
|
|
771
|
+
const requiredAmount = viem.formatUnits(rawAmountUnits, 6);
|
|
755
772
|
preflightError = new ZeroInsufficientFundsError(
|
|
756
|
-
`wallet has ${have} USDC on Base, needs ${
|
|
757
|
-
{ have, need:
|
|
773
|
+
`wallet has ${have} USDC on Base, needs ${requiredAmount}.`,
|
|
774
|
+
{ have, need: requiredAmount }
|
|
758
775
|
);
|
|
759
776
|
return { abort: true, reason: preflightError.message };
|
|
760
777
|
}
|
|
@@ -765,18 +782,64 @@ var Payments = class {
|
|
|
765
782
|
const httpClient = new http.x402HTTPClient(x402).onPaymentRequired(
|
|
766
783
|
signInWithX.createSIWxClientHook(account)
|
|
767
784
|
);
|
|
768
|
-
const
|
|
769
|
-
withSynthesizedX402Header(configuredFetch),
|
|
770
|
-
httpClient
|
|
771
|
-
);
|
|
785
|
+
const paymentFetch = withSynthesizedX402Header(configuredFetch);
|
|
772
786
|
let response;
|
|
773
787
|
try {
|
|
774
|
-
|
|
788
|
+
const request2 = new Request(input.url, {
|
|
775
789
|
method: input.method ?? "GET",
|
|
776
790
|
headers: input.headers,
|
|
777
791
|
body: input.body,
|
|
778
792
|
signal: input.signal
|
|
779
793
|
});
|
|
794
|
+
response = await paymentFetch(request2.clone());
|
|
795
|
+
for (let paymentStage = 0; paymentStage < 2; paymentStage++) {
|
|
796
|
+
if (response.status !== 402) break;
|
|
797
|
+
const parseChallenge = async (challengeResponse) => {
|
|
798
|
+
let body;
|
|
799
|
+
try {
|
|
800
|
+
const text = await challengeResponse.clone().text();
|
|
801
|
+
if (text) body = JSON.parse(text);
|
|
802
|
+
} catch {
|
|
803
|
+
}
|
|
804
|
+
return httpClient.getPaymentRequiredResponse(
|
|
805
|
+
(name) => challengeResponse.headers.get(name),
|
|
806
|
+
body
|
|
807
|
+
);
|
|
808
|
+
};
|
|
809
|
+
let paymentRequired = await parseChallenge(response);
|
|
810
|
+
const hookHeaders = await httpClient.handlePaymentRequired(paymentRequired);
|
|
811
|
+
if (hookHeaders) {
|
|
812
|
+
const hookRequest = request2.clone();
|
|
813
|
+
for (const [key, value] of Object.entries(hookHeaders)) {
|
|
814
|
+
hookRequest.headers.set(key, value);
|
|
815
|
+
}
|
|
816
|
+
response = await paymentFetch(hookRequest);
|
|
817
|
+
if (response.status === 402) {
|
|
818
|
+
paymentRequired = await parseChallenge(response);
|
|
819
|
+
} else {
|
|
820
|
+
break;
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
pendingAmountUnits = 0n;
|
|
824
|
+
const paymentPayload = await x402.createPaymentPayload(paymentRequired);
|
|
825
|
+
if (pendingAmountUnits === 0n) {
|
|
826
|
+
identityChallengeHandled = true;
|
|
827
|
+
capturedAmount = "0";
|
|
828
|
+
} else {
|
|
829
|
+
paidChallengeHandled = true;
|
|
830
|
+
capturedAmount = viem.formatUnits(pendingAmountUnits, 6);
|
|
831
|
+
}
|
|
832
|
+
const paymentHeaders = httpClient.encodePaymentSignatureHeader(paymentPayload);
|
|
833
|
+
const paidRequest = request2.clone();
|
|
834
|
+
for (const [key, value] of Object.entries(paymentHeaders)) {
|
|
835
|
+
paidRequest.headers.set(key, value);
|
|
836
|
+
}
|
|
837
|
+
paidRequest.headers.set(
|
|
838
|
+
"Access-Control-Expose-Headers",
|
|
839
|
+
"PAYMENT-RESPONSE,X-PAYMENT-RESPONSE"
|
|
840
|
+
);
|
|
841
|
+
response = await paymentFetch(paidRequest);
|
|
842
|
+
}
|
|
780
843
|
} catch (err) {
|
|
781
844
|
if (preflightError) throw preflightError;
|
|
782
845
|
if (err instanceof ZeroError) throw err;
|
|
@@ -1344,8 +1407,17 @@ var cancelBody2 = (response) => {
|
|
|
1344
1407
|
} catch {
|
|
1345
1408
|
}
|
|
1346
1409
|
};
|
|
1347
|
-
var detectPaymentRequirement = async (response) => {
|
|
1410
|
+
var detectPaymentRequirement = async (response, preferredProtocol) => {
|
|
1348
1411
|
if (response.status !== 402) return null;
|
|
1412
|
+
const wwwAuth = response.headers.get("www-authenticate");
|
|
1413
|
+
const advertisesMpp = wwwAuth ? splitWwwAuthenticateChallenges(wwwAuth).some((challenge) => {
|
|
1414
|
+
const scheme = challenge.trim().split(/\s+/)[0]?.toLowerCase() ?? "";
|
|
1415
|
+
return MPP_SCHEME_TOKENS.has(scheme);
|
|
1416
|
+
}) : false;
|
|
1417
|
+
if (preferredProtocol === "mpp") {
|
|
1418
|
+
cancelBody2(response);
|
|
1419
|
+
return advertisesMpp ? { protocol: "mpp", raw: { "www-authenticate": wwwAuth } } : { protocol: "unknown", raw: { requestedProtocol: "mpp" } };
|
|
1420
|
+
}
|
|
1349
1421
|
const x402Header = response.headers.get("payment-required") ?? response.headers.get("x-payment-required");
|
|
1350
1422
|
if (x402Header) {
|
|
1351
1423
|
cancelBody2(response);
|
|
@@ -1375,15 +1447,13 @@ var detectPaymentRequirement = async (response) => {
|
|
|
1375
1447
|
} catch {
|
|
1376
1448
|
}
|
|
1377
1449
|
}
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
return { protocol: "mpp", raw: { "www-authenticate": wwwAuth } };
|
|
1386
|
-
}
|
|
1450
|
+
if (preferredProtocol === "x402") {
|
|
1451
|
+
cancelBody2(response);
|
|
1452
|
+
return { protocol: "unknown", raw: { requestedProtocol: "x402" } };
|
|
1453
|
+
}
|
|
1454
|
+
if (advertisesMpp) {
|
|
1455
|
+
cancelBody2(response);
|
|
1456
|
+
return { protocol: "mpp", raw: { "www-authenticate": wwwAuth } };
|
|
1387
1457
|
}
|
|
1388
1458
|
cancelBody2(response);
|
|
1389
1459
|
return { protocol: "unknown", raw: {} };
|
|
@@ -1709,7 +1779,10 @@ var clientFetch = async (client, url, opts = {}) => {
|
|
|
1709
1779
|
return result2;
|
|
1710
1780
|
}
|
|
1711
1781
|
if (response.status === 402) {
|
|
1712
|
-
const requirement = await detectPaymentRequirement(
|
|
1782
|
+
const requirement = await detectPaymentRequirement(
|
|
1783
|
+
response,
|
|
1784
|
+
opts.paymentProtocol
|
|
1785
|
+
);
|
|
1713
1786
|
if (requirement?.protocol === "x402" || requirement?.protocol === "mpp") {
|
|
1714
1787
|
const payCall = requirement.protocol === "x402" ? client.payments.pay : client.payments.payMpp;
|
|
1715
1788
|
try {
|
|
@@ -1781,7 +1854,7 @@ var clientFetch = async (client, url, opts = {}) => {
|
|
|
1781
1854
|
} else if (requirement?.protocol === "unknown") {
|
|
1782
1855
|
const result2 = errorFetchResult(
|
|
1783
1856
|
startedAt,
|
|
1784
|
-
"Capability returned 402 with an unrecognized payment protocol. Neither x402 nor MPP markers were detected in the response headers or body.",
|
|
1857
|
+
opts.paymentProtocol ? `Capability did not advertise the requested ${opts.paymentProtocol} payment protocol.` : "Capability returned 402 with an unrecognized payment protocol. Neither x402 nor MPP markers were detected in the response headers or body.",
|
|
1785
1858
|
"unrecognized_402_protocol",
|
|
1786
1859
|
capabilityIdRequested,
|
|
1787
1860
|
"payment_failed",
|
|
@@ -1965,7 +2038,7 @@ var clientFetch = async (client, url, opts = {}) => {
|
|
|
1965
2038
|
|
|
1966
2039
|
// package.json
|
|
1967
2040
|
var package_default = {
|
|
1968
|
-
version: "0.
|
|
2041
|
+
version: "0.14.0"};
|
|
1969
2042
|
|
|
1970
2043
|
// src/version.ts
|
|
1971
2044
|
var SDK_VERSION = package_default.version;
|
|
@@ -3955,5 +4028,5 @@ exports.normalizeTransportEnvelopeRequest = normalizeTransportEnvelopeRequest;
|
|
|
3955
4028
|
exports.paymentHasAnchor = paymentHasAnchor;
|
|
3956
4029
|
exports.tempoChainLabelFromId = tempoChainLabelFromId;
|
|
3957
4030
|
exports.unwrapTransportEnvelope = unwrapTransportEnvelope;
|
|
3958
|
-
//# sourceMappingURL=chunk-
|
|
3959
|
-
//# sourceMappingURL=chunk-
|
|
4031
|
+
//# sourceMappingURL=chunk-AM5RDERD.cjs.map
|
|
4032
|
+
//# sourceMappingURL=chunk-AM5RDERD.cjs.map
|