@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.
- package/README.md +46 -15
- package/SKILL.md +118 -0
- package/dist/{chunk-DIMOTJ2E.js → chunk-7VW4QMV4.js} +445 -105
- package/dist/chunk-7VW4QMV4.js.map +1 -0
- package/dist/cli.js +125 -54
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +220 -26
- package/dist/index.js +1 -1
- package/package.json +12 -4
- package/dist/chunk-DIMOTJ2E.js.map +0 -1
|
@@ -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>
|
|
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 ?? "
|
|
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
|
|
272
|
-
throw new Error(
|
|
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("
|
|
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
|
|
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("
|
|
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
|
|
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: "
|
|
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
|
|
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("
|
|
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("
|
|
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:
|
|
447
|
-
keyId:
|
|
448
|
-
derivationPath:
|
|
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(
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
586
|
-
* need not resolve it
|
|
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
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
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
|
|
612
|
-
*
|
|
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
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
this.
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
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
|
-
|
|
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
|
-
/**
|
|
640
|
-
*
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
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
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
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
|
-
|
|
666
|
-
|
|
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
|
-
|
|
669
|
-
|
|
670
|
-
|
|
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
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
}
|
|
688
|
-
|
|
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-
|
|
1062
|
+
//# sourceMappingURL=chunk-7VW4QMV4.js.map
|