@zeroxyz/sdk 0.11.0 → 0.13.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.13.0
8
+
9
+ ### Added
10
+
11
+ - **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)
12
+ - **`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)
13
+ - **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)
14
+ - **`client.wallet.revokeDelegation()` / `client.wallet.reauthorizeDelegation()`** — per-wallet delegation lifecycle (default: the primary wallet). New export `DelegationOptions`. (ZERO-488)
15
+
16
+ ## 0.12.0
17
+
18
+ ### Added
19
+
20
+ - **`exampleRequest` on capability responses** (`capabilities.get()`) — the bare, ready-to-send request body an agent should copy: `.request` unwrapped from the stored `{request,response}` transcript **and** the HTTP-transport envelope, validated against the current `bodySchema`. `null` when there's no usable example or the stored one is stale (missing a now-required field); the raw `example` is retained for the response sample. Prefer `exampleRequest` over `example` when building a fetch body. (ZERO-393, #757)
21
+
22
+ ### Fixed
23
+
24
+ - **MPP session close honors a metered zero receipt** — a no-result metered call now closes the channel at `$0` instead of throwing an orphan `close-failed`. An ambiguous zero close (no explicit metered receipt) still refuses, as before. (#761)
25
+
7
26
  ## 0.11.0
8
27
 
9
28
  ### Added
package/README.md CHANGED
@@ -102,7 +102,7 @@ The SDK refreshes the access token automatically after a `401`, and every refres
102
102
 
103
103
  Endpoints that don't charge pass straight through: a `200` on the first request returns with `payment: null` and `outcome: "success"`. No payment is attempted.
104
104
 
105
- **Building the request — search → get → fetch.** A `search()` result carries the `url` and `method`, so a no-body `GET` capability can go straight to `fetch()`. For anything that takes a request body, call `capabilities.get()` first: its `bodySchema`, `method`, and `headers` are what tell you how to construct the call. Don't guess the body from the search summary.
105
+ **Building the request — search → get → fetch.** A `search()` result carries the `url` and `method`, so a no-body `GET` capability can go straight to `fetch()`. For anything that takes a request body, call `capabilities.get()` first: its `bodySchema`, `method`, and `headers` are what tell you how to construct the call. Don't guess the body from the search summary. When present, **`exampleRequest`** is the single best artifact to copy — the bare, ready-to-send request body from a real successful call, already unwrapped from the stored transcript and validated against the current schema (it's `null` when there's no example or the stored one is stale, so fall back to `bodySchema`). The raw `example` (`{ request, response }`) is retained for the response sample.
106
106
 
107
107
  Each search result also carries a `token` field (format: `z_xxx.N`) — a short server-issued attribution token that encodes the search context and the result's position. Pass it as `capabilityId` to `fetch()` (or as the `id` to `capabilities.get()`) instead of the raw uid/slug; the server uses it to attribute the run back to the originating search for ranking and analytics. Tokens expire when the session rolls, so use them within the same search session.
108
108
 
@@ -133,6 +133,7 @@ const result = await client.fetch(url, {
133
133
  body: JSON.stringify({ city: "tokyo" }),
134
134
  maxPay: "0.01", // USDC ceiling; an over-cap challenge aborts before signing
135
135
  capabilityId: "cap_abc", // attribute the run to a known capability
136
+ walletAddress: "0x…", // session mode: pay from this profile wallet (default: primary)
136
137
  signal: controller.signal,
137
138
  });
138
139
 
@@ -230,20 +231,32 @@ await client.wallet.list(); // every wallet linked to the session user
230
231
  await client.wallet.provision(); // create the user's managed wallet (idempotent)
231
232
 
232
233
  // 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.
234
+ // Pass makePrimary to have it pay for the user's calls from now on, and an
235
+ // optional nickname (owner-private display name, max 64 chars).
234
236
  const imported = await client.wallet.import({
235
237
  privateKey: "0x…",
236
238
  makePrimary: true,
239
+ nickname: "Trading bot",
237
240
  });
238
241
 
239
242
  // Switch which linked wallet is primary, or unlink one. remove() rejects the
240
243
  // primary wallet and any wallet holding a balance — reassign / drain first.
241
244
  await client.wallet.setPrimary({ walletAddress: imported.walletAddress });
242
245
  await client.wallet.remove({ walletAddress: "0x…" });
246
+
247
+ // Nickname a wallet (or clear with null). Nicknames are private to the
248
+ // authenticated user — they never appear on reviews or public surfaces.
249
+ await client.wallet.setNickname({ walletAddress: "0x…", nickname: "Cold storage" });
250
+
251
+ // Revoke / re-grant Zero's signing delegation per wallet (default: primary).
252
+ await client.wallet.revokeDelegation({ walletAddress: "0x…" });
253
+ await client.wallet.reauthorizeDelegation({ walletAddress: "0x…" });
243
254
  ```
