@paybox-sh/sdk 0.7.0 → 0.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +27 -15
- package/SKILL.md +121 -0
- package/dist/{chunk-EBB4HNOB.js → chunk-OBZSVPP7.js} +339 -130
- package/dist/chunk-OBZSVPP7.js.map +1 -0
- package/dist/cli.js +65 -51
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +140 -52
- package/dist/index.js +1 -1
- package/package.json +12 -4
- package/dist/chunk-EBB4HNOB.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";
|
|
@@ -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("
|
|
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(
|
|
@@ -307,6 +392,22 @@ function buildX402Envelope(typedData, binding, creds) {
|
|
|
307
392
|
);
|
|
308
393
|
return { api_pub: creds.apiPubHex, signed_body, agent_signature };
|
|
309
394
|
}
|
|
395
|
+
function buildX402SolanaEnvelope(transactionBase64, binding, creds) {
|
|
396
|
+
if (!binding.walletId) {
|
|
397
|
+
throw new Error("PayBox did not supply a wallet id for this payment.");
|
|
398
|
+
}
|
|
399
|
+
if (!binding.keyId || !binding.derivationPath) {
|
|
400
|
+
throw new Error(
|
|
401
|
+
"This wallet isn't provisioned for signing yet (missing key binding). Re-provision it in the PayBox app, then retry."
|
|
402
|
+
);
|
|
403
|
+
}
|
|
404
|
+
const { signed_body, agent_signature } = buildEnvelope(
|
|
405
|
+
solanaSigningPayload(transactionBase64),
|
|
406
|
+
binding,
|
|
407
|
+
creds
|
|
408
|
+
);
|
|
409
|
+
return { api_pub: creds.apiPubHex, signed_body, agent_signature };
|
|
410
|
+
}
|
|
310
411
|
function buildIssuedAtEnvelope(creds) {
|
|
311
412
|
const signedBodyBytes = new TextEncoder().encode(
|
|
312
413
|
JSON.stringify({ issued_at: (/* @__PURE__ */ new Date()).toISOString() })
|
|
@@ -320,19 +421,19 @@ function buildIssuedAtEnvelope(creds) {
|
|
|
320
421
|
}
|
|
321
422
|
async function signIntent(intent, creds, binding, transport) {
|
|
322
423
|
if (!binding.walletId) {
|
|
323
|
-
return { ok: false, error: "
|
|
424
|
+
return { ok: false, error: "PayBox did not supply a wallet id for this operation." };
|
|
324
425
|
}
|
|
325
426
|
if (!binding.keyId || !binding.derivationPath) {
|
|
326
427
|
return {
|
|
327
428
|
ok: false,
|
|
328
|
-
error: "This wallet isn't provisioned for signing yet (missing key binding). Re-provision it in the
|
|
429
|
+
error: "This wallet isn't provisioned for signing yet (missing key binding). Re-provision it in the PayBox app, then retry."
|
|
329
430
|
};
|
|
330
431
|
}
|
|
331
432
|
const ecdsaSign = async (digest) => {
|
|
332
433
|
const result = await transport(buildEnvelope(digest, binding, creds));
|
|
333
434
|
const { r, s, recovery_id } = result.signature;
|
|
334
435
|
if (r == null || s == null || recovery_id == null) {
|
|
335
|
-
throw new Error("
|
|
436
|
+
throw new Error("the signing service returned an ECDSA result without {r, s, recovery_id}");
|
|
336
437
|
}
|
|
337
438
|
return {
|
|
338
439
|
r: `0x${r}`,
|
|
@@ -343,7 +444,7 @@ async function signIntent(intent, creds, binding, transport) {
|
|
|
343
444
|
const ed25519Sign = async (message) => {
|
|
344
445
|
const result = await transport(buildEnvelope(bytesToHex(message), binding, creds));
|
|
345
446
|
const sig = result.signature.signature;
|
|
346
|
-
if (!sig) throw new Error("
|
|
447
|
+
if (!sig) throw new Error("the signing service returned an Ed25519 result without a signature");
|
|
347
448
|
return hexToBytes(sig);
|
|
348
449
|
};
|
|
349
450
|
try {
|
|
@@ -412,6 +513,11 @@ async function signIntent(intent, creds, binding, transport) {
|
|
|
412
513
|
|
|
413
514
|
// src/signer.ts
|
|
414
515
|
function credsFromToken(token) {
|
|
516
|
+
if (token.trim().startsWith("pbx_live_")) {
|
|
517
|
+
throw new Error(
|
|
518
|
+
"invalid signing key: got a `pbx_live_` API key; expected a `pbxk1.` token (keypair only)"
|
|
519
|
+
);
|
|
520
|
+
}
|
|
415
521
|
const kp = parseToken(token);
|
|
416
522
|
if (!kp) {
|
|
417
523
|
throw new Error("invalid signing key: expected a `pbxk1.` token (keypair only)");
|
|
@@ -444,21 +550,37 @@ async function signWalletRequest(post, requestId, intent, creds, binding) {
|
|
|
444
550
|
function isSolanaRefreshablePlan(plan) {
|
|
445
551
|
const txs = plan.txs;
|
|
446
552
|
const solana = txs.some((t) => t.vm === "solana");
|
|
447
|
-
return solana && txs.some(
|
|
553
|
+
return solana && txs.some(
|
|
554
|
+
(t) => t.kind === "swap" || t.kind === "prediction_trade" || t.kind === "jupiter_swap"
|
|
555
|
+
);
|
|
448
556
|
}
|
|
449
|
-
async function
|
|
557
|
+
async function refreshPlan(post, requestId) {
|
|
450
558
|
const r = await post(`/agent/requests/${requestId}/refresh`, {});
|
|
451
559
|
if (!r || typeof r.plan !== "object" || r.plan === null) {
|
|
452
|
-
throw new Error("
|
|
560
|
+
throw new Error("refresh returned no plan");
|
|
453
561
|
}
|
|
454
|
-
|
|
562
|
+
return r.plan;
|
|
563
|
+
}
|
|
564
|
+
async function refreshSwapPlan(post, requestId) {
|
|
565
|
+
const refreshed = await refreshPlan(post, requestId);
|
|
455
566
|
if (!Array.isArray(refreshed.txs) || refreshed.txs.length === 0) {
|
|
456
567
|
throw new Error("swap refresh returned a plan with no txs");
|
|
457
568
|
}
|
|
458
569
|
return refreshed;
|
|
459
570
|
}
|
|
571
|
+
function isSolanaRailX402(plan) {
|
|
572
|
+
return plan.x402?.rail === "solana";
|
|
573
|
+
}
|
|
574
|
+
async function refreshX402SolanaTx(post, requestId) {
|
|
575
|
+
const refreshed = await refreshPlan(post, requestId);
|
|
576
|
+
const tx = refreshed.solana?.transaction;
|
|
577
|
+
if (typeof tx !== "string" || tx.length === 0) {
|
|
578
|
+
throw new Error("x402 refresh returned a plan with no Solana transaction");
|
|
579
|
+
}
|
|
580
|
+
return tx;
|
|
581
|
+
}
|
|
460
582
|
async function signSwapRequest(post, requestId, plan, creds) {
|
|
461
|
-
const signPlan = isSolanaRefreshablePlan(plan) ? await refreshSwapPlan(post, requestId
|
|
583
|
+
const signPlan = isSolanaRefreshablePlan(plan) ? await refreshSwapPlan(post, requestId) : plan;
|
|
462
584
|
let binding = {
|
|
463
585
|
walletId: signPlan.binding.wallet_id,
|
|
464
586
|
keyId: signPlan.binding.key_id ?? "",
|
|
@@ -479,7 +601,7 @@ async function signX402Request(post, requestId, plan, creds) {
|
|
|
479
601
|
if (binding.walletId && (!binding.keyId || !binding.derivationPath)) {
|
|
480
602
|
binding = await resolveBinding(post, requestId, creds);
|
|
481
603
|
}
|
|
482
|
-
const envelope = buildX402Envelope(
|
|
604
|
+
const envelope = isSolanaRailX402(plan) ? buildX402SolanaEnvelope(await refreshX402SolanaTx(post, requestId), binding, creds) : buildX402Envelope(
|
|
483
605
|
plan.typed_data,
|
|
484
606
|
binding,
|
|
485
607
|
creds
|
|
@@ -490,6 +612,10 @@ async function signX402Request(post, requestId, plan, creds) {
|
|
|
490
612
|
// src/client.ts
|
|
491
613
|
var DEFAULT_BASE_URL = "https://api.paybox.sh";
|
|
492
614
|
var EXPIRY_SKEW_MS = 6e4;
|
|
615
|
+
function signingResultForResponse(result, response) {
|
|
616
|
+
const exposesPlan = response.status === "pending_approval" || response.status === "pending_signature";
|
|
617
|
+
return { ...result, response, plan: exposesPlan ? result.plan : null };
|
|
618
|
+
}
|
|
493
619
|
var PayboxError = class extends Error {
|
|
494
620
|
constructor(status, body, context) {
|
|
495
621
|
super(`paybox ${context} failed (${status}): ${body}`);
|
|
@@ -525,9 +651,17 @@ var PayboxClient = class _PayboxClient {
|
|
|
525
651
|
}
|
|
526
652
|
return client;
|
|
527
653
|
}
|
|
528
|
-
|
|
654
|
+
signingCreds() {
|
|
655
|
+
if (!this.opts.signingKey) return null;
|
|
656
|
+
try {
|
|
657
|
+
return credsFromToken(this.opts.signingKey);
|
|
658
|
+
} catch {
|
|
659
|
+
return null;
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
/** Whether in-process signing is available (a parseable `pbxk1.` key is set). */
|
|
529
663
|
get canSign() {
|
|
530
|
-
return
|
|
664
|
+
return this.signingCreds() !== null;
|
|
531
665
|
}
|
|
532
666
|
async bearer() {
|
|
533
667
|
if (this.opts.token) return this.opts.token;
|
|
@@ -564,7 +698,8 @@ var PayboxClient = class _PayboxClient {
|
|
|
564
698
|
return text ? JSON.parse(text) : void 0;
|
|
565
699
|
}
|
|
566
700
|
poster = (path, body) => this.request("POST", path, { body });
|
|
567
|
-
/** List
|
|
701
|
+
/** List granted credentials plus the `ungranted` credentials (id, name, kind,
|
|
702
|
+
* safe metadata) the user owns but hasn't granted this connector. */
|
|
568
703
|
listCredentials() {
|
|
569
704
|
return this.request("GET", "/agent/grants");
|
|
570
705
|
}
|
|
@@ -574,6 +709,16 @@ var PayboxClient = class _PayboxClient {
|
|
|
574
709
|
query: { address: args.address, network_ids: args.networkIds }
|
|
575
710
|
});
|
|
576
711
|
}
|
|
712
|
+
/** Verify one confirmed Solana transaction's exact wallet/mint effect, then
|
|
713
|
+
* read the current balance at or after its slot (`verify_solana_balance`). */
|
|
714
|
+
verifySolanaBalance(args) {
|
|
715
|
+
const body = {
|
|
716
|
+
address: args.address,
|
|
717
|
+
token_mint: args.tokenMint,
|
|
718
|
+
transaction_signature: args.transactionSignature
|
|
719
|
+
};
|
|
720
|
+
return this.request("POST", "/agent/portfolio/verify-solana-balance", { body });
|
|
721
|
+
}
|
|
577
722
|
/** Charge a card credential (the `request_payment` tool). */
|
|
578
723
|
requestPayment(args) {
|
|
579
724
|
const operation = {
|
|
@@ -599,72 +744,88 @@ var PayboxClient = class _PayboxClient {
|
|
|
599
744
|
/** Sign with a wallet credential (the `request_wallet_sign` tool). Signs
|
|
600
745
|
* in-process and returns the completed request when a signing key is set and
|
|
601
746
|
* the request clears immediately; otherwise returns the pending response.
|
|
602
|
-
* Uses `/agent/wallet-sign`, which echoes the wallet binding
|
|
603
|
-
* need not resolve it
|
|
747
|
+
* Uses `/agent/wallet-sign`, which echoes the wallet binding only while
|
|
748
|
+
* approval or signature is pending so the signer need not resolve it
|
|
749
|
+
* separately. Terminal responses carry a null binding. */
|
|
604
750
|
async requestWalletSign(args, opts = {}) {
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
751
|
+
return this.withExpiryRetry(
|
|
752
|
+
async () => {
|
|
753
|
+
const operation = {
|
|
754
|
+
type: "wallet_sign",
|
|
755
|
+
credential_id: args.credentialId,
|
|
756
|
+
intent: args.intent
|
|
757
|
+
};
|
|
758
|
+
const { response, binding } = await this.request(
|
|
759
|
+
"POST",
|
|
760
|
+
"/agent/wallet-sign",
|
|
761
|
+
{ body: { operation } }
|
|
762
|
+
);
|
|
763
|
+
const creds = this.signingCreds();
|
|
764
|
+
if ((opts.autoSign ?? true) && response.status === "pending_signature" && creds) {
|
|
765
|
+
const sign = () => signWalletRequest(this.poster, response.request_id, args.intent, creds, binding);
|
|
766
|
+
await this.signWithReconcile(response.request_id, sign);
|
|
767
|
+
return this.getRequest(response.request_id);
|
|
768
|
+
}
|
|
769
|
+
return response;
|
|
770
|
+
},
|
|
771
|
+
(r) => r
|
|
614
772
|
);
|
|
615
|
-
if ((opts.autoSign ?? true) && response.status === "pending_signature" && this.opts.signingKey) {
|
|
616
|
-
await signWalletRequest(
|
|
617
|
-
this.poster,
|
|
618
|
-
response.request_id,
|
|
619
|
-
args.intent,
|
|
620
|
-
credsFromToken(this.opts.signingKey),
|
|
621
|
-
binding
|
|
622
|
-
);
|
|
623
|
-
return this.getRequest(response.request_id);
|
|
624
|
-
}
|
|
625
|
-
return response;
|
|
626
773
|
}
|
|
627
774
|
/** Swap tokens from a wallet credential (the `request_swap` tool). Returns the
|
|
628
|
-
* parked response
|
|
629
|
-
*
|
|
775
|
+
* parked response and its sign-ready plan while approval or signature is
|
|
776
|
+
* pending; terminal responses carry a null plan. Signs + submits in-process
|
|
777
|
+
* when a signing key is set and the swap cleared immediately. */
|
|
630
778
|
async requestSwap(args, opts = {}) {
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
779
|
+
return this.withExpiryRetry(
|
|
780
|
+
async () => {
|
|
781
|
+
const operation = {
|
|
782
|
+
type: "swap",
|
|
783
|
+
credential_id: args.credentialId,
|
|
784
|
+
src_chain: args.srcChain,
|
|
785
|
+
dst_chain: args.dstChain ?? args.srcChain,
|
|
786
|
+
src_token: args.srcToken,
|
|
787
|
+
dst_token: args.dstToken,
|
|
788
|
+
amount: args.amount,
|
|
789
|
+
swap_direction: args.swapDirection ?? "exact-amount-in",
|
|
790
|
+
// Keep in sync with the request_swap MCP tool default (server side).
|
|
791
|
+
slippage_bps: args.slippageBps ?? 150,
|
|
792
|
+
recipient: args.recipient ?? null,
|
|
793
|
+
value_cents: args.valueCents ?? null
|
|
794
|
+
};
|
|
795
|
+
const result = await this.request("POST", "/agent/swap", {
|
|
796
|
+
body: { operation }
|
|
797
|
+
});
|
|
798
|
+
return this.completeSwap(result, opts);
|
|
799
|
+
},
|
|
800
|
+
(r) => r.response
|
|
801
|
+
);
|
|
802
|
+
}
|
|
803
|
+
/** Complete a swap request: optionally wait out a `pending_approval` dwell,
|
|
804
|
+
* then sign the plan in-process and submit envelopes. Signing first refreshes
|
|
805
|
+
* the parked request via `/agent/requests/{id}/refresh`, so a long approval
|
|
806
|
+
* dwell never signs stale bytes. */
|
|
652
807
|
async completeSwap(result, opts) {
|
|
653
|
-
|
|
808
|
+
const creds = this.signingCreds();
|
|
809
|
+
if (!(opts.autoSign ?? true) || !creds) {
|
|
810
|
+
return signingResultForResponse(result, result.response);
|
|
811
|
+
}
|
|
654
812
|
let response = result.response;
|
|
655
813
|
if (response.status === "pending_approval" && opts.waitForApproval) {
|
|
656
814
|
response = await this.waitWhileApproval(response.request_id, opts.waitForApproval);
|
|
657
815
|
}
|
|
658
816
|
if (response.status !== "pending_signature") {
|
|
659
|
-
return
|
|
817
|
+
return signingResultForResponse(result, response);
|
|
818
|
+
}
|
|
819
|
+
const plan = result.plan;
|
|
820
|
+
if (!plan) {
|
|
821
|
+
throw new Error("paybox returned pending_signature without a swap signing plan");
|
|
660
822
|
}
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
result
|
|
665
|
-
|
|
823
|
+
const sign = () => signSwapRequest(this.poster, response.request_id, plan, creds);
|
|
824
|
+
await this.signWithReconcile(response.request_id, sign);
|
|
825
|
+
return signingResultForResponse(
|
|
826
|
+
result,
|
|
827
|
+
await this.requestAfterSign(response.request_id, opts.waitForApproval)
|
|
666
828
|
);
|
|
667
|
-
return { ...result, response: await this.getRequest(response.request_id) };
|
|
668
829
|
}
|
|
669
830
|
/** Poll while the request sits in `pending_approval` (the user is approving
|
|
670
831
|
* with their passkey); returns the first non-approval state. */
|
|
@@ -678,63 +839,121 @@ var PayboxClient = class _PayboxClient {
|
|
|
678
839
|
await new Promise((res) => setTimeout(res, interval));
|
|
679
840
|
}
|
|
680
841
|
}
|
|
681
|
-
/**
|
|
682
|
-
* `
|
|
683
|
-
*
|
|
684
|
-
*
|
|
842
|
+
/** Run an operation and, if it terminates on a server-marked expiry
|
|
843
|
+
* (`retryable` on a terminal `error`/`denied`: the window elapsed and nothing
|
|
844
|
+
* was signed or spent), re-issue it exactly once (SPE-456). Never loops: the
|
|
845
|
+
* second attempt's result is returned as-is. */
|
|
846
|
+
async withExpiryRetry(run, responseOf) {
|
|
847
|
+
const first = await run();
|
|
848
|
+
const r = responseOf(first);
|
|
849
|
+
if ((r.status === "error" || r.status === "denied") && r.retryable) {
|
|
850
|
+
return run();
|
|
851
|
+
}
|
|
852
|
+
return first;
|
|
853
|
+
}
|
|
854
|
+
/** Sign a parked request; if the sign/submit call fails, reconcile against the
|
|
855
|
+
* authoritative state once (SPE-456). The server leaves a pre-broadcast
|
|
856
|
+
* completion failure recoverable — the claim is released and the row stays
|
|
857
|
+
* `pending_signature` — so re-sign a still-open request a single time; a
|
|
858
|
+
* terminal or already-broadcast row is left as-is for the caller to read (and
|
|
859
|
+
* for `withExpiryRetry` to re-issue if it is retryable). Bounded to one extra
|
|
860
|
+
* attempt: a second failure propagates. */
|
|
861
|
+
async signWithReconcile(requestId, sign) {
|
|
862
|
+
try {
|
|
863
|
+
await sign();
|
|
864
|
+
} catch {
|
|
865
|
+
const reconciled = await this.getRequest(requestId);
|
|
866
|
+
if (reconciled.status !== "pending_signature") return;
|
|
867
|
+
await sign();
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
/** Discover x402 services with the original array-returning response. */
|
|
685
871
|
async discoverServices(query, opts = {}) {
|
|
686
|
-
const
|
|
687
|
-
query: {
|
|
872
|
+
const result = await this.request("GET", "/agent/services", {
|
|
873
|
+
query: {
|
|
874
|
+
query,
|
|
875
|
+
network: opts.network === "all" ? void 0 : opts.network,
|
|
876
|
+
raw: opts.raw ? "true" : void 0
|
|
877
|
+
}
|
|
878
|
+
});
|
|
879
|
+
return result.services;
|
|
880
|
+
}
|
|
881
|
+
/** Discover one bounded page of x402 services. Reuse the returned cursor with
|
|
882
|
+
* the same query and network while `has_more` is true. */
|
|
883
|
+
async discoverServicesPage(query, opts = {}) {
|
|
884
|
+
return this.request("GET", "/agent/services", {
|
|
885
|
+
query: {
|
|
886
|
+
query,
|
|
887
|
+
network: opts.network === "all" ? void 0 : opts.network,
|
|
888
|
+
limit: (opts.limit ?? 10).toString(),
|
|
889
|
+
cursor: opts.cursor,
|
|
890
|
+
raw: opts.raw ? "true" : void 0
|
|
891
|
+
}
|
|
688
892
|
});
|
|
689
|
-
return r.services;
|
|
690
893
|
}
|
|
691
894
|
/** Pay a paid x402 endpoint from a wallet credential (the `pay_x402` tool,
|
|
692
895
|
* header mode). Signs in-process when a signing key is set and the payment
|
|
693
896
|
* clears immediately — then the X-PAYMENT header is on the completed
|
|
694
897
|
* response's `output.value.x_payment` (replay it on the original request). */
|
|
695
898
|
async payX402(args, opts = {}) {
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
899
|
+
return this.withExpiryRetry(
|
|
900
|
+
async () => {
|
|
901
|
+
const result = await this.request("POST", "/agent/x402", {
|
|
902
|
+
body: {
|
|
903
|
+
credential_id: args.credentialId,
|
|
904
|
+
accepts: args.accepts,
|
|
905
|
+
resource_url: args.resourceUrl
|
|
906
|
+
}
|
|
907
|
+
});
|
|
908
|
+
return this.completeX402(result, opts);
|
|
909
|
+
},
|
|
910
|
+
(r) => r.response
|
|
911
|
+
);
|
|
704
912
|
}
|
|
705
913
|
/** Fetch a paid x402 resource and have paybox pay for it (the `use_service`
|
|
706
914
|
* tool, gateway mode): paybox probes the url, pays the 402, re-fetches with the
|
|
707
915
|
* payment, and returns the resource's reply on `output.value.response`. */
|
|
708
916
|
async useService(args, opts = {}) {
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
917
|
+
return this.withExpiryRetry(
|
|
918
|
+
async () => {
|
|
919
|
+
const result = await this.request("POST", "/agent/use-service", {
|
|
920
|
+
body: {
|
|
921
|
+
credential_id: args.credentialId,
|
|
922
|
+
url: args.url,
|
|
923
|
+
method: args.method,
|
|
924
|
+
body: args.body
|
|
925
|
+
}
|
|
926
|
+
});
|
|
927
|
+
return this.completeX402(result, opts);
|
|
928
|
+
},
|
|
929
|
+
(r) => r.response
|
|
930
|
+
);
|
|
718
931
|
}
|
|
719
932
|
/** Shared completion for the two x402 entry points: optionally wait out a
|
|
720
933
|
* `pending_approval` dwell, then sign the parked typed data in-process and
|
|
721
934
|
* return the finished request, exactly like the swap path. */
|
|
722
935
|
async completeX402(result, opts) {
|
|
723
|
-
|
|
936
|
+
const creds = this.signingCreds();
|
|
937
|
+
if (!(opts.autoSign ?? true) || !creds) {
|
|
938
|
+
return signingResultForResponse(result, result.response);
|
|
939
|
+
}
|
|
724
940
|
let response = result.response;
|
|
725
941
|
if (response.status === "pending_approval" && opts.waitForApproval) {
|
|
726
942
|
response = await this.waitWhileApproval(response.request_id, opts.waitForApproval);
|
|
727
943
|
}
|
|
728
944
|
if (response.status !== "pending_signature") {
|
|
729
|
-
return
|
|
945
|
+
return signingResultForResponse(result, response);
|
|
946
|
+
}
|
|
947
|
+
const plan = result.plan;
|
|
948
|
+
if (!plan) {
|
|
949
|
+
throw new Error("paybox returned pending_signature without an x402 signing plan");
|
|
730
950
|
}
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
result
|
|
735
|
-
|
|
951
|
+
const sign = () => signX402Request(this.poster, response.request_id, plan, creds);
|
|
952
|
+
await this.signWithReconcile(response.request_id, sign);
|
|
953
|
+
return signingResultForResponse(
|
|
954
|
+
result,
|
|
955
|
+
await this.requestAfterSign(response.request_id, opts.waitForApproval)
|
|
736
956
|
);
|
|
737
|
-
return { ...result, response: await this.getRequest(response.request_id) };
|
|
738
957
|
}
|
|
739
958
|
/** Claim the usable card credentials of an approved payment (the
|
|
740
959
|
* `claim_payment_credentials` tool). One-time: the claim is consumed. */
|
|
@@ -754,15 +973,13 @@ var PayboxClient = class _PayboxClient {
|
|
|
754
973
|
return this.request("GET", "/agent/buy-link", {
|
|
755
974
|
query: {
|
|
756
975
|
credential_id: args.credentialId,
|
|
976
|
+
destination_chain: args.destinationChain,
|
|
757
977
|
currency_code: args.currencyCode,
|
|
758
978
|
amount_usd: args.amountUsd?.toString()
|
|
759
979
|
}
|
|
760
980
|
});
|
|
761
981
|
}
|
|
762
|
-
// ---- World
|
|
763
|
-
// the `world` plugin in the paybox app. Reads are discovery-only; `worldTrade`
|
|
764
|
-
// parks a force-approved PredictionTrade the SDK signs in-process after the
|
|
765
|
-
// user approves (pass `waitForApproval` to wait + sign headlessly). ----
|
|
982
|
+
// ---- World REST discovery; money tools require the geofenced MCP window. ----
|
|
766
983
|
/** Browse World prediction markets (the `world_find_markets` tool). */
|
|
767
984
|
worldMarkets(args = {}) {
|
|
768
985
|
return this.request("GET", "/agent/world/markets", {
|
|
@@ -812,28 +1029,13 @@ var PayboxClient = class _PayboxClient {
|
|
|
812
1029
|
body: { addresses }
|
|
813
1030
|
});
|
|
814
1031
|
}
|
|
815
|
-
/**
|
|
1032
|
+
/** A wallet's World prediction-market positions: classified `positions` /
|
|
1033
|
+
* `settled` entries (each with a `position_result`) plus `settlement_cash`
|
|
1034
|
+
* for a settled winner's CASH cash-out (swap CASH to USDC via request_swap).
|
|
1035
|
+
* The `world_positions` tool. */
|
|
816
1036
|
worldPositions(address) {
|
|
817
1037
|
return this.request("GET", "/agent/world/positions", { query: { address } });
|
|
818
1038
|
}
|
|
819
|
-
/** Trade a World prediction market (the `world_buy_outcome` /
|
|
820
|
-
* `world_change_position` / `world_redeem` tools). Always parks as
|
|
821
|
-
* `pending_approval` (trades are force-approved); pass
|
|
822
|
-
* `{ waitForApproval: true }` to wait for the user's passkey approval and
|
|
823
|
-
* sign in-process. */
|
|
824
|
-
async worldTrade(args, opts = {}) {
|
|
825
|
-
const result = await this.request("POST", "/agent/world/trade", {
|
|
826
|
-
body: {
|
|
827
|
-
credential_id: args.credentialId,
|
|
828
|
-
market_mint: args.marketMint,
|
|
829
|
-
side: args.side,
|
|
830
|
-
size: args.size,
|
|
831
|
-
slippage_bps: args.slippageBps,
|
|
832
|
-
value_cents: args.valueCents
|
|
833
|
-
}
|
|
834
|
-
});
|
|
835
|
-
return this.completeSwap(result, opts);
|
|
836
|
-
}
|
|
837
1039
|
// ---- Hyperliquid plugin (read-only spot discovery) — requires the user to
|
|
838
1040
|
// have enabled the `hyperliquid` plugin in the paybox app. ----
|
|
839
1041
|
/** Hyperliquid spot markets (the `hyperliquid_markets` tool). */
|
|
@@ -852,6 +1054,12 @@ var PayboxClient = class _PayboxClient {
|
|
|
852
1054
|
getRequest(requestId) {
|
|
853
1055
|
return this.request("GET", `/agent/requests/${requestId}`);
|
|
854
1056
|
}
|
|
1057
|
+
/** After in-process sign, `--wait` polls through settlement/confirmation;
|
|
1058
|
+
* without it, return the first snapshot (the historical no-wait contract). */
|
|
1059
|
+
requestAfterSign(requestId, wait) {
|
|
1060
|
+
if (!wait) return this.getRequest(requestId);
|
|
1061
|
+
return this.waitForRequest(requestId, typeof wait === "object" ? wait : {});
|
|
1062
|
+
}
|
|
855
1063
|
/** Poll until the request leaves a pending state (or the timeout elapses). */
|
|
856
1064
|
async waitForRequest(requestId, opts = {}) {
|
|
857
1065
|
const interval = opts.intervalMs ?? 2e3;
|
|
@@ -871,6 +1079,7 @@ export {
|
|
|
871
1079
|
agentClientId,
|
|
872
1080
|
openBrowser,
|
|
873
1081
|
loginLoopback,
|
|
1082
|
+
loginDevice,
|
|
874
1083
|
refreshTokens,
|
|
875
1084
|
configDir,
|
|
876
1085
|
configFile,
|
|
@@ -880,4 +1089,4 @@ export {
|
|
|
880
1089
|
PayboxError,
|
|
881
1090
|
PayboxClient
|
|
882
1091
|
};
|
|
883
|
-
//# sourceMappingURL=chunk-
|
|
1092
|
+
//# sourceMappingURL=chunk-OBZSVPP7.js.map
|