@paybox-sh/sdk 0.7.0 → 0.8.1

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.
@@ -43,10 +43,10 @@ function openBrowser(url) {
43
43
  } catch {
44
44
  }
45
45
  }
46
- var CALLBACK_PAGE = '<!doctype html><meta charset=utf-8><title>Paybox</title><body style="font:16px system-ui;padding:3rem;text-align:center"><h2>Paybox CLI connected</h2><p>You can close this tab and return to the terminal.</p>';
46
+ var CALLBACK_PAGE = '<!doctype html><meta charset=utf-8><title>PayBox</title><body style="font:16px system-ui;padding:3rem;text-align:center"><h2>PayBox CLI connected</h2><p>You can close this tab and return to the terminal.</p>';
47
47
  async function loginLoopback(baseUrl, opts = {}) {
48
48
  const scope = opts.scope ?? "mcp offline_access";
49
- const clientName = opts.clientName ?? "Paybox CLI";
49
+ const clientName = opts.clientName ?? "PayBox CLI";
50
50
  const log = opts.log ?? ((m) => console.error(m));
51
51
  const meta = await metadata(baseUrl);
52
52
  const resource = mcpResource(baseUrl);
@@ -126,6 +126,91 @@ async function loginLoopback(baseUrl, opts = {}) {
126
126
  server.close();
127
127
  }
128
128
  }
