@paybox-sh/sdk 0.5.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";
@@ -268,18 +353,18 @@ function solanaSigningPayload(base64Tx) {
268
353
  if ((b & 128) === 0) break;
269
354
  shift += 7;
270
355
  }
271
- if (sigCount !== 1) {
272
- throw new Error(`expected a single-signer solana tx, got ${sigCount} signature slot(s)`);
356
+ if (sigCount < 1) {
357
+ throw new Error("solana tx has no signature slots");
273
358
  }
274
- return bytesToHex(new Uint8Array(raw.subarray(i + 64)));
359
+ return bytesToHex(new Uint8Array(raw.subarray(i + sigCount * 64)));
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)");
@@ -441,16 +531,35 @@ async function signWalletRequest(post, requestId, intent, creds, binding) {
441
531
  await post(`/agent/requests/${requestId}/signature`, { artifact: result.artifact });
442
532
  return result.artifact;
443
533
  }
534
+ function isSolanaRefreshablePlan(plan) {
535
+ const txs = plan.txs;
536
+ const solana = txs.some((t) => t.vm === "solana");
537
+ return solana && txs.some(
538
+ (t) => t.kind === "swap" || t.kind === "prediction_trade" || t.kind === "jupiter_swap"
539
+ );
540
+ }
541
+ async function refreshSwapPlan(post, requestId, plan) {
542
+ const r = await post(`/agent/requests/${requestId}/refresh`, {});
543
+ if (!r || typeof r.plan !== "object" || r.plan === null) {
544
+ throw new Error("swap refresh returned no plan");
545
+ }
546
+ const refreshed = r.plan;
547
+ if (!Array.isArray(refreshed.txs) || refreshed.txs.length === 0) {
548
+ throw new Error("swap refresh returned a plan with no txs");
549
+ }
550
+ return refreshed;
551
+ }
444
552
  async function signSwapRequest(post, requestId, plan, creds) {
553
+ const signPlan = isSolanaRefreshablePlan(plan) ? await refreshSwapPlan(post, requestId, plan) : plan;
445
554
  let binding = {
446
- walletId: plan.binding.wallet_id,
447
- keyId: plan.binding.key_id ?? "",
448
- derivationPath: plan.binding.derivation_path ?? ""
555
+ walletId: signPlan.binding.wallet_id,
556
+ keyId: signPlan.binding.key_id ?? "",
557
+ derivationPath: signPlan.binding.derivation_path ?? ""
449
558
  };
450
559
  if (binding.walletId && (!binding.keyId || !binding.derivationPath)) {
451
560
  binding = await resolveBinding(post, requestId, creds);
452
561
  }
453
- const envelopes = buildSwapEnvelopes(plan.txs, binding, creds);
562
+ const envelopes = buildSwapEnvelopes(signPlan.txs, binding, creds);
454
563
  await post(`/agent/requests/${requestId}/envelopes`, { envelopes });
455
564
  }
