@paybox-sh/sdk 0.7.0 → 0.8.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.
@@ -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(
@@ -320,19 +405,19 @@ function buildIssuedAtEnvelope(creds) {
320
405
  }
321
406
  async function signIntent(intent, creds, binding, transport) {
322
407
  if (!binding.walletId) {
323
- return { ok: false, error: "paybox did not supply a wallet id for this operation." };
408
+ return { ok: false, error: "PayBox did not supply a wallet id for this operation." };
324
409
  }
325
410
  if (!binding.keyId || !binding.derivationPath) {
326
411
  return {
327
412
  ok: false,
328
- error: "This wallet isn't provisioned for signing yet (missing key binding). Re-provision it in the paybox app, then retry."
413
+ error: "This wallet isn't provisioned for signing yet (missing key binding). Re-provision it in the PayBox app, then retry."
329
414
  };
330
415
  }
331
416
  const ecdsaSign = async (digest) => {
332
417
  const result = await transport(buildEnvelope(digest, binding, creds));
333
418
  const { r, s, recovery_id } = result.signature;
334
419
  if (r == null || s == null || recovery_id == null) {
335
- throw new Error("MoonX returned an ECDSA result without {r, s, recovery_id}");
420
+ throw new Error("the signing service returned an ECDSA result without {r, s, recovery_id}");
336
421
  }
337
422
  return {
338
423
  r: `0x${r}`,
@@ -343,7 +428,7 @@ async function signIntent(intent, creds, binding, transport) {
343
428
  const ed25519Sign = async (message) => {
344
429
  const result = await transport(buildEnvelope(bytesToHex(message), binding, creds));
345
430
  const sig = result.signature.signature;
346
- if (!sig) throw new Error("MoonX returned an Ed25519 result without a signature");
431
+ if (!sig) throw new Error("the signing service returned an Ed25519 result without a signature");
347
432
  return hexToBytes(sig);
348
433
  };
349
434
  try {
@@ -412,6 +497,11 @@ async function signIntent(intent, creds, binding, transport) {
412
497
 
413
498
  // src/signer.ts
414
499
  function credsFromToken(token) {
500
+ if (token.trim().startsWith("pbx_live_")) {
501
+ throw new Error(
502
+ "invalid signing key: got a `pbx_live_` API key; expected a `pbxk1.` token (keypair only)"
503
+ );
504
+ }
415
505
  const kp = parseToken(token);
416
506
  if (!kp) {
417
507
  throw new Error("invalid signing key: expected a `pbxk1.` token (keypair only)");
@@ -444,7 +534,9 @@ async function signWalletRequest(post, requestId, intent, creds, binding) {
444
534
  function isSolanaRefreshablePlan(plan) {
445
535
  const txs = plan.txs;
446
536
  const solana = txs.some((t) => t.vm === "solana");
447
- return solana && txs.some((t) => t.kind === "swap" || t.kind === "prediction_trade");
537
+ return solana && txs.some(
538
+ (t) => t.kind === "swap" || t.kind === "prediction_trade" || t.kind === "jupiter_swap"
539
+ );
448
540
  }
449
541
  async function refreshSwapPlan(post, requestId, plan) {
450
542
  const r = await post(`/agent/requests/${requestId}/refresh`, {});
@@ -490,6 +582,10 @@ async function signX402Request(post, requestId, plan, creds) {
490
582
  // src/client.ts
491
583
  var DEFAULT_BASE_URL = "https://api.paybox.sh";
492
584
  var EXPIRY_SKEW_MS = 6e4;
585
+ function signingResultForResponse(result, response) {
586
+ const exposesPlan = response.status === "pending_approval" || response.status === "pending_signature";
587
+ return { ...result, response, plan: exposesPlan ? result.plan : null };
588
+ }
493
589
  var PayboxError = class extends Error {
494
590
  constructor(status, body, context) {
495
591
  super(`paybox ${context} failed (${status}): ${body}`);
@@ -525,9 +621,17 @@ var PayboxClient = class _PayboxClient {
525
621
  }
526
622
  return client;
527
623
  }
528
- /** Whether in-process signing is available (a `pbxk1.` key is configured). */
624
+ signingCreds() {
625
+ if (!this.opts.signingKey) return null;
626
+ try {
627
+ return credsFromToken(this.opts.signingKey);
628
+ } catch {
629
+ return null;
630
+ }
631
+ }
632
+ /** Whether in-process signing is available (a parseable `pbxk1.` key is set). */
529
633
  get canSign() {
530
- return Boolean(this.opts.signingKey);
634
+ return this.signingCreds() !== null;
531
635
  }
532
636
  async bearer() {
533
637
  if (this.opts.token) return this.opts.token;
@@ -564,7 +668,8 @@ var PayboxClient = class _PayboxClient {
564
668
  return text ? JSON.parse(text) : void 0;
565
669
  }
566
670
  poster = (path, body) => this.request("POST", path, { body });
567
- /** List the credentials this client may use (the `list_credentials` tool). */
671
+ /** List granted credentials plus the `ungranted` credentials (id, name, kind,
672
+ * safe metadata) the user owns but hasn't granted this connector. */
568
673
  listCredentials() {
569
674
  return this.request("GET", "/agent/grants");
570
675
  }
@@ -574,6 +679,16 @@ var PayboxClient = class _PayboxClient {
574
679
  query: { address: args.address, network_ids: args.networkIds }
575
680
  });
576
681
  }
682
+ /** Verify one confirmed Solana transaction's exact wallet/mint effect, then
683
+ * read the current balance at or after its slot (`verify_solana_balance`). */
684
+ verifySolanaBalance(args) {
685
+ const body = {
686
+ address: args.address,
687
+ token_mint: args.tokenMint,
688
+ transaction_signature: args.transactionSignature
689
+ };
690
+ return this.request("POST", "/agent/portfolio/verify-solana-balance", { body });
691
+ }
577
692
  /** Charge a card credential (the `request_payment` tool). */
578
693
  requestPayment(args) {
579
694
  const operation = {
@@ -599,72 +714,88 @@ var PayboxClient = class _PayboxClient {
599
714
  /** Sign with a wallet credential (the `request_wallet_sign` tool). Signs
600
715
  * in-process and returns the completed request when a signing key is set and
601
716
  * 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. */
717
+ * Uses `/agent/wallet-sign`, which echoes the wallet binding only while
718
+ * approval or signature is pending so the signer need not resolve it
719
+ * separately. Terminal responses carry a null binding. */
604
720
  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 } }
721
+ return this.withExpiryRetry(
722
+ async () => {
723
+ const operation = {
724
+ type: "wallet_sign",
725
+ credential_id: args.credentialId,
726
+ intent: args.intent
727
+ };
728
+ const { response, binding } = await this.request(
729
+ "POST",
730
+ "/agent/wallet-sign",
731
+ { body: { operation } }
732
+ );
733
+ const creds = this.signingCreds();
734
+ if ((opts.autoSign ?? true) && response.status === "pending_signature" && creds) {
735
+ const sign = () => signWalletRequest(this.poster, response.request_id, args.intent, creds, binding);
736
+ await this.signWithReconcile(response.request_id, sign);
737
+ return this.getRequest(response.request_id);
738
+ }
739
+ return response;
740
+ },
741
+ (r) => r
614
742
  );
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
743
  }
627
744
  /** 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. */
745
+ * parked response and its sign-ready plan while approval or signature is
746
+ * pending; terminal responses carry a null plan. Signs + submits in-process
747
+ * when a signing key is set and the swap cleared immediately. */
630
748
  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. */
749
+ return this.withExpiryRetry(
750
+ async () => {
751
+ const operation = {
752
+ type: "swap",
753
+ credential_id: args.credentialId,
754
+ src_chain: args.srcChain,
755
+ dst_chain: args.dstChain ?? args.srcChain,
756
+ src_token: args.srcToken,
757
+ dst_token: args.dstToken,
758
+ amount: args.amount,
759
+ swap_direction: args.swapDirection ?? "exact-amount-in",
760
+ // Keep in sync with the request_swap MCP tool default (server side).
761
+ slippage_bps: args.slippageBps ?? 150,
762
+ recipient: args.recipient ?? null,
763
+ value_cents: args.valueCents ?? null
764
+ };
765
+ const result = await this.request("POST", "/agent/swap", {
766
+ body: { operation }
767
+ });
768
+ return this.completeSwap(result, opts);
769
+ },
770
+ (r) => r.response
771
+ );
772
+ }
773
+ /** Complete a swap request: optionally wait out a `pending_approval` dwell,
774
+ * then sign the plan in-process and submit envelopes. Signing first refreshes
775
+ * the parked request via `/agent/requests/{id}/refresh`, so a long approval
776
+ * dwell never signs stale bytes. */
652
777
  async completeSwap(result, opts) {
653
- if (!(opts.autoSign ?? true) || !this.opts.signingKey) return result;
778
+ const creds = this.signingCreds();
779
+ if (!(opts.autoSign ?? true) || !creds) {
780
+ return signingResultForResponse(result, result.response);
781
+ }
654
782
  let response = result.response;
655
783
  if (response.status === "pending_approval" && opts.waitForApproval) {
656
784
  response = await this.waitWhileApproval(response.request_id, opts.waitForApproval);
657
785
  }
658
786
  if (response.status !== "pending_signature") {
659
- return { ...result, response };
787
+ return signingResultForResponse(result, response);
788
+ }
789
+ const plan = result.plan;
790
+ if (!plan) {
791
+ throw new Error("paybox returned pending_signature without a swap signing plan");
660
792
  }
661
- await signSwapRequest(
662
- this.poster,
663
- response.request_id,
664
- result.plan,
665
- credsFromToken(this.opts.signingKey)
793
+ const sign = () => signSwapRequest(this.poster, response.request_id, plan, creds);
794
+ await this.signWithReconcile(response.request_id, sign);
795
+ return signingResultForResponse(
796
+ result,
797
+ await this.requestAfterSign(response.request_id, opts.waitForApproval)
666
798
  );
667
- return { ...result, response: await this.getRequest(response.request_id) };
668
799
  }
669
800
  /** Poll while the request sits in `pending_approval` (the user is approving
670
801
  * with their passkey); returns the first non-approval state. */
@@ -678,63 +809,121 @@ var PayboxClient = class _PayboxClient {
678
809
  await new Promise((res) => setTimeout(res, interval));
679
810
  }
680
811
  }
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. */
812
+ /** Run an operation and, if it terminates on a server-marked expiry
813
+ * (`retryable` on a terminal `error`/`denied`: the window elapsed and nothing
814
+ * was signed or spent), re-issue it exactly once (SPE-456). Never loops: the
815
+ * second attempt's result is returned as-is. */
816
+ async withExpiryRetry(run, responseOf) {
817
+ const first = await run();
818
+ const r = responseOf(first);
819
+ if ((r.status === "error" || r.status === "denied") && r.retryable) {
820
+ return run();
821
+ }
822
+ return first;
823
+ }
824
+ /** Sign a parked request; if the sign/submit call fails, reconcile against the
825
+ * authoritative state once (SPE-456). The server leaves a pre-broadcast
826
+ * completion failure recoverable — the claim is released and the row stays
827
+ * `pending_signature` — so re-sign a still-open request a single time; a
828
+ * terminal or already-broadcast row is left as-is for the caller to read (and
829
+ * for `withExpiryRetry` to re-issue if it is retryable). Bounded to one extra
830
+ * attempt: a second failure propagates. */
831
+ async signWithReconcile(requestId, sign) {
832
+ try {
833
+ await sign();
834
+ } catch {
835
+ const reconciled = await this.getRequest(requestId);
836
+ if (reconciled.status !== "pending_signature") return;
837
+ await sign();
838
+ }
839
+ }
840
+ /** Discover x402 services with the original array-returning response. */
685
841
  async discoverServices(query, opts = {}) {
686
- const r = await this.request("GET", "/agent/services", {
687
- query: { query, raw: opts.raw ? "true" : void 0 }
842
+ const result = await this.request("GET", "/agent/services", {
843
+ query: {
844
+ query,
845
+ network: opts.network === "all" ? void 0 : opts.network,
846
+ raw: opts.raw ? "true" : void 0
847
+ }
848
+ });
849
+ return result.services;
850
+ }
851
+ /** Discover one bounded page of x402 services. Reuse the returned cursor with
852
+ * the same query and network while `has_more` is true. */
853
+ async discoverServicesPage(query, opts = {}) {
854
+ return this.request("GET", "/agent/services", {
855
+ query: {
856
+ query,
857
+ network: opts.network === "all" ? void 0 : opts.network,
858
+ limit: (opts.limit ?? 10).toString(),
859
+ cursor: opts.cursor,
860
+ raw: opts.raw ? "true" : void 0
861
+ }
688
862
  });
689
- return r.services;
690
863
  }
691
864
  /** Pay a paid x402 endpoint from a wallet credential (the `pay_x402` tool,
692
865
  * header mode). Signs in-process when a signing key is set and the payment
693
866
  * clears immediately — then the X-PAYMENT header is on the completed
694
867
  * response's `output.value.x_payment` (replay it on the original request). */
695
868
  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);
869
+ return this.withExpiryRetry(
870
+ async () => {
871
+ const result = await this.request("POST", "/agent/x402", {
872
+ body: {
873
+ credential_id: args.credentialId,
874
+ accepts: args.accepts,
875
+ resource_url: args.resourceUrl
876
+ }
877
+ });
878
+ return this.completeX402(result, opts);
879
+ },
880
+ (r) => r.response
881
+ );
704
882
  }
705
883
  /** Fetch a paid x402 resource and have paybox pay for it (the `use_service`
706
884
  * tool, gateway mode): paybox probes the url, pays the 402, re-fetches with the
707
885
  * payment, and returns the resource's reply on `output.value.response`. */
708
886
  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);
887
+ return this.withExpiryRetry(
888
+ async () => {
889
+ const result = await this.request("POST", "/agent/use-service", {
890
+ body: {
891
+ credential_id: args.credentialId,
892
+ url: args.url,
893
+ method: args.method,
894
+ body: args.body
895
+ }
896
+ });
897
+ return this.completeX402(result, opts);
898
+ },
899
+ (r) => r.response
900
+ );
718
901
  }
719
902
  /** Shared completion for the two x402 entry points: optionally wait out a
720
903
  * `pending_approval` dwell, then sign the parked typed data in-process and
721
904
  * return the finished request, exactly like the swap path. */
722
905
  async completeX402(result, opts) {
723
- if (!(opts.autoSign ?? true) || !this.opts.signingKey) return result;
906
+ const creds = this.signingCreds();
907
+ if (!(opts.autoSign ?? true) || !creds) {
908
+ return signingResultForResponse(result, result.response);
909
+ }
724
910
  let response = result.response;
725
911
  if (response.status === "pending_approval" && opts.waitForApproval) {
726
912
  response = await this.waitWhileApproval(response.request_id, opts.waitForApproval);
727
913
  }
728
914
  if (response.status !== "pending_signature") {
729
- return { ...result, response };
915
+ return signingResultForResponse(result, response);
730
916
  }
731
- await signX402Request(
732
- this.poster,
733
- response.request_id,
734
- result.plan,
735
- credsFromToken(this.opts.signingKey)
917
+ const plan = result.plan;
918
+ if (!plan) {
919
+ throw new Error("paybox returned pending_signature without an x402 signing plan");
920
+ }
921
+ const sign = () => signX402Request(this.poster, response.request_id, plan, creds);
922
+ await this.signWithReconcile(response.request_id, sign);
923
+ return signingResultForResponse(
924
+ result,
925
+ await this.requestAfterSign(response.request_id, opts.waitForApproval)
736
926
  );
737
- return { ...result, response: await this.getRequest(response.request_id) };
738
927
  }
739
928
  /** Claim the usable card credentials of an approved payment (the
740
929
  * `claim_payment_credentials` tool). One-time: the claim is consumed. */
@@ -754,15 +943,13 @@ var PayboxClient = class _PayboxClient {
754
943
  return this.request("GET", "/agent/buy-link", {
755
944
  query: {
756
945
  credential_id: args.credentialId,
946
+ destination_chain: args.destinationChain,
757
947
  currency_code: args.currencyCode,
758
948
  amount_usd: args.amountUsd?.toString()
759
949
  }
760
950
  });
761
951
  }
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). ----
952
+ // ---- World REST discovery; money tools require the geofenced MCP window. ----
766
953
  /** Browse World prediction markets (the `world_find_markets` tool). */
767
954
  worldMarkets(args = {}) {
768
955
  return this.request("GET", "/agent/world/markets", {
@@ -812,28 +999,13 @@ var PayboxClient = class _PayboxClient {
812
999
  body: { addresses }
813
1000
  });
814
1001
  }
815
- /** Solana token positions for an address (the `world_positions` tool). */
1002
+ /** A wallet's World prediction-market positions: classified `positions` /
1003
+ * `settled` entries (each with a `position_result`) plus `settlement_cash`
1004
+ * for a settled winner's CASH cash-out (swap CASH to USDC via request_swap).
1005
+ * The `world_positions` tool. */
816
1006
  worldPositions(address) {
817
1007
  return this.request("GET", "/agent/world/positions", { query: { address } });
818
1008
  }
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
1009
  // ---- Hyperliquid plugin (read-only spot discovery) — requires the user to
838
1010
  // have enabled the `hyperliquid` plugin in the paybox app. ----
839
1011
  /** Hyperliquid spot markets (the `hyperliquid_markets` tool). */
@@ -852,6 +1024,12 @@ var PayboxClient = class _PayboxClient {
852
1024
  getRequest(requestId) {
853
1025
  return this.request("GET", `/agent/requests/${requestId}`);
854
1026
  }
1027
+ /** After in-process sign, `--wait` polls through settlement/confirmation;
1028
+ * without it, return the first snapshot (the historical no-wait contract). */
1029
+ requestAfterSign(requestId, wait) {
1030
+ if (!wait) return this.getRequest(requestId);
1031
+ return this.waitForRequest(requestId, typeof wait === "object" ? wait : {});
1032
+ }
855
1033
  /** Poll until the request leaves a pending state (or the timeout elapses). */
856
1034
  async waitForRequest(requestId, opts = {}) {
857
1035
  const interval = opts.intervalMs ?? 2e3;
@@ -871,6 +1049,7 @@ export {
871
1049
  agentClientId,
872
1050
  openBrowser,
873
1051
  loginLoopback,
1052
+ loginDevice,
874
1053
  refreshTokens,
875
1054
  configDir,
876
1055
  configFile,
@@ -880,4 +1059,4 @@ export {
880
1059
  PayboxError,
881
1060
  PayboxClient
882
1061
  };
883
- //# sourceMappingURL=chunk-EBB4HNOB.js.map
1062
+ //# sourceMappingURL=chunk-7VW4QMV4.js.map