129
+ async function loginDevice(baseUrl, opts = {}) {
130
+ const scope = opts.scope ?? "mcp offline_access";
131
+ const clientName = opts.clientName ?? "PayBox CLI";
132
+ const log = opts.log ?? ((m) => console.error(m));
133
+ const meta = await metadata(baseUrl);
134
+ if (!meta.device_authorization_endpoint) {
135
+ throw new Error(
136
+ "this Paybox API does not advertise device_authorization_endpoint \u2014 upgrade the API and retry `paybox login`"
137
+ );
138
+ }
139
+ const resource = mcpResource(baseUrl);
140
+ const reg = await fetch(meta.registration_endpoint, {
141
+ method: "POST",
142
+ headers: { "content-type": "application/json" },
143
+ body: JSON.stringify({
144
+ client_name: clientName,
145
+ redirect_uris: ["http://127.0.0.1/device"],
146
+ token_endpoint_auth_method: "none",
147
+ grant_types: ["urn:ietf:params:oauth:grant-type:device_code", "refresh_token"],
148
+ response_types: ["code"],
149
+ // Must match (or be a superset of) what we ask for below — the server
150
+ // only grants scope that was declared at registration.
151
+ scope
152
+ })
153
+ });
154
+ if (!reg.ok) throw new Error(`client registration failed (${reg.status}): ${await reg.text()}`);
155
+ const { client_id: clientId } = await reg.json();
156
+ const started = await fetch(meta.device_authorization_endpoint, {
157
+ method: "POST",
158
+ headers: { "content-type": "application/x-www-form-urlencoded" },
159
+ body: new URLSearchParams({ client_id: clientId, scope, resource })
160
+ });
161
+ if (!started.ok) {
162
+ throw new Error(`device authorization failed (${started.status}): ${await started.text()}`);
163
+ }
164
+ const da = await started.json();
165
+ log(
166
+ `Open this URL on your phone (or this computer) to approve access:
167
+ ${da.verification_uri_complete}
168
+ Code: ${da.user_code}
169
+ `
170
+ );
171
+ openBrowser(da.verification_uri_complete);
172
+ const timeoutMs = opts.timeoutMs ?? da.expires_in * 1e3;
173
+ const deadline = Date.now() + timeoutMs;
174
+ let intervalSec = da.interval && da.interval > 0 ? da.interval : 5;
175
+ while (Date.now() < deadline) {
176
+ await new Promise((r) => setTimeout(r, intervalSec * 1e3));
177
+ const tok = await fetch(meta.token_endpoint, {
178
+ method: "POST",
179
+ headers: { "content-type": "application/x-www-form-urlencoded" },
180
+ body: new URLSearchParams({
181
+ grant_type: "urn:ietf:params:oauth:grant-type:device_code",
182
+ device_code: da.device_code,
183
+ client_id: clientId
184
+ })
185
+ });
186
+ const raw = await tok.text();
187
+ if (tok.ok) {
188
+ const t = JSON.parse(raw);
189
+ return {
190
+ clientId,
191
+ accessToken: t.access_token,
192
+ refreshToken: t.refresh_token,
193
+ expiresAt: t.expires_in ? Date.now() + t.expires_in * 1e3 : void 0,
194
+ resource
195
+ };
196
+ }
197
+ let err = raw || "authorization failed";
198
+ let code = "";
199
+ try {
200
+ const body = JSON.parse(raw);
201
+ code = body.error ?? "";
202
+ err = body.error_description ?? body.error ?? err;
203
+ } catch {
204
+ }
205
+ if (code === "authorization_pending") continue;
206
+ if (code === "slow_down") {
207
+ intervalSec += 5;
208
+ continue;
209
+ }
210
+ throw new Error(err);
211
+ }
212
+ throw new Error(`login timed out after ${Math.round(timeoutMs / 1e3)}s \u2014 no approval received`);
213
+ }
129
214
  async function refreshTokens(baseUrl, current) {
130
215
  if (!current.refreshToken) throw new Error("no refresh token \u2014 run `paybox login` again");
131
216
  const meta = await metadata(baseUrl);
@@ -178,7 +263,7 @@ function saveConfig(config) {
178
263
  if (process.platform !== "win32") chmodSync(configFile(), 384);
179
264
  }
180
265
 
181
- // node_modules/.pnpm/@paybox+mcp-app@file+..+mcp-app_@modelcontextprotocol+sdk@1.29.0_zod@4.4.3__bn.js@5.2.3_ac1e09a20c76a1143611be5dd3d4f9db/node_modules/@paybox/mcp-app/src/creds.ts
266
+ // node_modules/.pnpm/@paybox+mcp-app@file+..+mcp-app_@modelcontextprotocol+sdk@1.29.0_zod@4.4.3__bn.js@5.2.3_ac1e09a20c76a1143611be5dd3d4f9db/node_modules/@paybox/mcp-app/src/signing/creds.ts
182
267
  var TOKEN_PREFIX = "pbxk1.";
183
268
  function validateHex(s, label) {
184
269
  const trimmed = s.trim();
@@ -213,7 +298,7 @@ function parseToken(input) {
213
298
  return { apiPubHex, apiPrivHex };
214
299
  }
215
300
 
216
- // node_modules/.pnpm/@paybox+mcp-app@file+..+mcp-app_@modelcontextprotocol+sdk@1.29.0_zod@4.4.3__bn.js@5.2.3_ac1e09a20c76a1143611be5dd3d4f9db/node_modules/@paybox/mcp-app/src/sign.ts
301
+ // node_modules/.pnpm/@paybox+mcp-app@file+..+mcp-app_@modelcontextprotocol+sdk@1.29.0_zod@4.4.3__bn.js@5.2.3_ac1e09a20c76a1143611be5dd3d4f9db/node_modules/@paybox/mcp-app/src/signing/sign.ts
217
302
  import { Buffer as Buffer2 } from "buffer";
218
303
  import nacl from "tweetnacl";
219
304
  import { MoonXEvmSigner, EvmSchemeMismatchError } from "@moon-x/evm-adapter";
@@ -275,11 +360,11 @@ function solanaSigningPayload(base64Tx) {
275
360
  }
276
361
  function buildSwapEnvelopes(txs, binding, creds) {
277
362
  if (!binding.walletId) {
278
- throw new Error("paybox did not supply a wallet id for this operation.");
363
+ throw new Error("PayBox did not supply a wallet id for this operation.");
279
364
  }
280
365
  if (!binding.keyId || !binding.derivationPath) {
281
366
  throw new Error(
282
- "This wallet isn't provisioned for signing yet (missing key binding). Re-provision it in the paybox app, then retry."
367
+ "This wallet isn't provisioned for signing yet (missing key binding). Re-provision it in the PayBox app, then retry."
283
368
  );
284
369
  }
285
370
  return txs.map((t) => {
@@ -293,11 +378,11 @@ function eip712SigningHash(typedData) {
293
378
  }
294
379
  function buildX402Envelope(typedData, binding, creds) {
295
380
  if (!binding.walletId) {
296
- throw new Error("paybox did not supply a wallet id for this payment.");
381
+ throw new Error("PayBox did not supply a wallet id for this payment.");
297
382
  }
298
383
  if (!binding.keyId || !binding.derivationPath) {
299
384
  throw new Error(
300
- "This wallet isn't provisioned for signing yet (missing key binding). Re-provision it in the paybox app, then retry."
385
+ "This wallet isn't provisioned for signing yet (missing key binding). Re-provision it in the PayBox app, then retry."
301
386
  );
302
387
  }
303
388
  const { signed_body, agent_signature } = buildEnvelope(
@@ -307,6 +392,22 @@ function buildX402Envelope(typedData, binding, creds) {
307
392
  );
308
393
  return { api_pub: creds.apiPubHex, signed_body, agent_signature };
309
394
  }
395
+ function buildX402SolanaEnvelope(transactionBase64, binding, creds) {
396
+ if (!binding.walletId) {
397
+ throw new Error("PayBox did not supply a wallet id for this payment.");
398
+ }
399
+ if (!binding.keyId || !binding.derivationPath) {
400
+ throw new Error(
401
+ "This wallet isn't provisioned for signing yet (missing key binding). Re-provision it in the PayBox app, then retry."
402
+ );
403
+ }
404
+ const { signed_body, agent_signature } = buildEnvelope(
405
+ solanaSigningPayload(transactionBase64),
406
+ binding,
407
+ creds
408
+ );
409
+ return { api_pub: creds.apiPubHex, signed_body, agent_signature };
410
+ }
310
411
  function buildIssuedAtEnvelope(creds) {
311
412
  const signedBodyBytes = new TextEncoder().encode(
312
413
  JSON.stringify({ issued_at: (/* @__PURE__ */ new Date()).toISOString() })
@@ -320,19 +421,19 @@ function buildIssuedAtEnvelope(creds) {
320
421
  }
321
422
  async function signIntent(intent, creds, binding, transport) {
322
423
  if (!binding.walletId) {
323
- return { ok: false, error: "paybox did not supply a wallet id for this operation." };
424
+ return { ok: false, error: "PayBox did not supply a wallet id for this operation." };
324
425
  }
325
426
  if (!binding.keyId || !binding.derivationPath) {
326
427
  return {
327
428
  ok: false,
328
- error: "This wallet isn't provisioned for signing yet (missing key binding). Re-provision it in the paybox app, then retry."
429
+ error: "This wallet isn't provisioned for signing yet (missing key binding). Re-provision it in the PayBox app, then retry."
329
430
  };
330
431
  }
331
432
  const ecdsaSign = async (digest) => {
332
433
  const result = await transport(buildEnvelope(digest, binding, creds));
333
434
  const { r, s, recovery_id } = result.signature;
334
435
  if (r == null || s == null || recovery_id == null) {
335
- throw new Error("MoonX returned an ECDSA result without {r, s, recovery_id}");
436
+ throw new Error("the signing service returned an ECDSA result without {r, s, recovery_id}");
336
437
  }
337
438
  return {
338
439
  r: `0x${r}`,
@@ -343,7 +444,7 @@ async function signIntent(intent, creds, binding, transport) {
343
444
  const ed25519Sign = async (message) => {
344
445
  const result = await transport(buildEnvelope(bytesToHex(message), binding, creds));
345
446
  const sig = result.signature.signature;
346
- if (!sig) throw new Error("MoonX returned an Ed25519 result without a signature");
447
+ if (!sig) throw new Error("the signing service returned an Ed25519 result without a signature");
347
448
  return hexToBytes(sig);
348
449
  };
349
450
  try {
@@ -412,6 +513,11 @@ async function signIntent(intent, creds, binding, transport) {
412
513
 
413
514
  // src/signer.ts
414
515
  function credsFromToken(token) {
516
+ if (token.trim().startsWith("pbx_live_")) {
517
+ throw new Error(
518
+ "invalid signing key: got a `pbx_live_` API key; expected a `pbxk1.` token (keypair only)"
519
+ );
520
+ }
415
521
  const kp = parseToken(token);
416
522
  if (!kp) {
417
523
  throw new Error("invalid signing key: expected a `pbxk1.` token (keypair only)");
@@ -444,21 +550,37 @@ async function signWalletRequest(post, requestId, intent, creds, binding) {
444
550
  function isSolanaRefreshablePlan(plan) {
445
551
  const txs = plan.txs;
446
552
  const solana = txs.some((t) => t.vm === "solana");
447
- return solana && txs.some((t) => t.kind === "swap" || t.kind === "prediction_trade");
553
+ return solana && txs.some(
554
+ (t) => t.kind === "swap" || t.kind === "prediction_trade" || t.kind === "jupiter_swap"
555
+ );
448
556
  }
449
- async function refreshSwapPlan(post, requestId, plan) {
557
+ async function refreshPlan(post, requestId) {
450
558
  const r = await post(`/agent/requests/${requestId}/refresh`, {});
451
559
  if (!r || typeof r.plan !== "object" || r.plan === null) {
452
- throw new Error("swap refresh returned no plan");
560
+ throw new Error("refresh returned no plan");
453
561
  }
454
- const refreshed = r.plan;
562
+ return r.plan;
563
+ }
564
+ async function refreshSwapPlan(post, requestId) {
565
+ const refreshed = await refreshPlan(post, requestId);
455
566
  if (!Array.isArray(refreshed.txs) || refreshed.txs.length === 0) {
456
567
  throw new Error("swap refresh returned a plan with no txs");
457
568
  }
458
569
  return refreshed;
459
570
  }
571
+ function isSolanaRailX402(plan) {
572
+ return plan.x402?.rail === "solana";
573
+ }
574
+ async function refreshX402SolanaTx(post, requestId) {
575
+ const refreshed = await refreshPlan(post, requestId);
576
+ const tx = refreshed.solana?.transaction;
577
+ if (typeof tx !== "string" || tx.length === 0) {
578
+ throw new Error("x402 refresh returned a plan with no Solana transaction");
579
+ }
580
+ return tx;
581
+ }
460
582
  async function signSwapRequest(post, requestId, plan, creds) {
461
- const signPlan = isSolanaRefreshablePlan(plan) ? await refreshSwapPlan(post, requestId, plan) : plan;
583
+ const signPlan = isSolanaRefreshablePlan(plan) ? await refreshSwapPlan(post, requestId) : plan;
462
584
  let binding = {
463
585
  walletId: signPlan.binding.wallet_id,
464
586
  keyId: signPlan.binding.key_id ?? "",
@@ -479,7 +601,7 @@ async function signX402Request(post, requestId, plan, creds) {
479
601
  if (binding.walletId && (!binding.keyId || !binding.derivationPath)) {
480
602
  binding = await resolveBinding(post, requestId, creds);
481
603
  }
482
- const envelope = buildX402Envelope(
604
+ const envelope = isSolanaRailX402(plan) ? buildX402SolanaEnvelope(await refreshX402SolanaTx(post, requestId), binding, creds) : buildX402Envelope(
483
605
  plan.typed_data,
484
606
  binding,
485
607
  creds
@@ -490,6 +612,10 @@ async function signX402Request(post, requestId, plan, creds) {
490
612
  // src/client.ts
491
613
  var DEFAULT_BASE_URL = "https://api.paybox.sh";
492
614
  var EXPIRY_SKEW_MS = 6e4;
615
+ function signingResultForResponse(result, response) {
616
+ const exposesPlan = response.status === "pending_approval" || response.status === "pending_signature";
617
+ return { ...result, response, plan: exposesPlan ? result.plan : null };
618
+ }
493
619
  var PayboxError = class extends Error {
494
620
  constructor(status, body, context) {
495
621
  super(`paybox ${context} failed (${status}): ${body}`);
@@ -525,9 +651,17 @@ var PayboxClient = class _PayboxClient {
525
651
  }
526
652
  return client;
527
653
  }
528
- /** Whether in-process signing is available (a `pbxk1.` key is configured). */
654
+ signingCreds() {
655
+ if (!this.opts.signingKey) return null;
656
+ try {
657
+ return credsFromToken(this.opts.signingKey);
658
+ } catch {
659
+ return null;
660
+ }
661
+ }
662
+ /** Whether in-process signing is available (a parseable `pbxk1.` key is set). */
529
663
  get canSign() {
530
- return Boolean(this.opts.signingKey);
664
+ return this.signingCreds() !== null;
531
665
  }
532
666
  async bearer() {
533
667
  if (this.opts.token) return this.opts.token;
@@ -564,7 +698,8 @@ var PayboxClient = class _PayboxClient {
564
698
  return text ? JSON.parse(text) : void 0;
565
699
  }
566
700
  poster = (path, body) => this.request("POST", path, { body });
567
- /** List the credentials this client may use (the `list_credentials` tool). */
701
+ /** List granted credentials plus the `ungranted` credentials (id, name, kind,
702
+ * safe metadata) the user owns but hasn't granted this connector. */
568
703
  listCredentials() {
569
704
  return this.request("GET", "/agent/grants");
570
705
  }
@@ -574,6 +709,16 @@ var PayboxClient = class _PayboxClient {
574
709
  query: { address: args.address, network_ids: args.networkIds }
575
710
  });
576
711
  }
712
+ /** Verify one confirmed Solana transaction's exact wallet/mint effect, then
713
+ * read the current balance at or after its slot (`verify_solana_balance`). */
714
+ verifySolanaBalance(args) {
715
+ const body = {
716
+ address: args.address,
717
+ token_mint: args.tokenMint,
718
+ transaction_signature: args.transactionSignature
719
+ };
720
+ return this.request("POST", "/agent/portfolio/verify-solana-balance", { body });
721
+ }
577
722
  /** Charge a card credential (the `request_payment` tool). */
578
723
  requestPayment(args) {
579
724
  const operation = {
@@ -599,72 +744,88 @@ var PayboxClient = class _PayboxClient {
599
744
  /** Sign with a wallet credential (the `request_wallet_sign` tool). Signs
600
745
  * in-process and returns the completed request when a signing key is set and
601
746
  * the request clears immediately; otherwise returns the pending response.
602
- * Uses `/agent/wallet-sign`, which echoes the wallet binding so the signer
603
- * need not resolve it separately. */
747
+ * Uses `/agent/wallet-sign`, which echoes the wallet binding only while
748
+ * approval or signature is pending so the signer need not resolve it
749
+ * separately. Terminal responses carry a null binding. */
604
750
  async requestWalletSign(args, opts = {}) {
605
- const operation = {
606
- type: "wallet_sign",
607
- credential_id: args.credentialId,
608
- intent: args.intent
609
- };
610
- const { response, binding } = await this.request(
611
- "POST",
612
- "/agent/wallet-sign",
613
- { body: { operation } }
751
+ return this.withExpiryRetry(
752
+ async () => {
753
+ const operation = {
754
+ type: "wallet_sign",
755
+ credential_id: args.credentialId,
756
+ intent: args.intent
757
+ };
758
+ const { response, binding } = await this.request(
759
+ "POST",
760
+ "/agent/wallet-sign",
761
+ { body: { operation } }
762
+ );
763
+ const creds = this.signingCreds();
764
+ if ((opts.autoSign ?? true) && response.status === "pending_signature" && creds) {
765
+ const sign = () => signWalletRequest(this.poster, response.request_id, args.intent, creds, binding);
766
+ await this.signWithReconcile(response.request_id, sign);
767
+ return this.getRequest(response.request_id);
768
+ }
769
+ return response;
770
+ },
771
+ (r) => r
614
772
  );
615
- if ((opts.autoSign ?? true) && response.status === "pending_signature" && this.opts.signingKey) {
616
- await signWalletRequest(
617
- this.poster,
618
- response.request_id,
619
- args.intent,
620
- credsFromToken(this.opts.signingKey),
621
- binding
622
- );
623
- return this.getRequest(response.request_id);
624
- }
625
- return response;
626
773
  }
627
774
  /** Swap tokens from a wallet credential (the `request_swap` tool). Returns the
628
- * parked response + sign-ready plan; signs + submits in-process when a signing
629
- * key is set and the swap cleared immediately. */
775
+ * parked response and its sign-ready plan while approval or signature is
776
+ * pending; terminal responses carry a null plan. Signs + submits in-process
777
+ * when a signing key is set and the swap cleared immediately. */
630
778
  async requestSwap(args, opts = {}) {
631
- const operation = {
632
- type: "swap",
633
- credential_id: args.credentialId,
634
- src_chain: args.srcChain,
635
- dst_chain: args.dstChain ?? args.srcChain,
636
- src_token: args.srcToken,
637
- dst_token: args.dstToken,
638
- amount: args.amount,
639
- swap_direction: args.swapDirection ?? "exact-amount-in",
640
- slippage_bps: args.slippageBps ?? 50,
641
- recipient: args.recipient ?? null,
642
- value_cents: args.valueCents ?? null
643
- };
644
- const result = await this.request("POST", "/agent/swap", { body: { operation } });
645
- return this.completeSwap(result, opts);
646
- }
647
- /** Shared completion for swap-shaped requests (`request_swap` and the World
648
- * trade): optionally wait out a `pending_approval` dwell, then sign the plan
649
- * in-process and submit envelopes. Signing re-pins a Solana plan's blockhash
650
- * (and re-quotes a stale World order) via `/agent/requests/{id}/refresh`
651
- * first, so a long approval dwell never signs stale bytes. */
779
+ return this.withExpiryRetry(
780
+ async () => {
781
+ const operation = {
782
+ type: "swap",
783
+ credential_id: args.credentialId,
784
+ src_chain: args.srcChain,
785
+ dst_chain: args.dstChain ?? args.srcChain,
786
+ src_token: args.srcToken,
787
+ dst_token: args.dstToken,
788
+ amount: args.amount,
789
+ swap_direction: args.swapDirection ?? "exact-amount-in",
790
+ // Keep in sync with the request_swap MCP tool default (server side).
791
+ slippage_bps: args.slippageBps ?? 150,
792
+ recipient: args.recipient ?? null,
793
+ value_cents: args.valueCents ?? null
794
+ };
795
+ const result = await this.request("POST", "/agent/swap", {
796
+ body: { operation }
797
+ });
798
+ return this.completeSwap(result, opts);
799
+ },
800
+ (r) => r.response
801
+ );
802
+ }
803
+ /** Complete a swap request: optionally wait out a `pending_approval` dwell,
804
+ * then sign the plan in-process and submit envelopes. Signing first refreshes
805
+ * the parked request via `/agent/requests/{id}/refresh`, so a long approval
806
+ * dwell never signs stale bytes. */
652
807
  async completeSwap(result, opts) {
653
- if (!(opts.autoSign ?? true) || !this.opts.signingKey) return result;
808
+ const creds = this.signingCreds();
809
+ if (!(opts.autoSign ?? true) || !creds) {
810
+ return signingResultForResponse(result, result.response);
811
+ }
654
812
  let response = result.response;
655
813
  if (response.status === "pending_approval" && opts.waitForApproval) {
656
814
  response = await this.waitWhileApproval(response.request_id, opts.waitForApproval);
657
815
  }
658
816
  if (response.status !== "pending_signature") {
659
- return { ...result, response };
817
+ return signingResultForResponse(result, response);
818
+ }
819
+ const plan = result.plan;
820
+ if (!plan) {
821
+ throw new Error("paybox returned pending_signature without a swap signing plan");
660
822
  }
661
- await signSwapRequest(
662
- this.poster,
663
- response.request_id,
664
- result.plan,
665
- credsFromToken(this.opts.signingKey)
823
+ const sign = () => signSwapRequest(this.poster, response.request_id, plan, creds);
824
+ await this.signWithReconcile(response.request_id, sign);
825
+ return signingResultForResponse(
826
+ result,
827
+ await this.requestAfterSign(response.request_id, opts.waitForApproval)
666
828
  );
667
- return { ...result, response: await this.getRequest(response.request_id) };
668
829
  }
669
830
  /** Poll while the request sits in `pending_approval` (the user is approving
670
831
  * with their passkey); returns the first non-approval state. */
@@ -678,63 +839,121 @@ var PayboxClient = class _PayboxClient {
678
839
  await new Promise((res) => setTimeout(res, interval));
679
840
  }
680
841
  }
681
- /** Discover curated paid x402 services from the Bazaar catalog (the
682
- * `discover_services` tool). Pass a query to search the curated set; omit it
683
- * for the curated listing. Set `raw` only to inspect uncurated compact
684
- * Bazaar service summaries. */
842
+ /** Run an operation and, if it terminates on a server-marked expiry
843
+ * (`retryable` on a terminal `error`/`denied`: the window elapsed and nothing
844
+ * was signed or spent), re-issue it exactly once (SPE-456). Never loops: the
845
+ * second attempt's result is returned as-is. */
846
+ async withExpiryRetry(run, responseOf) {
847
+ const first = await run();
848
+ const r = responseOf(first);
849
+ if ((r.status === "error" || r.status === "denied") && r.retryable) {
850
+ return run();
851
+ }
852
+ return first;
853
+ }
854
+ /** Sign a parked request; if the sign/submit call fails, reconcile against the
855
+ * authoritative state once (SPE-456). The server leaves a pre-broadcast
856
+ * completion failure recoverable — the claim is released and the row stays
857
+ * `pending_signature` — so re-sign a still-open request a single time; a
858
+ * terminal or already-broadcast row is left as-is for the caller to read (and
859
+ * for `withExpiryRetry` to re-issue if it is retryable). Bounded to one extra
860
+ * attempt: a second failure propagates. */
861
+ async signWithReconcile(requestId, sign) {
862
+ try {
863
+ await sign();
864
+ } catch {
865
+ const reconciled = await this.getRequest(requestId);
866
+ if (reconciled.status !== "pending_signature") return;
867
+ await sign();
868
+ }
869
+ }
870
+ /** Discover x402 services with the original array-returning response. */
685
871
  async discoverServices(query, opts = {}) {
686
- const r = await this.request("GET", "/agent/services", {
687
- query: { query, raw: opts.raw ? "true" : void 0 }
872
+ const result = await this.request("GET", "/agent/services", {
873
+ query: {
874
+ query,
875
+ network: opts.network === "all" ? void 0 : opts.network,
876
+ raw: opts.raw ? "true" : void 0
877
+ }
878
+ });
879
+ return result.services;
880
+ }
881
+ /** Discover one bounded page of x402 services. Reuse the returned cursor with
882
+ * the same query and network while `has_more` is true. */
883
+ async discoverServicesPage(query, opts = {}) {
884
+ return this.request("GET", "/agent/services", {
885
+ query: {
886
+ query,
887
+ network: opts.network === "all" ? void 0 : opts.network,
888
+ limit: (opts.limit ?? 10).toString(),
889
+ cursor: opts.cursor,
890
+ raw: opts.raw ? "true" : void 0
891
+ }
688
892
  });
689
- return r.services;
690
893
  }
691
894
  /** Pay a paid x402 endpoint from a wallet credential (the `pay_x402` tool,
692
895
  * header mode). Signs in-process when a signing key is set and the payment
693
896
  * clears immediately — then the X-PAYMENT header is on the completed
694
897
  * response's `output.value.x_payment` (replay it on the original request). */
695
898
  async payX402(args, opts = {}) {
696
- const result = await this.request("POST", "/agent/x402", {
697
- body: {
698
- credential_id: args.credentialId,
699
- accepts: args.accepts,
700
- resource_url: args.resourceUrl
701
- }
702
- });
703
- return this.completeX402(result, opts);
899
+ return this.withExpiryRetry(
900
+ async () => {
901
+ const result = await this.request("POST", "/agent/x402", {
902
+ body: {
903
+ credential_id: args.credentialId,
904
+ accepts: args.accepts,
905
+ resource_url: args.resourceUrl
906
+ }
907
+ });
908
+ return this.completeX402(result, opts);
909
+ },
910
+ (r) => r.response
911
+ );
704
912
  }
705
913
  /** Fetch a paid x402 resource and have paybox pay for it (the `use_service`
706
914
  * tool, gateway mode): paybox probes the url, pays the 402, re-fetches with the
707
915
  * payment, and returns the resource's reply on `output.value.response`. */
708
916
  async useService(args, opts = {}) {
709
- const result = await this.request("POST", "/agent/use-service", {
710
- body: {
711
- credential_id: args.credentialId,
712
- url: args.url,
713
- method: args.method,
714
- body: args.body
715
- }
716
- });
717
- return this.completeX402(result, opts);
917
+ return this.withExpiryRetry(
918
+ async () => {
919
+ const result = await this.request("POST", "/agent/use-service", {
920
+ body: {
921
+ credential_id: args.credentialId,
922
+ url: args.url,
923
+ method: args.method,
924
+ body: args.body
925
+ }
926
+ });
927
+ return this.completeX402(result, opts);
928
+ },
929
+ (r) => r.response
930
+ );
718
931
  }
719
932
  /** Shared completion for the two x402 entry points: optionally wait out a
720
933
  * `pending_approval` dwell, then sign the parked typed data in-process and
721
934
  * return the finished request, exactly like the swap path. */
722
935
  async completeX402(result, opts) {
723
- if (!(opts.autoSign ?? true) || !this.opts.signingKey) return result;
936
+ const creds = this.signingCreds();
937
+ if (!(opts.autoSign ?? true) || !creds) {
938
+ return signingResultForResponse(result, result.response);
939
+ }
724
940
  let response = result.response;
725
941
  if (response.status === "pending_approval" && opts.waitForApproval) {
726
942
  response = await this.waitWhileApproval(response.request_id, opts.waitForApproval);
727
943
  }
728
944
  if (response.status !== "pending_signature") {
729
- return { ...result, response };
945
+ return signingResultForResponse(result, response);
946
+ }
947
+ const plan = result.plan;
948
+ if (!plan) {
949
+ throw new Error("paybox returned pending_signature without an x402 signing plan");
730
950
  }
731
- await signX402Request(
732
- this.poster,
733
- response.request_id,
734
- result.plan,
735
- credsFromToken(this.opts.signingKey)
951
+ const sign = () => signX402Request(this.poster, response.request_id, plan, creds);
952
+ await this.signWithReconcile(response.request_id, sign);
953
+ return signingResultForResponse(
954
+ result,
955
+ await this.requestAfterSign(response.request_id, opts.waitForApproval)
736
956
  );
737
- return { ...result, response: await this.getRequest(response.request_id) };
738
957
  }
739
958
  /** Claim the usable card credentials of an approved payment (the
740
959
  * `claim_payment_credentials` tool). One-time: the claim is consumed. */
@@ -754,15 +973,13 @@ var PayboxClient = class _PayboxClient {
754
973
  return this.request("GET", "/agent/buy-link", {
755
974
  query: {
756
975
  credential_id: args.credentialId,
976
+ destination_chain: args.destinationChain,
757
977
  currency_code: args.currencyCode,
758
978
  amount_usd: args.amountUsd?.toString()
759
979
  }
760
980
  });
761
981
  }
762
- // ---- World plugin (prediction markets) — requires the user to have enabled
763
- // the `world` plugin in the paybox app. Reads are discovery-only; `worldTrade`
764
- // parks a force-approved PredictionTrade the SDK signs in-process after the
765
- // user approves (pass `waitForApproval` to wait + sign headlessly). ----
982
+ // ---- World REST discovery; money tools require the geofenced MCP window. ----
766
983
  /** Browse World prediction markets (the `world_find_markets` tool). */
767
984
  worldMarkets(args = {}) {
768
985
  return this.request("GET", "/agent/world/markets", {
@@ -812,28 +1029,13 @@ var PayboxClient = class _PayboxClient {
812
1029
  body: { addresses }
813
1030
  });
814
1031
  }
815
- /** Solana token positions for an address (the `world_positions` tool). */
1032
+ /** A wallet's World prediction-market positions: classified `positions` /
1033
+ * `settled` entries (each with a `position_result`) plus `settlement_cash`
1034
+ * for a settled winner's CASH cash-out (swap CASH to USDC via request_swap).
1035
+ * The `world_positions` tool. */
816
1036
  worldPositions(address) {
817
1037
  return this.request("GET", "/agent/world/positions", { query: { address } });
818
1038
  }
819
- /** Trade a World prediction market (the `world_buy_outcome` /
820
- * `world_change_position` / `world_redeem` tools). Always parks as
821
- * `pending_approval` (trades are force-approved); pass
822
- * `{ waitForApproval: true }` to wait for the user's passkey approval and
823
- * sign in-process. */
824
- async worldTrade(args, opts = {}) {
825
- const result = await this.request("POST", "/agent/world/trade", {
826
- body: {
827
- credential_id: args.credentialId,
828
- market_mint: args.marketMint,
829
- side: args.side,
830
- size: args.size,
831
- slippage_bps: args.slippageBps,
832
- value_cents: args.valueCents
833
- }
834
- });
835
- return this.completeSwap(result, opts);
836
- }
837
1039
  // ---- Hyperliquid plugin (read-only spot discovery) — requires the user to
838
1040
  // have enabled the `hyperliquid` plugin in the paybox app. ----
839
1041
  /** Hyperliquid spot markets (the `hyperliquid_markets` tool). */
@@ -852,6 +1054,12 @@ var PayboxClient = class _PayboxClient {
852
1054
  getRequest(requestId) {
853
1055
  return this.request("GET", `/agent/requests/${requestId}`);
854
1056
  }
1057
+ /** After in-process sign, `--wait` polls through settlement/confirmation;
1058
+ * without it, return the first snapshot (the historical no-wait contract). */
1059
+ requestAfterSign(requestId, wait) {
1060
+ if (!wait) return this.getRequest(requestId);
1061
+ return this.waitForRequest(requestId, typeof wait === "object" ? wait : {});
1062
+ }
855
1063
  /** Poll until the request leaves a pending state (or the timeout elapses). */
856
1064
  async waitForRequest(requestId, opts = {}) {
857
1065
  const interval = opts.intervalMs ?? 2e3;
@@ -871,6 +1079,7 @@ export {
871
1079
  agentClientId,
872
1080
  openBrowser,
873
1081
  loginLoopback,
1082
+ loginDevice,
874
1083
  refreshTokens,
875
1084
  configDir,
876
1085
  configFile,
@@ -880,4 +1089,4 @@ export {
880
1089
  PayboxError,
881
1090
  PayboxClient
882
1091
  };
883
- //# sourceMappingURL=chunk-EBB4HNOB.js.map
1092
+ //# sourceMappingURL=chunk-OBZSVPP7.js.map