@paybox-sh/sdk 0.1.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/LICENSE +201 -0
- package/README.md +100 -0
- package/dist/chunk-23N5RHOB.js +621 -0
- package/dist/chunk-23N5RHOB.js.map +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +162 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +250 -0
- package/dist/index.js +23 -0
- package/dist/index.js.map +1 -0
- package/package.json +61 -0
|
@@ -0,0 +1,621 @@
|
|
|
1
|
+
// src/auth.ts
|
|
2
|
+
import { spawn } from "node:child_process";
|
|
3
|
+
import { createHash, randomBytes } from "node:crypto";
|
|
4
|
+
import { createServer } from "node:http";
|
|
5
|
+
function mcpResource(baseUrl) {
|
|
6
|
+
return `${baseUrl.replace(/\/+$/, "")}/mcp`;
|
|
7
|
+
}
|
|
8
|
+
async function metadata(baseUrl) {
|
|
9
|
+
const url = `${baseUrl.replace(/\/+$/, "")}/.well-known/oauth-authorization-server`;
|
|
10
|
+
const res = await fetch(url);
|
|
11
|
+
if (!res.ok) {
|
|
12
|
+
throw new Error(`could not read OAuth metadata from ${url} (${res.status})`);
|
|
13
|
+
}
|
|
14
|
+
return await res.json();
|
|
15
|
+
}
|
|
16
|
+
function base64url(b) {
|
|
17
|
+
return b.toString("base64url");
|
|
18
|
+
}
|
|
19
|
+
function pkce() {
|
|
20
|
+
const verifier = base64url(randomBytes(32));
|
|
21
|
+
const challenge = base64url(createHash("sha256").update(verifier).digest());
|
|
22
|
+
return { verifier, challenge };
|
|
23
|
+
}
|
|
24
|
+
function openBrowser(url) {
|
|
25
|
+
const [cmd, args] = process.platform === "darwin" ? ["open", [url]] : process.platform === "win32" ? ["rundll32", ["url.dll,FileProtocolHandler", url]] : ["xdg-open", [url]];
|
|
26
|
+
try {
|
|
27
|
+
spawn(cmd, [...args], { stdio: "ignore", detached: true }).unref();
|
|
28
|
+
} catch {
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
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>';
|
|
32
|
+
async function loginLoopback(baseUrl, opts = {}) {
|
|
33
|
+
const scope = opts.scope ?? "mcp offline_access";
|
|
34
|
+
const clientName = opts.clientName ?? "Paybox CLI";
|
|
35
|
+
const log = opts.log ?? ((m) => console.error(m));
|
|
36
|
+
const meta = await metadata(baseUrl);
|
|
37
|
+
const resource = mcpResource(baseUrl);
|
|
38
|
+
let resolveCode;
|
|
39
|
+
let rejectCode;
|
|
40
|
+
const codePromise = new Promise((res, rej) => {
|
|
41
|
+
resolveCode = res;
|
|
42
|
+
rejectCode = rej;
|
|
43
|
+
});
|
|
44
|
+
const server = createServer((req, res) => {
|
|
45
|
+
const u = new URL(req.url ?? "/", "http://127.0.0.1");
|
|
46
|
+
if (u.pathname !== "/callback") {
|
|
47
|
+
res.writeHead(404).end();
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
res.writeHead(200, { "content-type": "text/html" }).end(CALLBACK_PAGE);
|
|
51
|
+
const code = u.searchParams.get("code");
|
|
52
|
+
if (code) resolveCode({ code, state: u.searchParams.get("state") });
|
|
53
|
+
else rejectCode(new Error(u.searchParams.get("error") ?? "authorization failed"));
|
|
54
|
+
});
|
|
55
|
+
await new Promise((r) => server.listen(0, "127.0.0.1", r));
|
|
56
|
+
const port = server.address().port;
|
|
57
|
+
const redirectUri = `http://127.0.0.1:${port}/callback`;
|
|
58
|
+
const timeoutMs = opts.timeoutMs ?? 3e5;
|
|
59
|
+
const timer = setTimeout(
|
|
60
|
+
() => rejectCode(new Error(`login timed out after ${Math.round(timeoutMs / 1e3)}s \u2014 no approval received`)),
|
|
61
|
+
timeoutMs
|
|
62
|
+
);
|
|
63
|
+
timer.unref();
|
|
64
|
+
try {
|
|
65
|
+
const reg = await fetch(meta.registration_endpoint, {
|
|
66
|
+
method: "POST",
|
|
67
|
+
headers: { "content-type": "application/json" },
|
|
68
|
+
body: JSON.stringify({
|
|
69
|
+
client_name: clientName,
|
|
70
|
+
redirect_uris: [redirectUri],
|
|
71
|
+
token_endpoint_auth_method: "none",
|
|
72
|
+
grant_types: ["authorization_code", "refresh_token"],
|
|
73
|
+
response_types: ["code"]
|
|
74
|
+
})
|
|
75
|
+
});
|
|
76
|
+
if (!reg.ok) throw new Error(`client registration failed (${reg.status}): ${await reg.text()}`);
|
|
77
|
+
const { client_id: clientId } = await reg.json();
|
|
78
|
+
const { verifier, challenge } = pkce();
|
|
79
|
+
const state = base64url(randomBytes(16));
|
|
80
|
+
const authUrl = `${meta.authorization_endpoint}?response_type=code&client_id=${encodeURIComponent(clientId)}&redirect_uri=${encodeURIComponent(redirectUri)}&code_challenge=${challenge}&code_challenge_method=S256&scope=${encodeURIComponent(scope)}&state=${state}&resource=${encodeURIComponent(resource)}`;
|
|
81
|
+
log(`Opening your browser to approve access:
|
|
82
|
+
${authUrl}
|
|
83
|
+
`);
|
|
84
|
+
openBrowser(authUrl);
|
|
85
|
+
const { code, state: returnedState } = await codePromise;
|
|
86
|
+
if (returnedState !== state) throw new Error("OAuth state mismatch \u2014 aborting");
|
|
87
|
+
const form = new URLSearchParams({
|
|
88
|
+
grant_type: "authorization_code",
|
|
89
|
+
code,
|
|
90
|
+
redirect_uri: redirectUri,
|
|
91
|
+
client_id: clientId,
|
|
92
|
+
code_verifier: verifier,
|
|
93
|
+
resource
|
|
94
|
+
});
|
|
95
|
+
const tok = await fetch(meta.token_endpoint, {
|
|
96
|
+
method: "POST",
|
|
97
|
+
headers: { "content-type": "application/x-www-form-urlencoded" },
|
|
98
|
+
body: form
|
|
99
|
+
});
|
|
100
|
+
if (!tok.ok) throw new Error(`token exchange failed (${tok.status}): ${await tok.text()}`);
|
|
101
|
+
const t = await tok.json();
|
|
102
|
+
return {
|
|
103
|
+
clientId,
|
|
104
|
+
accessToken: t.access_token,
|
|
105
|
+
refreshToken: t.refresh_token,
|
|
106
|
+
expiresAt: t.expires_in ? Date.now() + t.expires_in * 1e3 : void 0,
|
|
107
|
+
resource
|
|
108
|
+
};
|
|
109
|
+
} finally {
|
|
110
|
+
clearTimeout(timer);
|
|
111
|
+
server.close();
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
async function refreshTokens(baseUrl, current) {
|
|
115
|
+
if (!current.refreshToken) throw new Error("no refresh token \u2014 run `paybox login` again");
|
|
116
|
+
const meta = await metadata(baseUrl);
|
|
117
|
+
const resource = current.resource ?? mcpResource(baseUrl);
|
|
118
|
+
const form = new URLSearchParams({
|
|
119
|
+
grant_type: "refresh_token",
|
|
120
|
+
refresh_token: current.refreshToken,
|
|
121
|
+
client_id: current.clientId,
|
|
122
|
+
resource
|
|
123
|
+
});
|
|
124
|
+
const res = await fetch(meta.token_endpoint, {
|
|
125
|
+
method: "POST",
|
|
126
|
+
headers: { "content-type": "application/x-www-form-urlencoded" },
|
|
127
|
+
body: form
|
|
128
|
+
});
|
|
129
|
+
if (!res.ok) throw new Error(`token refresh failed (${res.status}): ${await res.text()}`);
|
|
130
|
+
const t = await res.json();
|
|
131
|
+
return {
|
|
132
|
+
...current,
|
|
133
|
+
accessToken: t.access_token,
|
|
134
|
+
refreshToken: t.refresh_token ?? current.refreshToken,
|
|
135
|
+
expiresAt: t.expires_in ? Date.now() + t.expires_in * 1e3 : void 0,
|
|
136
|
+
resource
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// src/config.ts
|
|
141
|
+
import { chmodSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
142
|
+
import { homedir } from "node:os";
|
|
143
|
+
import { join } from "node:path";
|
|
144
|
+
function configDir() {
|
|
145
|
+
if (process.env.PAYBOX_CONFIG_DIR) return process.env.PAYBOX_CONFIG_DIR;
|
|
146
|
+
const base = process.env.XDG_CONFIG_HOME ?? join(homedir(), ".config");
|
|
147
|
+
return join(base, "paybox");
|
|
148
|
+
}
|
|
149
|
+
function configFile() {
|
|
150
|
+
return join(configDir(), "config.json");
|
|
151
|
+
}
|
|
152
|
+
function loadConfig() {
|
|
153
|
+
try {
|
|
154
|
+
return JSON.parse(readFileSync(configFile(), "utf8"));
|
|
155
|
+
} catch {
|
|
156
|
+
return {};
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
function saveConfig(config) {
|
|
160
|
+
mkdirSync(configDir(), { recursive: true, mode: 448 });
|
|
161
|
+
writeFileSync(configFile(), `${JSON.stringify(config, null, 2)}
|
|
162
|
+
`, { mode: 384 });
|
|
163
|
+
if (process.platform !== "win32") chmodSync(configFile(), 384);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// 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
|
|
167
|
+
var TOKEN_PREFIX = "pbxk1.";
|
|
168
|
+
function validateHex(s, label) {
|
|
169
|
+
const trimmed = s.trim();
|
|
170
|
+
if (!trimmed) return `${label} is required`;
|
|
171
|
+
const body = trimmed.startsWith("0x") || trimmed.startsWith("0X") ? trimmed.slice(2) : trimmed;
|
|
172
|
+
if (body.length === 0) return `${label} is empty`;
|
|
173
|
+
if (body.length % 2 !== 0) return `${label} must have an even number of hex digits`;
|
|
174
|
+
if (!/^[0-9a-fA-F]+$/.test(body)) return `${label} must be hexadecimal`;
|
|
175
|
+
return null;
|
|
176
|
+
}
|
|
177
|
+
function b64urlDecode(s) {
|
|
178
|
+
let b = s.replace(/-/g, "+").replace(/_/g, "/");
|
|
179
|
+
while (b.length % 4) b += "=";
|
|
180
|
+
return atob(b);
|
|
181
|
+
}
|
|
182
|
+
function parseToken(input) {
|
|
183
|
+
const t = input.trim();
|
|
184
|
+
if (!t) return null;
|
|
185
|
+
const body = t.startsWith(TOKEN_PREFIX) ? t.slice(TOKEN_PREFIX.length) : t;
|
|
186
|
+
let obj;
|
|
187
|
+
try {
|
|
188
|
+
obj = JSON.parse(b64urlDecode(body));
|
|
189
|
+
} catch {
|
|
190
|
+
return null;
|
|
191
|
+
}
|
|
192
|
+
if (!obj || typeof obj !== "object") return null;
|
|
193
|
+
const o = obj;
|
|
194
|
+
const apiPubHex = String(o.p ?? o.apiPubHex ?? "").trim();
|
|
195
|
+
const apiPrivHex = String(o.s ?? o.apiPrivHex ?? "").trim();
|
|
196
|
+
if (!apiPubHex || !apiPrivHex) return null;
|
|
197
|
+
if (validateHex(apiPubHex, "key") || validateHex(apiPrivHex, "key")) return null;
|
|
198
|
+
return { apiPubHex, apiPrivHex };
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// 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
|
|
202
|
+
import { Buffer } from "buffer";
|
|
203
|
+
import nacl from "tweetnacl";
|
|
204
|
+
import { MoonXEvmSigner, EvmSchemeMismatchError } from "@moon-x/evm-adapter";
|
|
205
|
+
import { MoonXSolanaSigner, SolanaSchemeMismatchError } from "@moon-x/solana-adapter";
|
|
206
|
+
import { Transaction } from "@solana/web3.js";
|
|
207
|
+
import { keccak256, serializeTransaction } from "viem";
|
|
208
|
+
function reviveTx(w) {
|
|
209
|
+
return {
|
|
210
|
+
type: "eip1559",
|
|
211
|
+
to: w.to,
|
|
212
|
+
value: BigInt(w.value),
|
|
213
|
+
data: w.data,
|
|
214
|
+
chainId: w.chainId,
|
|
215
|
+
gas: BigInt(w.gas),
|
|
216
|
+
maxPriorityFeePerGas: BigInt(w.maxPriorityFeePerGas),
|
|
217
|
+
maxFeePerGas: BigInt(w.maxFeePerGas),
|
|
218
|
+
nonce: w.nonce
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
function hexToBytes(hex) {
|
|
222
|
+
return new Uint8Array(Buffer.from(hex.replace(/^0x/, ""), "hex"));
|
|
223
|
+
}
|
|
224
|
+
function bytesToHex(bytes) {
|
|
225
|
+
return Buffer.from(bytes).toString("hex");
|
|
226
|
+
}
|
|
227
|
+
function buildEnvelope(rawSigningPayloadHex, binding, creds) {
|
|
228
|
+
const signedPayload = {
|
|
229
|
+
raw_signing_payload: rawSigningPayloadHex.replace(/^0x/, "").toLowerCase(),
|
|
230
|
+
key_id: binding.keyId,
|
|
231
|
+
derivation_path: binding.derivationPath,
|
|
232
|
+
issued_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
233
|
+
};
|
|
234
|
+
const signedBodyBytes = new TextEncoder().encode(JSON.stringify(signedPayload));
|
|
235
|
+
const keyPair = nacl.sign.keyPair.fromSeed(hexToBytes(creds.apiPrivHex));
|
|
236
|
+
const agentSig = nacl.sign.detached(signedBodyBytes, keyPair.secretKey);
|
|
237
|
+
return {
|
|
238
|
+
signed_body: bytesToHex(signedBodyBytes),
|
|
239
|
+
agent_signature: bytesToHex(agentSig)
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
function eip1559SigningHash(tx) {
|
|
243
|
+
return keccak256(serializeTransaction(reviveTx(tx)));
|
|
244
|
+
}
|
|
245
|
+
function solanaSigningPayload(base64Tx) {
|
|
246
|
+
const raw = Buffer.from(base64Tx, "base64");
|
|
247
|
+
let i = 0;
|
|
248
|
+
let sigCount = 0;
|
|
249
|
+
let shift = 0;
|
|
250
|
+
for (; ; ) {
|
|
251
|
+
const b = raw[i++];
|
|
252
|
+
sigCount |= (b & 127) << shift;
|
|
253
|
+
if ((b & 128) === 0) break;
|
|
254
|
+
shift += 7;
|
|
255
|
+
}
|
|
256
|
+
if (sigCount !== 1) {
|
|
257
|
+
throw new Error(`expected a single-signer solana tx, got ${sigCount} signature slot(s)`);
|
|
258
|
+
}
|
|
259
|
+
return bytesToHex(new Uint8Array(raw.subarray(i + 64)));
|
|
260
|
+
}
|
|
261
|
+
function buildSwapEnvelopes(txs, binding, creds) {
|
|
262
|
+
if (!binding.walletId) {
|
|
263
|
+
throw new Error("paybox did not supply a wallet id for this operation.");
|
|
264
|
+
}
|
|
265
|
+
if (!binding.keyId || !binding.derivationPath) {
|
|
266
|
+
throw new Error(
|
|
267
|
+
"This wallet isn't provisioned for signing yet (missing key binding). Re-provision it in the paybox app, then retry."
|
|
268
|
+
);
|
|
269
|
+
}
|
|
270
|
+
return txs.map((t) => {
|
|
271
|
+
const payload = t.vm === "solana" ? solanaSigningPayload(t.transaction) : eip1559SigningHash(t.transaction);
|
|
272
|
+
const { signed_body, agent_signature } = buildEnvelope(payload, binding, creds);
|
|
273
|
+
return { api_pub: creds.apiPubHex, signed_body, agent_signature };
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
function buildIssuedAtEnvelope(creds) {
|
|
277
|
+
const signedBodyBytes = new TextEncoder().encode(
|
|
278
|
+
JSON.stringify({ issued_at: (/* @__PURE__ */ new Date()).toISOString() })
|
|
279
|
+
);
|
|
280
|
+
const keyPair = nacl.sign.keyPair.fromSeed(hexToBytes(creds.apiPrivHex));
|
|
281
|
+
const agentSig = nacl.sign.detached(signedBodyBytes, keyPair.secretKey);
|
|
282
|
+
return {
|
|
283
|
+
signed_body: bytesToHex(signedBodyBytes),
|
|
284
|
+
agent_signature: bytesToHex(agentSig)
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
async function signIntent(intent, creds, binding, transport) {
|
|
288
|
+
if (!binding.walletId) {
|
|
289
|
+
return { ok: false, error: "paybox did not supply a wallet id for this operation." };
|
|
290
|
+
}
|
|
291
|
+
if (!binding.keyId || !binding.derivationPath) {
|
|
292
|
+
return {
|
|
293
|
+
ok: false,
|
|
294
|
+
error: "This wallet isn't provisioned for signing yet (missing key binding). Re-provision it in the paybox app, then retry."
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
const ecdsaSign = async (digest) => {
|
|
298
|
+
const result = await transport(buildEnvelope(digest, binding, creds));
|
|
299
|
+
const { r, s, recovery_id } = result.signature;
|
|
300
|
+
if (r == null || s == null || recovery_id == null) {
|
|
301
|
+
throw new Error("MoonX returned an ECDSA result without {r, s, recovery_id}");
|
|
302
|
+
}
|
|
303
|
+
return {
|
|
304
|
+
r: `0x${r}`,
|
|
305
|
+
s: `0x${s}`,
|
|
306
|
+
yParity: recovery_id
|
|
307
|
+
};
|
|
308
|
+
};
|
|
309
|
+
const ed25519Sign = async (message) => {
|
|
310
|
+
const result = await transport(buildEnvelope(bytesToHex(message), binding, creds));
|
|
311
|
+
const sig = result.signature.signature;
|
|
312
|
+
if (!sig) throw new Error("MoonX returned an Ed25519 result without a signature");
|
|
313
|
+
return hexToBytes(sig);
|
|
314
|
+
};
|
|
315
|
+
try {
|
|
316
|
+
switch (intent.op) {
|
|
317
|
+
case "message":
|
|
318
|
+
return {
|
|
319
|
+
ok: true,
|
|
320
|
+
artifact: { signature: await new MoonXEvmSigner(ecdsaSign).signMessage(intent.message) }
|
|
321
|
+
};
|
|
322
|
+
case "typedData":
|
|
323
|
+
return {
|
|
324
|
+
ok: true,
|
|
325
|
+
artifact: {
|
|
326
|
+
signature: await new MoonXEvmSigner(ecdsaSign).signTypedData(intent.typedData)
|
|
327
|
+
}
|
|
328
|
+
};
|
|
329
|
+
case "transaction":
|
|
330
|
+
return {
|
|
331
|
+
ok: true,
|
|
332
|
+
artifact: {
|
|
333
|
+
serializedTransaction: await new MoonXEvmSigner(ecdsaSign).signTransaction(
|
|
334
|
+
reviveTx(intent.transaction)
|
|
335
|
+
)
|
|
336
|
+
}
|
|
337
|
+
};
|
|
338
|
+
case "authorization":
|
|
339
|
+
return {
|
|
340
|
+
ok: true,
|
|
341
|
+
artifact: {
|
|
342
|
+
authorization: await new MoonXEvmSigner(ecdsaSign).signAuthorization(
|
|
343
|
+
intent.authorization
|
|
344
|
+
)
|
|
345
|
+
}
|
|
346
|
+
};
|
|
347
|
+
case "solanaMessage": {
|
|
348
|
+
const sig = await new MoonXSolanaSigner(ed25519Sign, intent.address).signMessage(
|
|
349
|
+
intent.message
|
|
350
|
+
);
|
|
351
|
+
return { ok: true, artifact: { signature: bytesToHex(sig) } };
|
|
352
|
+
}
|
|
353
|
+
case "solanaTransaction": {
|
|
354
|
+
const tx = Transaction.from(Buffer.from(intent.transactionBase64, "base64"));
|
|
355
|
+
const signed = await new MoonXSolanaSigner(ed25519Sign, intent.address).signTransaction(
|
|
356
|
+
tx
|
|
357
|
+
);
|
|
358
|
+
return {
|
|
359
|
+
ok: true,
|
|
360
|
+
artifact: { signedTransactionBase64: Buffer.from(signed).toString("base64") }
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
case "raw": {
|
|
364
|
+
const result = await transport(buildEnvelope(intent.rawSigningPayloadHex, binding, creds));
|
|
365
|
+
return { ok: true, artifact: { scheme: result.scheme, signature: result.signature } };
|
|
366
|
+
}
|
|
367
|
+
default:
|
|
368
|
+
return { ok: false, error: `unknown op: ${intent.op}` };
|
|
369
|
+
}
|
|
370
|
+
} catch (err) {
|
|
371
|
+
if (err instanceof EvmSchemeMismatchError || err instanceof SolanaSchemeMismatchError) {
|
|
372
|
+
return { ok: false, error: err.message };
|
|
373
|
+
}
|
|
374
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
375
|
+
return { ok: false, error: message };
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// src/signer.ts
|
|
380
|
+
function credsFromToken(token) {
|
|
381
|
+
const kp = parseToken(token);
|
|
382
|
+
if (!kp) {
|
|
383
|
+
throw new Error("invalid signing key: expected a `pbxk1.` token (keypair only)");
|
|
384
|
+
}
|
|
385
|
+
return { apiPubHex: kp.apiPubHex, apiPrivHex: kp.apiPrivHex, savedAt: Date.now() };
|
|
386
|
+
}
|
|
387
|
+
function toBinding(b) {
|
|
388
|
+
return { walletId: b.wallet_id, keyId: b.key_id ?? "", derivationPath: b.derivation_path ?? "" };
|
|
389
|
+
}
|
|
390
|
+
async function resolveBinding(post, requestId, creds) {
|
|
391
|
+
const env = buildIssuedAtEnvelope(creds);
|
|
392
|
+
const r = await post(`/agent/requests/${requestId}/binding`, {
|
|
393
|
+
api_pub: creds.apiPubHex,
|
|
394
|
+
signed_body: env.signed_body,
|
|
395
|
+
agent_signature: env.agent_signature
|
|
396
|
+
});
|
|
397
|
+
return toBinding(r);
|
|
398
|
+
}
|
|
399
|
+
async function signWalletRequest(post, requestId, intent, creds, binding) {
|
|
400
|
+
const resolved = binding && binding.key_id && binding.derivation_path ? toBinding(binding) : await resolveBinding(post, requestId, creds);
|
|
401
|
+
const transport = (envelope) => post(`/agent/requests/${requestId}/moonx-sign`, {
|
|
402
|
+
api_pub: creds.apiPubHex,
|
|
403
|
+
...envelope
|
|
404
|
+
});
|
|
405
|
+
const result = await signIntent(intent, creds, resolved, transport);
|
|
406
|
+
if (!result.ok) throw new Error(result.error);
|
|
407
|
+
await post(`/agent/requests/${requestId}/signature`, { artifact: result.artifact });
|
|
408
|
+
return result.artifact;
|
|
409
|
+
}
|
|
410
|
+
async function signSwapRequest(post, requestId, plan, creds) {
|
|
411
|
+
let binding = {
|
|
412
|
+
walletId: plan.binding.wallet_id,
|
|
413
|
+
keyId: plan.binding.key_id ?? "",
|
|
414
|
+
derivationPath: plan.binding.derivation_path ?? ""
|
|
415
|
+
};
|
|
416
|
+
if (binding.walletId && (!binding.keyId || !binding.derivationPath)) {
|
|
417
|
+
binding = await resolveBinding(post, requestId, creds);
|
|
418
|
+
}
|
|
419
|
+
const envelopes = buildSwapEnvelopes(plan.txs, binding, creds);
|
|
420
|
+
await post(`/agent/requests/${requestId}/envelopes`, { envelopes });
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
// src/client.ts
|
|
424
|
+
var DEFAULT_BASE_URL = "https://api.paybox.sh";
|
|
425
|
+
var EXPIRY_SKEW_MS = 6e4;
|
|
426
|
+
var PayboxError = class extends Error {
|
|
427
|
+
constructor(status, body, context) {
|
|
428
|
+
super(`paybox ${context} failed (${status}): ${body}`);
|
|
429
|
+
this.status = status;
|
|
430
|
+
this.body = body;
|
|
431
|
+
this.name = "PayboxError";
|
|
432
|
+
}
|
|
433
|
+
};
|
|
434
|
+
var PayboxClient = class _PayboxClient {
|
|
435
|
+
baseUrl;
|
|
436
|
+
opts;
|
|
437
|
+
fetchImpl;
|
|
438
|
+
oauth;
|
|
439
|
+
configBacked = false;
|
|
440
|
+
constructor(options = {}) {
|
|
441
|
+
this.opts = options;
|
|
442
|
+
this.baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
443
|
+
this.fetchImpl = options.fetchImpl ?? fetch;
|
|
444
|
+
}
|
|
445
|
+
/** Build a client from `~/.config/paybox/config.json` + env, with overrides. */
|
|
446
|
+
static fromConfig(overrides = {}) {
|
|
447
|
+
const cfg = loadConfig();
|
|
448
|
+
const client = new _PayboxClient({
|
|
449
|
+
baseUrl: overrides.baseUrl ?? process.env.PAYBOX_API_URL ?? cfg.baseUrl,
|
|
450
|
+
apiKey: overrides.apiKey ?? process.env.PAYBOX_API_KEY ?? cfg.apiKey,
|
|
451
|
+
token: overrides.token ?? process.env.PAYBOX_ACCESS_TOKEN,
|
|
452
|
+
signingKey: overrides.signingKey ?? process.env.PAYBOX_SIGNING_KEY ?? cfg.signingKey,
|
|
453
|
+
fetchImpl: overrides.fetchImpl
|
|
454
|
+
});
|
|
455
|
+
if (!client.opts.apiKey && !client.opts.token && cfg.oauth) {
|
|
456
|
+
client.oauth = cfg.oauth;
|
|
457
|
+
client.configBacked = true;
|
|
458
|
+
}
|
|
459
|
+
return client;
|
|
460
|
+
}
|
|
461
|
+
/** Whether in-process signing is available (a `pbxk1.` key is configured). */
|
|
462
|
+
get canSign() {
|
|
463
|
+
return Boolean(this.opts.signingKey);
|
|
464
|
+
}
|
|
465
|
+
async bearer() {
|
|
466
|
+
if (this.opts.token) return this.opts.token;
|
|
467
|
+
if (this.opts.apiKey) return this.opts.apiKey;
|
|
468
|
+
if (this.oauth) {
|
|
469
|
+
if (this.oauth.expiresAt && Date.now() > this.oauth.expiresAt - EXPIRY_SKEW_MS) {
|
|
470
|
+
this.oauth = await refreshTokens(this.baseUrl, this.oauth);
|
|
471
|
+
if (this.configBacked) {
|
|
472
|
+
const cfg = loadConfig();
|
|
473
|
+
cfg.oauth = this.oauth;
|
|
474
|
+
saveConfig(cfg);
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
return this.oauth.accessToken;
|
|
478
|
+
}
|
|
479
|
+
throw new Error(
|
|
480
|
+
"not authenticated \u2014 run `paybox login`, set PAYBOX_API_KEY, or pass { apiKey } / { token }"
|
|
481
|
+
);
|
|
482
|
+
}
|
|
483
|
+
async request(method, path, init = {}) {
|
|
484
|
+
const url = new URL(this.baseUrl + path);
|
|
485
|
+
for (const [k, v] of Object.entries(init.query ?? {})) {
|
|
486
|
+
if (v != null) url.searchParams.set(k, v);
|
|
487
|
+
}
|
|
488
|
+
const headers = { authorization: `Bearer ${await this.bearer()}` };
|
|
489
|
+
let body;
|
|
490
|
+
if (init.body !== void 0) {
|
|
491
|
+
headers["content-type"] = "application/json";
|
|
492
|
+
body = JSON.stringify(init.body);
|
|
493
|
+
}
|
|
494
|
+
const res = await this.fetchImpl(url, { method, headers, body });
|
|
495
|
+
const text = await res.text();
|
|
496
|
+
if (!res.ok) throw new PayboxError(res.status, text, `${method} ${path}`);
|
|
497
|
+
return text ? JSON.parse(text) : void 0;
|
|
498
|
+
}
|
|
499
|
+
poster = (path, body) => this.request("POST", path, { body });
|
|
500
|
+
/** List the credentials this client may use (the `list_credentials` tool). */
|
|
501
|
+
listCredentials() {
|
|
502
|
+
return this.request("GET", "/agent/grants");
|
|
503
|
+
}
|
|
504
|
+
/** Token balances across chains (the `get_portfolio` tool). */
|
|
505
|
+
getPortfolio(args) {
|
|
506
|
+
return this.request("GET", "/agent/portfolio", {
|
|
507
|
+
query: { address: args.address, network_ids: args.networkIds }
|
|
508
|
+
});
|
|
509
|
+
}
|
|
510
|
+
/** Charge a card credential (the `request_payment` tool). */
|
|
511
|
+
requestPayment(args) {
|
|
512
|
+
const operation = {
|
|
513
|
+
type: "payment",
|
|
514
|
+
credential_id: args.credentialId,
|
|
515
|
+
merchant: args.merchant,
|
|
516
|
+
merchant_url: args.merchantUrl,
|
|
517
|
+
amount_cents: args.amountCents,
|
|
518
|
+
currency: args.currency ?? "USD"
|
|
519
|
+
};
|
|
520
|
+
return this.request("POST", "/agent/request", { body: { operation } });
|
|
521
|
+
}
|
|
522
|
+
/** Reveal a secret credential (the `request_secret` tool). */
|
|
523
|
+
requestSecret(args) {
|
|
524
|
+
const operation = {
|
|
525
|
+
type: "secret",
|
|
526
|
+
credential_id: args.credentialId,
|
|
527
|
+
raw: args.raw ?? false,
|
|
528
|
+
purpose: args.purpose ?? null
|
|
529
|
+
};
|
|
530
|
+
return this.request("POST", "/agent/request", { body: { operation } });
|
|
531
|
+
}
|
|
532
|
+
/** Sign with a wallet credential (the `request_wallet_sign` tool). Signs
|
|
533
|
+
* in-process and returns the completed request when a signing key is set and
|
|
534
|
+
* the request clears immediately; otherwise returns the pending response.
|
|
535
|
+
* Uses `/agent/wallet-sign`, which echoes the wallet binding so the signer
|
|
536
|
+
* need not resolve it separately. */
|
|
537
|
+
async requestWalletSign(args, opts = {}) {
|
|
538
|
+
const operation = {
|
|
539
|
+
type: "wallet_sign",
|
|
540
|
+
credential_id: args.credentialId,
|
|
541
|
+
chain: args.chain,
|
|
542
|
+
contract: args.contract ?? null,
|
|
543
|
+
function: args.fn ?? null,
|
|
544
|
+
value_cents: args.valueCents ?? null,
|
|
545
|
+
intent: args.intent
|
|
546
|
+
};
|
|
547
|
+
const { response, binding } = await this.request(
|
|
548
|
+
"POST",
|
|
549
|
+
"/agent/wallet-sign",
|
|
550
|
+
{ body: { operation } }
|
|
551
|
+
);
|
|
552
|
+
if ((opts.autoSign ?? true) && response.status === "pending_signature" && this.opts.signingKey) {
|
|
553
|
+
await signWalletRequest(
|
|
554
|
+
this.poster,
|
|
555
|
+
response.request_id,
|
|
556
|
+
args.intent,
|
|
557
|
+
credsFromToken(this.opts.signingKey),
|
|
558
|
+
binding
|
|
559
|
+
);
|
|
560
|
+
return this.getRequest(response.request_id);
|
|
561
|
+
}
|
|
562
|
+
return response;
|
|
563
|
+
}
|
|
564
|
+
/** Swap tokens from a wallet credential (the `request_swap` tool). Returns the
|
|
565
|
+
* parked response + sign-ready plan; signs + submits in-process when a signing
|
|
566
|
+
* key is set and the swap cleared immediately. */
|
|
567
|
+
async requestSwap(args, opts = {}) {
|
|
568
|
+
const operation = {
|
|
569
|
+
type: "swap",
|
|
570
|
+
credential_id: args.credentialId,
|
|
571
|
+
src_chain: args.srcChain,
|
|
572
|
+
dst_chain: args.dstChain ?? args.srcChain,
|
|
573
|
+
src_token: args.srcToken,
|
|
574
|
+
dst_token: args.dstToken,
|
|
575
|
+
amount: args.amount,
|
|
576
|
+
swap_direction: args.swapDirection ?? "exact-amount-in",
|
|
577
|
+
slippage_bps: args.slippageBps ?? 50,
|
|
578
|
+
recipient: args.recipient ?? null,
|
|
579
|
+
value_cents: args.valueCents ?? null
|
|
580
|
+
};
|
|
581
|
+
const result = await this.request("POST", "/agent/swap", { body: { operation } });
|
|
582
|
+
if ((opts.autoSign ?? true) && result.response.status === "pending_signature" && this.opts.signingKey) {
|
|
583
|
+
await signSwapRequest(
|
|
584
|
+
this.poster,
|
|
585
|
+
result.response.request_id,
|
|
586
|
+
result.plan,
|
|
587
|
+
credsFromToken(this.opts.signingKey)
|
|
588
|
+
);
|
|
589
|
+
return { ...result, response: await this.getRequest(result.response.request_id) };
|
|
590
|
+
}
|
|
591
|
+
return result;
|
|
592
|
+
}
|
|
593
|
+
/** Poll a request's status (the `get_request` tool). */
|
|
594
|
+
getRequest(requestId) {
|
|
595
|
+
return this.request("GET", `/agent/requests/${requestId}`);
|
|
596
|
+
}
|
|
597
|
+
/** Poll until the request leaves a pending state (or the timeout elapses). */
|
|
598
|
+
async waitForRequest(requestId, opts = {}) {
|
|
599
|
+
const interval = opts.intervalMs ?? 2e3;
|
|
600
|
+
const deadline = Date.now() + (opts.timeoutMs ?? 3e5);
|
|
601
|
+
for (; ; ) {
|
|
602
|
+
const r = await this.getRequest(requestId);
|
|
603
|
+
const pending = r.status === "pending_approval" || r.status === "pending_signature";
|
|
604
|
+
if (!pending || Date.now() > deadline) return r;
|
|
605
|
+
await new Promise((res) => setTimeout(res, interval));
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
};
|
|
609
|
+
|
|
610
|
+
export {
|
|
611
|
+
mcpResource,
|
|
612
|
+
loginLoopback,
|
|
613
|
+
refreshTokens,
|
|
614
|
+
configFile,
|
|
615
|
+
loadConfig,
|
|
616
|
+
saveConfig,
|
|
617
|
+
credsFromToken,
|
|
618
|
+
PayboxError,
|
|
619
|
+
PayboxClient
|
|
620
|
+
};
|
|
621
|
+
//# sourceMappingURL=chunk-23N5RHOB.js.map
|