456
565
  async function signX402Request(post, requestId, plan, creds) {
@@ -473,6 +582,10 @@ async function signX402Request(post, requestId, plan, creds) {
473
582
  // src/client.ts
474
583
  var DEFAULT_BASE_URL = "https://api.paybox.sh";
475
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
+ }
476
589
  var PayboxError = class extends Error {
477
590
  constructor(status, body, context) {
478
591
  super(`paybox ${context} failed (${status}): ${body}`);
@@ -508,9 +621,17 @@ var PayboxClient = class _PayboxClient {
508
621
  }
509
622
  return client;
510
623
  }
511
- /** 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). */
512
633
  get canSign() {
513
- return Boolean(this.opts.signingKey);
634
+ return this.signingCreds() !== null;
514
635
  }
515
636
  async bearer() {
516
637
  if (this.opts.token) return this.opts.token;
@@ -547,7 +668,8 @@ var PayboxClient = class _PayboxClient {
547
668
  return text ? JSON.parse(text) : void 0;
548
669
  }
549
670
  poster = (path, body) => this.request("POST", path, { body });
550
- /** 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. */
551
673
  listCredentials() {
552
674
  return this.request("GET", "/agent/grants");
553
675
  }
@@ -557,6 +679,16 @@ var PayboxClient = class _PayboxClient {
557
679
  query: { address: args.address, network_ids: args.networkIds }
558
680
  });
559
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
+ }
560
692
  /** Charge a card credential (the `request_payment` tool). */
561
693
  requestPayment(args) {
562
694
  const operation = {
@@ -582,122 +714,329 @@ var PayboxClient = class _PayboxClient {
582
714
  /** Sign with a wallet credential (the `request_wallet_sign` tool). Signs
583
715
  * in-process and returns the completed request when a signing key is set and
584
716
  * the request clears immediately; otherwise returns the pending response.
585
- * Uses `/agent/wallet-sign`, which echoes the wallet binding so the signer
586
- * 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. */
587
720
  async requestWalletSign(args, opts = {}) {
588
- const operation = {
589
- type: "wallet_sign",
590
- credential_id: args.credentialId,
591
- intent: args.intent
592
- };
593
- const { response, binding } = await this.request(
594
- "POST",
595
- "/agent/wallet-sign",
596
- { 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
597
742
  );
598
- if ((opts.autoSign ?? true) && response.status === "pending_signature" && this.opts.signingKey) {
599
- await signWalletRequest(
600
- this.poster,
601
- response.request_id,
602
- args.intent,
603
- credsFromToken(this.opts.signingKey),
604
- binding
605
- );
606
- return this.getRequest(response.request_id);
607
- }
608
- return response;
609
743
  }
610
744
  /** Swap tokens from a wallet credential (the `request_swap` tool). Returns the
611
- * parked response + sign-ready plan; signs + submits in-process when a signing
612
- * 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. */
613
748
  async requestSwap(args, opts = {}) {
614
- const operation = {
615
- type: "swap",
616
- credential_id: args.credentialId,
617
- src_chain: args.srcChain,
618
- dst_chain: args.dstChain ?? args.srcChain,
619
- src_token: args.srcToken,
620
- dst_token: args.dstToken,
621
- amount: args.amount,
622
- swap_direction: args.swapDirection ?? "exact-amount-in",
623
- slippage_bps: args.slippageBps ?? 50,
624
- recipient: args.recipient ?? null,
625
- value_cents: args.valueCents ?? null
626
- };
627
- const result = await this.request("POST", "/agent/swap", { body: { operation } });
628
- if ((opts.autoSign ?? true) && result.response.status === "pending_signature" && this.opts.signingKey) {
629
- await signSwapRequest(
630
- this.poster,
631
- result.response.request_id,
632
- result.plan,
633
- credsFromToken(this.opts.signingKey)
634
- );
635
- return { ...result, response: await this.getRequest(result.response.request_id) };
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. */
777
+ async completeSwap(result, opts) {
778
+ const creds = this.signingCreds();
779
+ if (!(opts.autoSign ?? true) || !creds) {
780
+ return signingResultForResponse(result, result.response);
636
781
  }
637
- return result;
782
+ let response = result.response;
783
+ if (response.status === "pending_approval" && opts.waitForApproval) {
784
+ response = await this.waitWhileApproval(response.request_id, opts.waitForApproval);
785
+ }
786
+ if (response.status !== "pending_signature") {
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");
792
+ }
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)
798
+ );
799
+ }
800
+ /** Poll while the request sits in `pending_approval` (the user is approving
801
+ * with their passkey); returns the first non-approval state. */
802
+ async waitWhileApproval(requestId, wait) {
803
+ const o = typeof wait === "object" ? wait : {};
804
+ const interval = o.intervalMs ?? 2e3;
805
+ const deadline = Date.now() + (o.timeoutMs ?? 3e5);
806
+ for (; ; ) {
807
+ const r = await this.getRequest(requestId);
808
+ if (r.status !== "pending_approval" || Date.now() > deadline) return r;
809
+ await new Promise((res) => setTimeout(res, interval));
810
+ }
811
+ }
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;
638
823
  }
639
- /** Discover paid x402 services from the Bazaar catalog (the `discover_services`
640
- * tool). Pass a query for a ranked search; omit it for the catalog listing. */
641
- async discoverServices(query) {
642
- const r = await this.request("GET", "/agent/services", {
643
- query: { query }
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. */
841
+ async discoverServices(query, opts = {}) {
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
+ }
644
862
  });
645
- return r.services;
646
863
  }
647
864
  /** Pay a paid x402 endpoint from a wallet credential (the `pay_x402` tool,
648
865
  * header mode). Signs in-process when a signing key is set and the payment
649
866
  * clears immediately — then the X-PAYMENT header is on the completed
650
867
  * response's `output.value.x_payment` (replay it on the original request). */
651
868
  async payX402(args, opts = {}) {
652
- const result = await this.request("POST", "/agent/x402", {
653
- body: {
654
- credential_id: args.credentialId,
655
- accepts: args.accepts,
656
- resource_url: args.resourceUrl
657
- }
658
- });
659
- 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
+ );
660
882
  }
661
883
  /** Fetch a paid x402 resource and have paybox pay for it (the `use_service`
662
884
  * tool, gateway mode): paybox probes the url, pays the 402, re-fetches with the
663
885
  * payment, and returns the resource's reply on `output.value.response`. */
664
886
  async useService(args, opts = {}) {
665
- const result = await this.request("POST", "/agent/use-service", {
666
- body: {
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
+ );
901
+ }
902
+ /** Shared completion for the two x402 entry points: optionally wait out a
903
+ * `pending_approval` dwell, then sign the parked typed data in-process and
904
+ * return the finished request, exactly like the swap path. */
905
+ async completeX402(result, opts) {
906
+ const creds = this.signingCreds();
907
+ if (!(opts.autoSign ?? true) || !creds) {
908
+ return signingResultForResponse(result, result.response);
909
+ }
910
+ let response = result.response;
911
+ if (response.status === "pending_approval" && opts.waitForApproval) {
912
+ response = await this.waitWhileApproval(response.request_id, opts.waitForApproval);
913
+ }
914
+ if (response.status !== "pending_signature") {
915
+ return signingResultForResponse(result, response);
916
+ }
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)
926
+ );
927
+ }
928
+ /** Claim the usable card credentials of an approved payment (the
929
+ * `claim_payment_credentials` tool). One-time: the claim is consumed. */
930
+ claimPaymentCredentials(requestId) {
931
+ return this.request("POST", `/agent/requests/${requestId}/credentials`);
932
+ }
933
+ /** Open the account-change flow (the `request_account_change` tool): returns a
934
+ * `manage_url` deep link for the USER to open — they update this client's
935
+ * access (or add a credential) in their own passkey-gated app session.
936
+ * Nothing is parked or applied server-side. */
937
+ requestAccountChange(note) {
938
+ return this.request("POST", "/agent/account-change", { body: { note } });
939
+ }
940
+ /** A signed MoonPay checkout URL funding a granted wallet (the `get_buy_link`
941
+ * tool). Share the URL with the user; generating it charges nothing. */
942
+ getBuyLink(args) {
943
+ return this.request("GET", "/agent/buy-link", {
944
+ query: {
667
945
  credential_id: args.credentialId,
668
- url: args.url,
669
- method: args.method,
670
- body: args.body
946
+ destination_chain: args.destinationChain,
947
+ currency_code: args.currencyCode,
948
+ amount_usd: args.amountUsd?.toString()
671
949
  }
672
950
  });
673
- return this.completeX402(result, opts);
674
951
  }
675
- /** Shared completion for the two x402 entry points: sign the parked typed data
676
- * in-process (when a key is set + the request cleared) and return the finished
677
- * request, exactly like the swap path. */
678
- async completeX402(result, opts) {
679
- if ((opts.autoSign ?? true) && result.response.status === "pending_signature" && this.opts.signingKey) {
680
- await signX402Request(
681
- this.poster,
682
- result.response.request_id,
683
- result.plan,
684
- credsFromToken(this.opts.signingKey)
685
- );
686
- return { ...result, response: await this.getRequest(result.response.request_id) };
687
- }
688
- return result;
952
+ // ---- World REST discovery; money tools require the geofenced MCP window. ----
953
+ /** Browse World prediction markets (the `world_find_markets` tool). */
954
+ worldMarkets(args = {}) {
955
+ return this.request("GET", "/agent/world/markets", {
956
+ query: {
957
+ events: args.events ? "true" : void 0,
958
+ limit: args.limit?.toString(),
959
+ cursor: args.cursor?.toString(),
960
+ event_ticker: args.eventTicker,
961
+ series_ticker: args.seriesTicker,
962
+ status: args.status
963
+ }
964
+ });
965
+ }
966
+ /** One World event with its markets (the `world_get_market` tool). Nested
967
+ * markets default on, matching the tool — they carry the outcome mint ids
968
+ * the orderbook/trade calls need. */
969
+ worldMarket(eventTicker, withNestedMarkets = true) {
970
+ return this.request("GET", "/agent/world/market", {
971
+ query: {
972
+ event_ticker: eventTicker,
973
+ // Omit when true (the server default); send only the explicit opt-out.
974
+ with_nested_markets: withNestedMarkets ? void 0 : "false"
975
+ }
976
+ });
977
+ }
978
+ /** A World market's orderbook (the `world_orderbook` tool). */
979
+ worldOrderbook(id, byMint = false) {
980
+ return this.request("GET", "/agent/world/orderbook", {
981
+ query: { id, by_mint: byMint ? "true" : void 0 }
982
+ });
983
+ }
984
+ /** A World market's price history (the `world_market_prices` tool). */
985
+ worldPrices(args) {
986
+ return this.request("GET", "/agent/world/prices", {
987
+ query: {
988
+ ticker: args.ticker,
989
+ start_ts: args.startTs.toString(),
990
+ end_ts: args.endTs.toString(),
991
+ resolution: args.resolution?.toString()
992
+ }
993
+ });
994
+ }
995
+ /** Filter addresses down to World outcome mints (the
996
+ * `world_filter_outcome_mints` tool). */
997
+ worldFilterOutcomeMints(addresses) {
998
+ return this.request("POST", "/agent/world/filter-outcome-mints", {
999
+ body: { addresses }
1000
+ });
1001
+ }
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. */
1006
+ worldPositions(address) {
1007
+ return this.request("GET", "/agent/world/positions", { query: { address } });
1008
+ }
1009
+ // ---- Hyperliquid plugin (read-only spot discovery) — requires the user to
1010
+ // have enabled the `hyperliquid` plugin in the paybox app. ----
1011
+ /** Hyperliquid spot markets (the `hyperliquid_markets` tool). */
1012
+ hyperliquidMarkets() {
1013
+ return this.request("GET", "/agent/hyperliquid/markets");
1014
+ }
1015
+ /** Hyperliquid spot prices (the `hyperliquid_prices` tool). */
1016
+ hyperliquidPrices() {
1017
+ return this.request("GET", "/agent/hyperliquid/prices");
1018
+ }
1019
+ /** Resolve one Hyperliquid token by name (the `hyperliquid_token` tool). */
1020
+ hyperliquidToken(name) {
1021
+ return this.request("GET", "/agent/hyperliquid/token", { query: { name } });
689
1022
  }
690
1023
  /** Poll a request's status (the `get_request` tool). */
691
1024
  getRequest(requestId) {
692
1025
  return this.request("GET", `/agent/requests/${requestId}`);
693
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
+ }
694
1033
  /** Poll until the request leaves a pending state (or the timeout elapses). */
695
1034
  async waitForRequest(requestId, opts = {}) {
696
1035
  const interval = opts.intervalMs ?? 2e3;
697
1036
  const deadline = Date.now() + (opts.timeoutMs ?? 3e5);
698
1037
  for (; ; ) {
699
1038
  const r = await this.getRequest(requestId);
700
- const pending = r.status === "pending_approval" || r.status === "pending_signature";
1039
+ const pending = r.status === "pending_approval" || r.status === "pending_signature" || r.status === "pending_settlement" || r.status === "pending_confirmation";
701
1040
  if (!pending || Date.now() > deadline) return r;
702
1041
  await new Promise((res) => setTimeout(res, interval));
703
1042
  }
@@ -710,6 +1049,7 @@ export {
710
1049
  agentClientId,
711
1050
  openBrowser,
712
1051
  loginLoopback,
1052
+ loginDevice,
713
1053
  refreshTokens,
714
1054
  configDir,
715
1055
  configFile,
@@ -719,4 +1059,4 @@ export {
719
1059
  PayboxError,
720
1060
  PayboxClient
721
1061
  };
722
- //# sourceMappingURL=chunk-DIMOTJ2E.js.map
1062
+ //# sourceMappingURL=chunk-7VW4QMV4.js.map