@zeroxyz/sdk 0.12.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 CHANGED
@@ -4,6 +4,25 @@ 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
+
17
+ ## 0.13.0
18
+
19
+ ### Added
20
+
21
+ - **Pay and sign from a non-primary wallet** — `client.fetch()`, `payments.pay()` / `payMpp()`, and `client.auth.signMessage/signTransaction/signTypedData` accept an optional `walletAddress` (session mode). The SDK resolves the named profile wallet for the payment intent's `from`, the managed signature, and the recorded run's attribution (`runs.create` gained the same optional `walletAddress`), so per-wallet run filters reflect which wallet acted. The wallet must be on the caller's profile. Default behavior (primary wallet) is unchanged. (ZERO-488)
22
+ - **`client.runs.list({ walletAddress })`** — narrow session run reads to specific profile wallets (string or string[]). Default remains all profile wallets; an address not on the profile is a server-side 400, never a silent intersection. (ZERO-488)
23
+ - **Wallet nicknames** — `client.wallet.setNickname({ walletAddress, nickname })` (null clears), optional `nickname` on `client.wallet.import()`, and `UserWalletDto.nickname`. Nicknames are private to the authenticated user. New export `SetNicknameOptions`. (ZERO-488)
24
+ - **`client.wallet.revokeDelegation()` / `client.wallet.reauthorizeDelegation()`** — per-wallet delegation lifecycle (default: the primary wallet). New export `DelegationOptions`. (ZERO-488)
25
+
7
26
  ## 0.12.0
8
27
 
9
28
  ### 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,8 +131,10 @@ 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
- maxPay: "0.01", // USDC ceiling; an over-cap challenge aborts before signing
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
137
+ walletAddress: "0x…", // session mode: pay from this profile wallet (default: primary)
136
138
  signal: controller.signal,
137
139
  });
138
140
 