244
255
 
245
256
  `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
257
 
258
+ 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).
259
+
247
260
  ## Namespaces
248
261
 
249
262
  Beyond `search()` and `fetch()`, the client groups the rest of the API into resource namespaces:
@@ -262,8 +275,8 @@ const cap = await client.capabilities.get("cap_abc"); // by uid
262
275
  const cap2 = await client.capabilities.get("z_Ab12cd.1"); // by search token
263
276
 
264
277
  // Wallet — balance, one-time funding URL, and (session mode) multi-wallet
265
- // management: list / provision / import / setPrimary / remove. See
266
- // "Wallet & funding" above.
278
+ // management: list / provision / import / setPrimary / remove / setNickname /
279
+ // revokeDelegation / reauthorizeDelegation. See "Wallet & funding" above.
267
280
  const { amount } = await client.wallet.balance();
268
281
  const fundUrl = await client.wallet.fundingUrl({ amount: "10" });
269
282
 
@@ -15,36 +15,40 @@ var crypto$1 = require('crypto');
15
15
  var zod = require('zod');
16
16
 
17
17
  // 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
- });
18
+ var createManagedAccount = (client, address, opts = {}) => {
19
+ const signOptions = {
20
+ // expectedAddress pins verification to this adapter's frozen address.
21
+ expectedAddress: address,
22
+ ...opts.explicitWallet ? { walletAddress: address } : {}
23
+ };
24
+ return accounts.toAccount({
25
+ address,
26
+ async signMessage({ message }) {
27
+ return await client.auth.signMessage({ message }, signOptions);
28
+ },
29
+ async signTypedData(typedData) {
30
+ const td = typedData;
31
+ return await client.auth.signTypedData(
32
+ {
33
+ domain: td.domain,
34
+ types: td.types,
35
+ primaryType: td.primaryType,
36
+ message: td.message
37
+ },
38
+ signOptions
39
+ );
40
+ },
41
+ async signTransaction(transaction, options) {
42
+ const serialize = options?.serializer ?? viem.serializeTransaction;
43
+ const unsigned = await serialize(transaction);
44
+ const signature = await client.auth.signTransaction(
45
+ { unsignedTransaction: unsigned },
46
+ signOptions
47
+ );
48
+ return await serialize(transaction, viem.parseSignature(signature));
49
+ }
50
+ });
51
+ };
48
52
  var USDC_BASE = "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913";
49
53
  var USDC_TEMPO = "0x20c000000000000000000000b9537d11c60e8b50";
50
54
  var TEMPO_CHAIN_ID = 4217;
@@ -591,6 +595,7 @@ var pickSessionCloseAmount = (receipt, openTimeCumulative) => {
591
595
  if (receipt.metered === true) return fromReceipt;
592
596
  return fromReceipt > openTimeCumulative ? fromReceipt : openTimeCumulative;
593
597
  };
598
+ var isMeteredZeroReceipt = (receipt) => receipt?.metered === true && BigInt(receipt.acceptedCumulative) === 0n && BigInt(receipt.spent) === 0n;
594
599
  var raceWithSignal = (p, signal, client) => {
595
600
  if (!signal) return p;
596
601
  if (signal.aborted) {
@@ -640,12 +645,12 @@ var cancelBody = (response) => {
640
645
  } catch {
641
646
  }
642
647
  };
643
- var resolveAccount = async (client, overrideAccount, signal) => {
648
+ var resolveAccount = async (client, overrideAccount, signal, walletAddress) => {
644
649
  if (overrideAccount) return overrideAccount;
645
650
  const credentials = client.getCredentials();
646
651
  if (credentials.kind === "account") return credentials.account;
647
652
  if (credentials.kind === "session") {
648
- return await client.resolveManagedAccount(signal);
653
+ return await client.resolveManagedAccount(signal, walletAddress);
649
654
  }
650
655
  throw new ZeroAuthError(
651
656
  "auth_no_credentials",
@@ -691,7 +696,8 @@ var Payments = class {
691
696
  const account = await resolveAccount(
692
697
  this.client,
693
698
  input.account,
694
- input.signal
699
+ input.signal,
700
+ input.walletAddress
695
701
  );
696
702
  let capturedAmount = null;
697
703
  let capturedNetwork;
@@ -826,7 +832,8 @@ var Payments = class {
826
832
  const account = await resolveAccount(
827
833
  this.client,
828
834
  input.account,
829
- input.signal
835
+ input.signal,
836
+ input.walletAddress
830
837
  );
831
838
  const configuredFetch = buildConfiguredFetch(this.client, {
832
839
  timeoutMs: input.timeoutMs
@@ -1164,7 +1171,7 @@ var Payments = class {
1164
1171
  }
1165
1172
  const receipt = readSessionReceipt(response);
1166
1173
  let closeAmount = pickSessionCloseAmount(receipt, openTimeCumulative);
1167
- if (closeAmount === 0n && channelEntry.cumulativeAmount > 0n) {
1174
+ if (closeAmount === 0n && channelEntry.cumulativeAmount > 0n && !isMeteredZeroReceipt(receipt)) {
1168
1175
  closeAmount = channelEntry.cumulativeAmount;
1169
1176
  }
1170
1177
  const reconcileAmount = receipt && isUintString(receipt.acceptedCumulative) ? receipt.acceptedCumulative : closeAmount.toString();
@@ -1181,7 +1188,7 @@ var Payments = class {
1181
1188
  capturedAmount,
1182
1189
  cause
1183
1190
  });
1184
- if (closeAmount === 0n) {
1191
+ if (closeAmount === 0n && !isMeteredZeroReceipt(receipt)) {
1185
1192
  throw closeFailed(
1186
1193
  `Refusing to sign zero close credential for channel ${channelId} \u2014 neither receipt nor channelEntry produced a nonzero cumulativeAmount. Query escrow directly to recover; check logger for an earlier 'non-bigint cumulativeAmount' warning.`
1187
1194
  );
@@ -1606,7 +1613,8 @@ var recordErrorFetchRun = async (client, opts, result, capabilityIdOverride) =>
1606
1613
  ...fetchOriginForRecord && { fetchOrigin: fetchOriginForRecord },
1607
1614
  ...result.status !== null && { status: result.status },
1608
1615
  latencyMs: result.latencyMs,
1609
- ...opts.requestSchema && { requestSchema: opts.requestSchema }
1616
+ ...opts.requestSchema && { requestSchema: opts.requestSchema },
1617
+ ...opts.walletAddress && !opts.account && { walletAddress: opts.walletAddress }
1610
1618
  });
1611
1619
  result.runId = created.runId;
1612
1620
  delete result.runTrackingSkipped;
@@ -1715,6 +1723,7 @@ var clientFetch = async (client, url, opts = {}) => {
1715
1723
  body: requestBody,
1716
1724
  maxPay: opts.maxPay,
1717
1725
  account: opts.account,
1726
+ ...opts.walletAddress && { walletAddress: opts.walletAddress },
1718
1727
  signal: opts.signal,
1719
1728
  timeoutMs: opts.timeoutMs,
1720
1729
  ...opts.displayCostAmount && {
@@ -1887,6 +1896,10 @@ var clientFetch = async (client, url, opts = {}) => {
1887
1896
  ...opts.capabilityId === void 0 ? { fetchOrigin: "direct_url" } : opts.fetchOrigin && { fetchOrigin: opts.fetchOrigin },
1888
1897
  status: response.status,
1889
1898
  latencyMs,
1899
+ // Attribute to the selected wallet in session mode. Skipped when a
1900
+ // per-call `account` override records the run — that account's own
1901
+ // EIP-191 identity is the attribution.
1902
+ ...opts.walletAddress && !opts.account && { walletAddress: opts.walletAddress },
1890
1903
  ...opts.requestSchema && { requestSchema: opts.requestSchema },
1891
1904
  ...derived?.responseSchema && {
1892
1905
  responseSchema: derived.responseSchema
@@ -1952,7 +1965,7 @@ var clientFetch = async (client, url, opts = {}) => {
1952
1965
 
1953
1966
  // package.json
1954
1967
  var package_default = {
1955
- version: "0.11.0"};
1968
+ version: "0.13.0"};
1956
1969
 
1957
1970
  // src/version.ts
1958
1971
  var SDK_VERSION = package_default.version;
@@ -2442,6 +2455,9 @@ var userWalletDtoSchema = zod.z.object({
2442
2455
  walletAddress: zod.z.string(),
2443
2456
  source: zod.z.enum(["self_custody", "privy_embedded"]),
2444
2457
  isPrimary: zod.z.boolean(),
2458
+ // Owner-chosen display name; null/absent = unnamed (show the address).
2459
+ // Optional so the SDK tolerates servers that predate nicknames.
2460
+ nickname: zod.z.string().nullable().optional(),
2445
2461
  linkedAt: zod.z.union([zod.z.string(), zod.z.date()]).nullable().optional(),
2446
2462
  importedAt: zod.z.union([zod.z.string(), zod.z.date()]).nullable().optional(),
2447
2463
  delegationGranted: zod.z.boolean(),
@@ -2659,7 +2675,10 @@ var Auth = class {
2659
2675
  {
2660
2676
  method: "POST",
2661
2677
  path: "/v1/users/me/sign-message",
2662
- body: { message: normalizeMessage(input.message) }
2678
+ body: {
2679
+ message: normalizeMessage(input.message),
2680
+ ...opts.walletAddress ? { walletAddress: opts.walletAddress } : {}
2681
+ }
2663
2682
  },
2664
2683
  signResponseSchema
2665
2684
  );
@@ -2684,7 +2703,10 @@ var Auth = class {
2684
2703
  {
2685
2704
  method: "POST",
2686
2705
  path: "/v1/users/me/sign-transaction",
2687
- body: { unsignedTransaction: input.unsignedTransaction }
2706
+ body: {
2707
+ unsignedTransaction: input.unsignedTransaction,
2708
+ ...opts.walletAddress ? { walletAddress: opts.walletAddress } : {}
2709
+ }
2688
2710
  },
2689
2711
  signResponseSchema
2690
2712
  );
@@ -2723,7 +2745,10 @@ var Auth = class {
2723
2745
  {
2724
2746
  method: "POST",
2725
2747
  path: "/v1/users/me/sign-typed-data",
2726
- body: { typedData: input }
2748
+ body: {
2749
+ typedData: input,
2750
+ ...opts.walletAddress ? { walletAddress: opts.walletAddress } : {}
2751
+ }
2727
2752
  },
2728
2753
  signResponseSchema
2729
2754
  );
@@ -2842,6 +2867,12 @@ var capabilityResponseSchema = zod.z.object({
2842
2867
  bodySchema: zod.z.record(zod.z.string(), zod.z.unknown()).nullable(),
2843
2868
  responseSchema: zod.z.record(zod.z.string(), zod.z.unknown()).nullable(),
2844
2869
  example: zod.z.object({ request: zod.z.unknown(), response: zod.z.unknown() }).nullable(),
2870
+ // The bare, schema-validated request body to send — `.request` unwrapped
2871
+ // from the transcript envelope and checked against the current bodySchema.
2872
+ // Null when there's no usable example or it's stale vs the schema; `example`
2873
+ // is retained for the response sample. Optional for back-compat with older
2874
+ // servers that don't emit it.
2875
+ exampleRequest: zod.z.record(zod.z.string(), zod.z.unknown()).nullable().optional(),
2845
2876
  tags: zod.z.array(zod.z.string()).nullable(),
2846
2877
  exampleAgentPrompt: zod.z.string().nullable().optional(),
2847
2878
  whatItDoes: zod.z.string().nullable().optional(),
@@ -3094,6 +3125,9 @@ var Runs = class {
3094
3125
  if (params.unreviewed !== void 0) query.unreviewed = params.unreviewed;
3095
3126
  if (params.limit !== void 0) query.limit = params.limit;
3096
3127
  if (params.cursor !== void 0) query.cursor = params.cursor;
3128
+ if (params.walletAddress !== void 0) {
3129
+ query.walletAddress = Array.isArray(params.walletAddress) ? params.walletAddress.join(",") : params.walletAddress;
3130
+ }
3097
3131
  return request(
3098
3132
  this.client,
3099
3133
  {
@@ -3229,6 +3263,50 @@ var Wallet = class {
3229
3263
  },
3230
3264
  userWalletDtoSchema
3231
3265
  );
3266
+ // Re-stamp Zero's signing delegation on a profile wallet (default: the
3267
+ // primary). Session-only. Naming a wallet re-authorizes THAT wallet and
3268
+ // never provisions; the server 404s an address not on the profile.
3269
+ reauthorizeDelegation = (opts = {}) => request(
3270
+ this.client,
3271
+ {
3272
+ method: "POST",
3273
+ path: "/v1/users/me/wallets/provision",
3274
+ ...opts.walletAddress ? { body: { walletAddress: opts.walletAddress } } : {},
3275
+ signal: opts.signal
3276
+ },
3277
+ userWalletDtoSchema
3278
+ );
3279
+ // Revoke Zero's signing delegation on a profile wallet (default: the
3280
+ // primary). Session-only. Managed signing on that wallet 403s until it is
3281
+ // re-authorized. Note: this stamps the server-side gate — removing Zero's
3282
+ // signer cryptographically is owner-authorized and happens in the user's
3283
+ // Privy session (the web profile page does both).
3284
+ revokeDelegation = (opts = {}) => request(
3285
+ this.client,
3286
+ {
3287
+ method: "DELETE",
3288
+ path: "/v1/users/me/wallets/delegation",
3289
+ ...opts.walletAddress ? { query: { walletAddress: opts.walletAddress } } : {},
3290
+ signal: opts.signal
3291
+ },
3292
+ userWalletDtoSchema
3293
+ );
3294
+ // Set, change, or clear (null / empty string) the owner-chosen nickname on
3295
+ // a profile wallet. Session-only. Nicknames are private to the
3296
+ // authenticated user — they never appear on reviews or public surfaces.
3297
+ setNickname = (opts) => request(
3298
+ this.client,
3299
+ {
3300
+ method: "PATCH",
3301
+ path: "/v1/users/me/wallets/nickname",
3302
+ body: {
3303
+ walletAddress: opts.walletAddress,
3304
+ nickname: opts.nickname
3305
+ },
3306
+ signal: opts.signal
3307
+ },
3308
+ userWalletDtoSchema
3309
+ );
3232
3310
  // Link an existing self-custody wallet to the session user by private key.
3233
3311
  // Session-only. 409s when the wallet is already linked (to this or another
3234
3312
  // profile — the error message says which).
@@ -3239,7 +3317,8 @@ var Wallet = class {
3239
3317
  path: "/v1/users/me/wallets/import",
3240
3318
  body: {
3241
3319
  privateKey: opts.privateKey,
3242
- ...opts.makePrimary !== void 0 ? { makePrimary: opts.makePrimary } : {}
3320
+ ...opts.makePrimary !== void 0 ? { makePrimary: opts.makePrimary } : {},
3321
+ ...opts.nickname !== void 0 ? { nickname: opts.nickname } : {}
3243
3322
  },
3244
3323
  signal: opts.signal
3245
3324
  },
@@ -3442,6 +3521,9 @@ var ZeroClient = class _ZeroClient {
3442
3521
  managedAccountInFlight = null;
3443
3522
  managedAccountAC = null;
3444
3523
  managedAccountGen = 0;
3524
+ // Explicitly-selected (non-default) managed wallets, keyed by lowercased
3525
+ // address. Same lifecycle as managedAccount: cleared on clearManagedAccount.
3526
+ managedAccountByAddress = /* @__PURE__ */ new Map();
3445
3527
  // Multi-tenant servers calling `client.fetch(url, { account: tenantAccount })`
3446
3528
  // on every request would otherwise construct a new ZeroClient (with
3447
3529
  // five namespace instances) per call. Keyed by lowercased account
@@ -3663,8 +3745,16 @@ var ZeroClient = class _ZeroClient {
3663
3745
  }
3664
3746
  this.withAccountCache.clear();
3665
3747
  };
3666
- /** @internal — used by payments.pay() in session mode. Cached, dedupes concurrent lookups. */
3667
- resolveManagedAccount = (signal) => {
3748
+ /**
3749
+ * @internal — used by payments.pay() in session mode. Cached, dedupes
3750
+ * concurrent lookups. Pass `walletAddress` to sign as a specific profile
3751
+ * wallet instead of the primary; explicit wallets are cached per address
3752
+ * and their sign requests carry the address so intent and signer agree.
3753
+ */
3754
+ resolveManagedAccount = (signal, walletAddress) => {
3755
+ if (walletAddress) {
3756
+ return this.resolveManagedAccountByAddress(walletAddress, signal);
3757
+ }
3668
3758
  if (this.managedAccount) return Promise.resolve(this.managedAccount);
3669
3759
  if (this.managedAccountInFlight) {
3670
3760
  return this.raceWithSignal(this.managedAccountInFlight, signal);
@@ -3706,6 +3796,42 @@ var ZeroClient = class _ZeroClient {
3706
3796
  });
3707
3797
  return this.raceWithSignal(this.managedAccountInFlight, signal);
3708
3798
  };
3799
+ // Explicit-wallet variant of resolveManagedAccount. No in-flight dedupe:
3800
+ // this is the rare path (per-wallet pay), the resolution is idempotent, and
3801
+ // the per-address cache absorbs repeat calls.
3802
+ resolveManagedAccountByAddress = async (walletAddress, signal) => {
3803
+ if (this.credentials.kind !== "session") {
3804
+ throw new ZeroAuthError(
3805
+ "auth_no_credentials",
3806
+ "resolveManagedAccount() requires session credentials."
3807
+ );
3808
+ }
3809
+ const canonical = viem.getAddress(walletAddress);
3810
+ const cached = this.managedAccountByAddress.get(canonical.toLowerCase());
3811
+ if (cached) return cached;
3812
+ const startedAtGen = this.managedAccountGen;
3813
+ const wallets = await this.auth.wallets({ signal });
3814
+ const match = wallets.find(
3815
+ (w) => w.walletAddress.toLowerCase() === canonical.toLowerCase()
3816
+ );
3817
+ if (!match || match.source !== "privy_embedded") {
3818
+ throw new ZeroAuthError(
3819
+ "auth_error",
3820
+ `Wallet ${canonical} is not a Privy-embedded wallet on this profile. Managed signing can only use profile wallets (see client.wallet.list()).`
3821
+ );
3822
+ }
3823
+ if (this.managedAccountGen !== startedAtGen) {
3824
+ throw new ZeroAuthError(
3825
+ "auth_session_expired",
3826
+ "Session cleared during managed-account resolution. Retry to rebuild against the current session."
3827
+ );
3828
+ }
3829
+ const acc = createManagedAccount(this, canonical, {
3830
+ explicitWallet: true
3831
+ });
3832
+ this.managedAccountByAddress.set(canonical.toLowerCase(), acc);
3833
+ return acc;
3834
+ };
3709
3835
  // Per-caller signal gate: each caller can reject on their own abort
3710
3836
  // without cancelling the shared underlying promise.
3711
3837
  raceWithSignal = (p, signal) => {
@@ -3736,6 +3862,7 @@ var ZeroClient = class _ZeroClient {
3736
3862
  this.managedAccountAC?.abort();
3737
3863
  this.managedAccountInFlight = null;
3738
3864
  this.managedAccountAC = null;
3865
+ this.managedAccountByAddress.clear();
3739
3866
  this.managedAccountGen++;
3740
3867
  };
3741
3868
  /** @internal — Read cached managed account without triggering resolution. */
@@ -3828,5 +3955,5 @@ exports.normalizeTransportEnvelopeRequest = normalizeTransportEnvelopeRequest;
3828
3955
  exports.paymentHasAnchor = paymentHasAnchor;
3829
3956
  exports.tempoChainLabelFromId = tempoChainLabelFromId;
3830
3957
  exports.unwrapTransportEnvelope = unwrapTransportEnvelope;
3831
- //# sourceMappingURL=chunk-HKF4TRC6.cjs.map
3832
- //# sourceMappingURL=chunk-HKF4TRC6.cjs.map
3958
+ //# sourceMappingURL=chunk-2A2ZZ5XR.cjs.map
3959
+ //# sourceMappingURL=chunk-2A2ZZ5XR.cjs.map