@@ -194,7 +196,7 @@ for (const warning of result.warnings ?? []) {
194
196
 
195
197
  ### `maxPay`
196
198
 
197
- `maxPay` is your hard per-call ceiling, in USDC. The challenge amount is checked against it **before** anything is signed; an over-cap challenge aborts with `outcome: "payment_failed"` and no on-chain spend. Set it on every paid call — it's the guardrail that keeps a mispriced or hostile endpoint from draining the wallet.
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.
198
200
 
199
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.
200
202
 
@@ -230,20 +232,32 @@ await client.wallet.list(); // every wallet linked to the session user
230
232
  await client.wallet.provision(); // create the user's managed wallet (idempotent)
231
233
 
232
234
  // Link an existing self-custody wallet by private key (0x prefix optional).
233
- // Pass makePrimary to have it pay for the user's calls from now on.
235
+ // Pass makePrimary to have it pay for the user's calls from now on, and an
236
+ // optional nickname (owner-private display name, max 64 chars).
234
237
  const imported = await client.wallet.import({
235
238
  privateKey: "0x…",
236
239
  makePrimary: true,
240
+ nickname: "Trading bot",
237
241
  });
238
242
 
239
243
  // Switch which linked wallet is primary, or unlink one. remove() rejects the
240
244
  // primary wallet and any wallet holding a balance — reassign / drain first.
241
245
  await client.wallet.setPrimary({ walletAddress: imported.walletAddress });
242
246
  await client.wallet.remove({ walletAddress: "0x…" });
247
+
248
+ // Nickname a wallet (or clear with null). Nicknames are private to the
249
+ // authenticated user — they never appear on reviews or public surfaces.
250
+ await client.wallet.setNickname({ walletAddress: "0x…", nickname: "Cold storage" });
251
+
252
+ // Revoke / re-grant Zero's signing delegation per wallet (default: primary).
253
+ await client.wallet.revokeDelegation({ walletAddress: "0x…" });
254
+ await client.wallet.reauthorizeDelegation({ walletAddress: "0x…" });
243
255
  ```
244
256
 
245
257
  `wallet.address()` covers the common case (resolve — or provision — the primary managed wallet); reach for the methods above when you need the full wallet objects, explicit provisioning, or multi-wallet management. Imported wallets carry an `importedAt` timestamp on the returned wallet objects.
246
258
 
259
+ To **pay from a non-primary wallet**, pass `walletAddress` on the call itself — `client.fetch(url, { walletAddress })` (or `payments.pay` / `payMpp`) resolves the named profile wallet for the payment intent's `from`, the managed signature, and the recorded run's attribution, and `client.auth.sign*` accept `{ walletAddress }` too. The wallet must be a delegated Privy-embedded wallet on the session user's profile. Runs can likewise be filtered per wallet: `client.runs.list({ walletAddress: ["0x…"] })` (default remains all profile wallets; an address not on the profile is a 400).
260
+
247
261
  ## Namespaces
248
262
 
249
263
  Beyond `search()` and `fetch()`, the client groups the rest of the API into resource namespaces:
@@ -262,8 +276,8 @@ const cap = await client.capabilities.get("cap_abc"); // by uid
262
276
  const cap2 = await client.capabilities.get("z_Ab12cd.1"); // by search token
263
277
 
264
278
  // Wallet — balance, one-time funding URL, and (session mode) multi-wallet
265
- // management: list / provision / import / setPrimary / remove. See
266
- // "Wallet & funding" above.
279
+ // management: list / provision / import / setPrimary / remove / setNickname /
280
+ // revokeDelegation / reauthorizeDelegation. See "Wallet & funding" above.
267
281
  const { amount } = await client.wallet.balance();
268
282
  const fundUrl = await client.wallet.fundingUrl({ amount: "10" });
269
283
 
@@ -8,43 +8,46 @@ 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');
15
14
  var zod = require('zod');
16
15
 
17
16
  // src/auth/managed-account.ts
18
- var createManagedAccount = (client, address) => accounts.toAccount({
19
- address,
20
- async signMessage({ message }) {
21
- return await client.auth.signMessage(
22
- { message },
23
- { expectedAddress: address }
24
- );
25
- },
26
- async signTypedData(typedData) {
27
- const td = typedData;
28
- return await client.auth.signTypedData(
29
- {
30
- domain: td.domain,
31
- types: td.types,
32
- primaryType: td.primaryType,
33
- message: td.message
34
- },
35
- { expectedAddress: address }
36
- );
37
- },
38
- async signTransaction(transaction, options) {
39
- const serialize = options?.serializer ?? viem.serializeTransaction;
40
- const unsigned = await serialize(transaction);
41
- const signature = await client.auth.signTransaction(
42
- { unsignedTransaction: unsigned },
43
- { expectedAddress: address }
44
- );
45
- return await serialize(transaction, viem.parseSignature(signature));
46
- }
47
- });
17
+ var createManagedAccount = (client, address, opts = {}) => {
18
+ const signOptions = {
19
+ // expectedAddress pins verification to this adapter's frozen address.
20
+ expectedAddress: address,
21
+ ...opts.explicitWallet ? { walletAddress: address } : {}
22
+ };
23
+ return accounts.toAccount({
24
+ address,
25
+ async signMessage({ message }) {
26
+ return await client.auth.signMessage({ message }, signOptions);
27
+ },
28
+ async signTypedData(typedData) {
29
+ const td = typedData;
30
+ return await client.auth.signTypedData(
31
+ {
32
+ domain: td.domain,
33
+ types: td.types,
34
+ primaryType: td.primaryType,
35
+ message: td.message
36
+ },
37
+ signOptions
38
+ );
39
+ },
40
+ async signTransaction(transaction, options) {
41
+ const serialize = options?.serializer ?? viem.serializeTransaction;
42
+ const unsigned = await serialize(transaction);
43
+ const signature = await client.auth.signTransaction(
44
+ { unsignedTransaction: unsigned },
45
+ signOptions
46
+ );
47
+ return await serialize(transaction, viem.parseSignature(signature));
48
+ }
49
+ });
50
+ };
48
51
  var USDC_BASE = "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913";
49
52
  var USDC_TEMPO = "0x20c000000000000000000000b9537d11c60e8b50";
50
53
  var TEMPO_CHAIN_ID = 4217;
@@ -641,12 +644,12 @@ var cancelBody = (response) => {
641
644
  } catch {
642
645
  }
643
646
  };
644
- var resolveAccount = async (client, overrideAccount, signal) => {
647
+ var resolveAccount = async (client, overrideAccount, signal, walletAddress) => {
645
648
  if (overrideAccount) return overrideAccount;
646
649
  const credentials = client.getCredentials();
647
650
  if (credentials.kind === "account") return credentials.account;
648
651
  if (credentials.kind === "session") {
649
- return await client.resolveManagedAccount(signal);
652
+ return await client.resolveManagedAccount(signal, walletAddress);
650
653
  }
651
654
  throw new ZeroAuthError(
652
655
  "auth_no_credentials",
@@ -692,9 +695,13 @@ var Payments = class {
692
695
  const account = await resolveAccount(
693
696
  this.client,
694
697
  input.account,
695
- input.signal
698
+ input.signal,
699
+ input.walletAddress
696
700
  );
697
701
  let capturedAmount = null;
702
+ let pendingAmountUnits = 0n;
703
+ let identityChallengeHandled = false;
704
+ let paidChallengeHandled = false;
698
705
  let capturedNetwork;
699
706
  let preflightError = null;
700
707
  const x402 = client.registerExactEvmScheme(new client$1.x402Client(), { signer: account });
@@ -723,11 +730,25 @@ var Payments = class {
723
730
  };
724
731
  }
725
732
  const rawAmountUnits = BigInt(rawAmount);
726
- capturedAmount = viem.formatUnits(rawAmountUnits, 6);
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
+ }
727
748
  if (maxPayUnits !== null && rawAmountUnits > maxPayUnits) {
728
749
  return {
729
750
  abort: true,
730
- reason: `Payment of ${capturedAmount} USDC exceeds maxPay ${input.maxPay}`
751
+ reason: `Payment of ${challengeAmount} USDC exceeds maxPay ${input.maxPay}`
731
752
  };
732
753
  }
733
754
  const detected = networkToChain(capturedNetwork);
@@ -747,9 +768,10 @@ var Payments = class {
747
768
  }
748
769
  if (balance !== null && balance < rawAmountUnits) {
749
770
  const have = viem.formatUnits(balance, 6);
771
+ const requiredAmount = viem.formatUnits(rawAmountUnits, 6);
750
772
  preflightError = new ZeroInsufficientFundsError(
751
- `wallet has ${have} USDC on Base, needs ${capturedAmount}.`,
752
- { have, need: capturedAmount }
773
+ `wallet has ${have} USDC on Base, needs ${requiredAmount}.`,
774
+ { have, need: requiredAmount }
753
775
  );
754
776
  return { abort: true, reason: preflightError.message };
755
777
  }
@@ -760,18 +782,64 @@ var Payments = class {
760
782
  const httpClient = new http.x402HTTPClient(x402).onPaymentRequired(
761
783
  signInWithX.createSIWxClientHook(account)
762
784
  );
763
- const wrappedFetch = fetch.wrapFetchWithPayment(
764
- withSynthesizedX402Header(configuredFetch),
765
- httpClient
766
- );
785
+ const paymentFetch = withSynthesizedX402Header(configuredFetch);
767
786
  let response;
768
787
  try {
769
- response = await wrappedFetch(input.url, {
788
+ const request2 = new Request(input.url, {
770
789
  method: input.method ?? "GET",
771
790
  headers: input.headers,
772
791
  body: input.body,
773
792
  signal: input.signal
774
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
+ }
775
843
  } catch (err) {
776
844
  if (preflightError) throw preflightError;
777
845
  if (err instanceof ZeroError) throw err;
@@ -827,7 +895,8 @@ var Payments = class {
827
895
  const account = await resolveAccount(
828
896
  this.client,
829
897
  input.account,
830
- input.signal
898
+ input.signal,
899
+ input.walletAddress
831
900
  );
832
901
  const configuredFetch = buildConfiguredFetch(this.client, {
833
902
  timeoutMs: input.timeoutMs
@@ -1338,8 +1407,17 @@ var cancelBody2 = (response) => {
1338
1407
  } catch {
1339
1408
  }
1340
1409
  };
1341
- var detectPaymentRequirement = async (response) => {
1410
+ var detectPaymentRequirement = async (response, preferredProtocol) => {
1342
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
+ }
1343
1421
  const x402Header = response.headers.get("payment-required") ?? response.headers.get("x-payment-required");
1344
1422
  if (x402Header) {
1345
1423
  cancelBody2(response);
@@ -1369,15 +1447,13 @@ var detectPaymentRequirement = async (response) => {
1369
1447
  } catch {
1370
1448
  }
1371
1449
  }
1372
- const wwwAuth = response.headers.get("www-authenticate");
1373
- if (wwwAuth) {
1374
- const schemes = splitWwwAuthenticateChallenges(wwwAuth).map(
1375
- (challenge) => challenge.trim().split(/\s+/)[0]?.toLowerCase() ?? ""
1376
- );
1377
- if (schemes.some((scheme) => MPP_SCHEME_TOKENS.has(scheme))) {
1378
- cancelBody2(response);
1379
- return { protocol: "mpp", raw: { "www-authenticate": wwwAuth } };
1380
- }
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 } };
1381
1457
  }
1382
1458
  cancelBody2(response);
1383
1459
  return { protocol: "unknown", raw: {} };
@@ -1607,7 +1683,8 @@ var recordErrorFetchRun = async (client, opts, result, capabilityIdOverride) =>
1607
1683
  ...fetchOriginForRecord && { fetchOrigin: fetchOriginForRecord },
1608
1684
  ...result.status !== null && { status: result.status },
1609
1685
  latencyMs: result.latencyMs,
1610
- ...opts.requestSchema && { requestSchema: opts.requestSchema }
1686
+ ...opts.requestSchema && { requestSchema: opts.requestSchema },
1687
+ ...opts.walletAddress && !opts.account && { walletAddress: opts.walletAddress }
1611
1688
  });
1612
1689
  result.runId = created.runId;
1613
1690
  delete result.runTrackingSkipped;
@@ -1702,7 +1779,10 @@ var clientFetch = async (client, url, opts = {}) => {
1702
1779
  return result2;
1703
1780
  }
1704
1781
  if (response.status === 402) {
1705
- const requirement = await detectPaymentRequirement(response);
1782
+ const requirement = await detectPaymentRequirement(
1783
+ response,
1784
+ opts.paymentProtocol
1785
+ );
1706
1786
  if (requirement?.protocol === "x402" || requirement?.protocol === "mpp") {
1707
1787
  const payCall = requirement.protocol === "x402" ? client.payments.pay : client.payments.payMpp;
1708
1788
  try {
@@ -1716,6 +1796,7 @@ var clientFetch = async (client, url, opts = {}) => {
1716
1796
  body: requestBody,
1717
1797
  maxPay: opts.maxPay,
1718
1798
  account: opts.account,
1799
+ ...opts.walletAddress && { walletAddress: opts.walletAddress },
1719
1800
  signal: opts.signal,
1720
1801
  timeoutMs: opts.timeoutMs,
1721
1802
  ...opts.displayCostAmount && {
@@ -1773,7 +1854,7 @@ var clientFetch = async (client, url, opts = {}) => {
1773
1854
  } else if (requirement?.protocol === "unknown") {
1774
1855
  const result2 = errorFetchResult(
1775
1856
  startedAt,
1776
- "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.",
1777
1858
  "unrecognized_402_protocol",
1778
1859
  capabilityIdRequested,
1779
1860
  "payment_failed",
@@ -1888,6 +1969,10 @@ var clientFetch = async (client, url, opts = {}) => {
1888
1969
  ...opts.capabilityId === void 0 ? { fetchOrigin: "direct_url" } : opts.fetchOrigin && { fetchOrigin: opts.fetchOrigin },
1889
1970
  status: response.status,
1890
1971
  latencyMs,
1972
+ // Attribute to the selected wallet in session mode. Skipped when a
1973
+ // per-call `account` override records the run — that account's own
1974
+ // EIP-191 identity is the attribution.
1975
+ ...opts.walletAddress && !opts.account && { walletAddress: opts.walletAddress },
1891
1976
  ...opts.requestSchema && { requestSchema: opts.requestSchema },
1892
1977
  ...derived?.responseSchema && {
1893
1978
  responseSchema: derived.responseSchema
@@ -1953,7 +2038,7 @@ var clientFetch = async (client, url, opts = {}) => {
1953
2038
 
1954
2039
  // package.json
1955
2040
  var package_default = {
1956
- version: "0.12.0"};
2041
+ version: "0.14.0"};
1957
2042
 
1958
2043
  // src/version.ts
1959
2044
  var SDK_VERSION = package_default.version;
@@ -2443,6 +2528,9 @@ var userWalletDtoSchema = zod.z.object({
2443
2528
  walletAddress: zod.z.string(),
2444
2529
  source: zod.z.enum(["self_custody", "privy_embedded"]),
2445
2530
  isPrimary: zod.z.boolean(),
2531
+ // Owner-chosen display name; null/absent = unnamed (show the address).
2532
+ // Optional so the SDK tolerates servers that predate nicknames.
2533
+ nickname: zod.z.string().nullable().optional(),
2446
2534
  linkedAt: zod.z.union([zod.z.string(), zod.z.date()]).nullable().optional(),
2447
2535
  importedAt: zod.z.union([zod.z.string(), zod.z.date()]).nullable().optional(),
2448
2536
  delegationGranted: zod.z.boolean(),
@@ -2660,7 +2748,10 @@ var Auth = class {
2660
2748
  {
2661
2749
  method: "POST",
2662
2750
  path: "/v1/users/me/sign-message",
2663
- body: { message: normalizeMessage(input.message) }
2751
+ body: {
2752
+ message: normalizeMessage(input.message),
2753
+ ...opts.walletAddress ? { walletAddress: opts.walletAddress } : {}
2754
+ }
2664
2755
  },
2665
2756
  signResponseSchema
2666
2757
  );
@@ -2685,7 +2776,10 @@ var Auth = class {
2685
2776
  {
2686
2777
  method: "POST",
2687
2778
  path: "/v1/users/me/sign-transaction",
2688
- body: { unsignedTransaction: input.unsignedTransaction }
2779
+ body: {
2780
+ unsignedTransaction: input.unsignedTransaction,
2781
+ ...opts.walletAddress ? { walletAddress: opts.walletAddress } : {}
2782
+ }
2689
2783
  },
2690
2784
  signResponseSchema
2691
2785
  );
@@ -2724,7 +2818,10 @@ var Auth = class {
2724
2818
  {
2725
2819
  method: "POST",
2726
2820
  path: "/v1/users/me/sign-typed-data",
2727
- body: { typedData: input }
2821
+ body: {
2822
+ typedData: input,
2823
+ ...opts.walletAddress ? { walletAddress: opts.walletAddress } : {}
2824
+ }
2728
2825
  },
2729
2826
  signResponseSchema
2730
2827
  );
@@ -3101,6 +3198,9 @@ var Runs = class {
3101
3198
  if (params.unreviewed !== void 0) query.unreviewed = params.unreviewed;
3102
3199
  if (params.limit !== void 0) query.limit = params.limit;
3103
3200
  if (params.cursor !== void 0) query.cursor = params.cursor;
3201
+ if (params.walletAddress !== void 0) {
3202
+ query.walletAddress = Array.isArray(params.walletAddress) ? params.walletAddress.join(",") : params.walletAddress;
3203
+ }
3104
3204
  return request(
3105
3205
  this.client,
3106
3206
  {
@@ -3236,6 +3336,50 @@ var Wallet = class {
3236
3336
  },
3237
3337
  userWalletDtoSchema
3238
3338
  );
3339
+ // Re-stamp Zero's signing delegation on a profile wallet (default: the
3340
+ // primary). Session-only. Naming a wallet re-authorizes THAT wallet and
3341
+ // never provisions; the server 404s an address not on the profile.
3342
+ reauthorizeDelegation = (opts = {}) => request(
3343
+ this.client,
3344
+ {
3345
+ method: "POST",
3346
+ path: "/v1/users/me/wallets/provision",
3347
+ ...opts.walletAddress ? { body: { walletAddress: opts.walletAddress } } : {},
3348
+ signal: opts.signal
3349
+ },
3350
+ userWalletDtoSchema
3351
+ );
3352
+ // Revoke Zero's signing delegation on a profile wallet (default: the
3353
+ // primary). Session-only. Managed signing on that wallet 403s until it is
3354
+ // re-authorized. Note: this stamps the server-side gate — removing Zero's
3355
+ // signer cryptographically is owner-authorized and happens in the user's
3356
+ // Privy session (the web profile page does both).
3357
+ revokeDelegation = (opts = {}) => request(
3358
+ this.client,
3359
+ {
3360
+ method: "DELETE",
3361
+ path: "/v1/users/me/wallets/delegation",
3362
+ ...opts.walletAddress ? { query: { walletAddress: opts.walletAddress } } : {},
3363
+ signal: opts.signal
3364
+ },
3365
+ userWalletDtoSchema
3366
+ );
3367
+ // Set, change, or clear (null / empty string) the owner-chosen nickname on
3368
+ // a profile wallet. Session-only. Nicknames are private to the
3369
+ // authenticated user — they never appear on reviews or public surfaces.
3370
+ setNickname = (opts) => request(
3371
+ this.client,
3372
+ {
3373
+ method: "PATCH",
3374
+ path: "/v1/users/me/wallets/nickname",
3375
+ body: {
3376
+ walletAddress: opts.walletAddress,
3377
+ nickname: opts.nickname
3378
+ },
3379
+ signal: opts.signal
3380
+ },
3381
+ userWalletDtoSchema
3382
+ );
3239
3383
  // Link an existing self-custody wallet to the session user by private key.
3240
3384
  // Session-only. 409s when the wallet is already linked (to this or another
3241
3385
  // profile — the error message says which).
@@ -3246,7 +3390,8 @@ var Wallet = class {
3246
3390
  path: "/v1/users/me/wallets/import",
3247
3391
  body: {
3248
3392
  privateKey: opts.privateKey,
3249
- ...opts.makePrimary !== void 0 ? { makePrimary: opts.makePrimary } : {}
3393
+ ...opts.makePrimary !== void 0 ? { makePrimary: opts.makePrimary } : {},
3394
+ ...opts.nickname !== void 0 ? { nickname: opts.nickname } : {}
3250
3395
  },
3251
3396
  signal: opts.signal
3252
3397
  },
@@ -3449,6 +3594,9 @@ var ZeroClient = class _ZeroClient {
3449
3594
  managedAccountInFlight = null;
3450
3595
  managedAccountAC = null;
3451
3596
  managedAccountGen = 0;
3597
+ // Explicitly-selected (non-default) managed wallets, keyed by lowercased
3598
+ // address. Same lifecycle as managedAccount: cleared on clearManagedAccount.
3599
+ managedAccountByAddress = /* @__PURE__ */ new Map();
3452
3600
  // Multi-tenant servers calling `client.fetch(url, { account: tenantAccount })`
3453
3601
  // on every request would otherwise construct a new ZeroClient (with
3454
3602
  // five namespace instances) per call. Keyed by lowercased account
@@ -3670,8 +3818,16 @@ var ZeroClient = class _ZeroClient {
3670
3818
  }
3671
3819
  this.withAccountCache.clear();
3672
3820
  };
3673
- /** @internal — used by payments.pay() in session mode. Cached, dedupes concurrent lookups. */
3674
- resolveManagedAccount = (signal) => {
3821
+ /**
3822
+ * @internal — used by payments.pay() in session mode. Cached, dedupes
3823
+ * concurrent lookups. Pass `walletAddress` to sign as a specific profile
3824
+ * wallet instead of the primary; explicit wallets are cached per address
3825
+ * and their sign requests carry the address so intent and signer agree.
3826
+ */
3827
+ resolveManagedAccount = (signal, walletAddress) => {
3828
+ if (walletAddress) {
3829
+ return this.resolveManagedAccountByAddress(walletAddress, signal);
3830
+ }
3675
3831
  if (this.managedAccount) return Promise.resolve(this.managedAccount);
3676
3832
  if (this.managedAccountInFlight) {
3677
3833
  return this.raceWithSignal(this.managedAccountInFlight, signal);
@@ -3713,6 +3869,42 @@ var ZeroClient = class _ZeroClient {
3713
3869
  });
3714
3870
  return this.raceWithSignal(this.managedAccountInFlight, signal);
3715
3871
  };
3872
+ // Explicit-wallet variant of resolveManagedAccount. No in-flight dedupe:
3873
+ // this is the rare path (per-wallet pay), the resolution is idempotent, and
3874
+ // the per-address cache absorbs repeat calls.
3875
+ resolveManagedAccountByAddress = async (walletAddress, signal) => {
3876
+ if (this.credentials.kind !== "session") {
3877
+ throw new ZeroAuthError(
3878
+ "auth_no_credentials",
3879
+ "resolveManagedAccount() requires session credentials."
3880
+ );
3881
+ }
3882
+ const canonical = viem.getAddress(walletAddress);
3883
+ const cached = this.managedAccountByAddress.get(canonical.toLowerCase());
3884
+ if (cached) return cached;
3885
+ const startedAtGen = this.managedAccountGen;
3886
+ const wallets = await this.auth.wallets({ signal });
3887
+ const match = wallets.find(
3888
+ (w) => w.walletAddress.toLowerCase() === canonical.toLowerCase()
3889
+ );
3890
+ if (!match || match.source !== "privy_embedded") {
3891
+ throw new ZeroAuthError(
3892
+ "auth_error",
3893
+ `Wallet ${canonical} is not a Privy-embedded wallet on this profile. Managed signing can only use profile wallets (see client.wallet.list()).`
3894
+ );
3895
+ }
3896
+ if (this.managedAccountGen !== startedAtGen) {
3897
+ throw new ZeroAuthError(
3898
+ "auth_session_expired",
3899
+ "Session cleared during managed-account resolution. Retry to rebuild against the current session."
3900
+ );
3901
+ }
3902
+ const acc = createManagedAccount(this, canonical, {
3903
+ explicitWallet: true
3904
+ });
3905
+ this.managedAccountByAddress.set(canonical.toLowerCase(), acc);
3906
+ return acc;
3907
+ };
3716
3908
  // Per-caller signal gate: each caller can reject on their own abort
3717
3909
  // without cancelling the shared underlying promise.
3718
3910
  raceWithSignal = (p, signal) => {
@@ -3743,6 +3935,7 @@ var ZeroClient = class _ZeroClient {
3743
3935
  this.managedAccountAC?.abort();
3744
3936
  this.managedAccountInFlight = null;
3745
3937
  this.managedAccountAC = null;
3938
+ this.managedAccountByAddress.clear();
3746
3939
  this.managedAccountGen++;
3747
3940
  };
3748
3941
  /** @internal — Read cached managed account without triggering resolution. */
@@ -3835,5 +4028,5 @@ exports.normalizeTransportEnvelopeRequest = normalizeTransportEnvelopeRequest;
3835
4028
  exports.paymentHasAnchor = paymentHasAnchor;
3836
4029
  exports.tempoChainLabelFromId = tempoChainLabelFromId;
3837
4030
  exports.unwrapTransportEnvelope = unwrapTransportEnvelope;
3838
- //# sourceMappingURL=chunk-O7424YAR.cjs.map
3839
- //# sourceMappingURL=chunk-O7424YAR.cjs.map
4031
+ //# sourceMappingURL=chunk-AM5RDERD.cjs.map
4032
+ //# sourceMappingURL=chunk-AM5RDERD.cjs.map