@cmdoss/suipay-mcp 0.2.2-dev.2 → 0.2.2-dev.4
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 +8 -53
- package/dist/bin/suipay.js +2 -2
- package/dist/{chunk-62LFIYB7.js → chunk-KDOGSB5I.js} +301 -1905
- package/dist/chunk-XAM2YQC6.js +39 -0
- package/dist/{http-IivHoDeb.d.ts → http-C8xW9F1c.d.ts} +4 -37
- package/dist/http.d.ts +1 -1
- package/dist/http.js +1 -1
- package/dist/index.d.ts +9 -41
- package/dist/index.js +2 -10
- package/package.json +1 -1
- package/dist/chunk-AFEXT5X4.js +0 -46
|
@@ -151,46 +151,6 @@ import { normalizeStructTag as normalizeStructTag2 } from "@mysten/sui/utils";
|
|
|
151
151
|
|
|
152
152
|
// ../core/dist/assets/testnet.js
|
|
153
153
|
import { normalizeStructTag } from "@mysten/sui/utils";
|
|
154
|
-
var TESTNET_SUI_COIN_TYPE = "0x2::sui::SUI";
|
|
155
|
-
var TESTNET_USDC_COIN_TYPE = "0xa1ec7fc00a6f40db9693ad1415d0c193ad3906494428cf252621037bd7117e29::usdc::USDC";
|
|
156
|
-
var TESTNET_SUI_DECIMALS = 9;
|
|
157
|
-
var TESTNET_USDC_DECIMALS = 6;
|
|
158
|
-
var TESTNET_ASSETS = [
|
|
159
|
-
{
|
|
160
|
-
id: "sui",
|
|
161
|
-
symbol: "SUI",
|
|
162
|
-
coinType: TESTNET_SUI_COIN_TYPE,
|
|
163
|
-
decimals: TESTNET_SUI_DECIMALS,
|
|
164
|
-
network: "sui:testnet"
|
|
165
|
-
},
|
|
166
|
-
{
|
|
167
|
-
id: "usdc",
|
|
168
|
-
symbol: "USDC",
|
|
169
|
-
coinType: TESTNET_USDC_COIN_TYPE,
|
|
170
|
-
decimals: TESTNET_USDC_DECIMALS,
|
|
171
|
-
network: "sui:testnet"
|
|
172
|
-
}
|
|
173
|
-
];
|
|
174
|
-
function supportedAssetByCoinType(coinType) {
|
|
175
|
-
const normalized = tryNormalizeStructTag(coinType);
|
|
176
|
-
if (!normalized)
|
|
177
|
-
return null;
|
|
178
|
-
for (const asset of TESTNET_ASSETS) {
|
|
179
|
-
if (tryNormalizeStructTag(asset.coinType) === normalized)
|
|
180
|
-
return asset;
|
|
181
|
-
}
|
|
182
|
-
return null;
|
|
183
|
-
}
|
|
184
|
-
function tryNormalizeStructTag(coinType) {
|
|
185
|
-
const trimmed = coinType.trim();
|
|
186
|
-
if (!trimmed)
|
|
187
|
-
return null;
|
|
188
|
-
try {
|
|
189
|
-
return normalizeStructTag(trimmed);
|
|
190
|
-
} catch {
|
|
191
|
-
return null;
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
154
|
|
|
195
155
|
// ../core/dist/service-state/index.js
|
|
196
156
|
var STATE_DIMENSIONS = Object.freeze([
|
|
@@ -411,101 +371,6 @@ var SERVICE_STATE_TRANSITIONS = Object.freeze([
|
|
|
411
371
|
]);
|
|
412
372
|
|
|
413
373
|
// ../core/dist/policy/target.js
|
|
414
|
-
var AUTHORIZATION_PROTOCOL_VERSION = "suipay-target-v1";
|
|
415
|
-
function normalizeHexAddress(address) {
|
|
416
|
-
const raw = address.trim().toLowerCase();
|
|
417
|
-
if (!raw.startsWith("0x")) {
|
|
418
|
-
throw new Error("Address must be a 0x-prefixed Sui address.");
|
|
419
|
-
}
|
|
420
|
-
const hex = raw.slice(2);
|
|
421
|
-
if (!/^[0-9a-f]+$/.test(hex) || hex.length === 0 || hex.length > 64) {
|
|
422
|
-
throw new Error("Invalid Sui address.");
|
|
423
|
-
}
|
|
424
|
-
if (/^0+$/.test(hex)) {
|
|
425
|
-
throw new Error("Recipient must be a non-zero address.");
|
|
426
|
-
}
|
|
427
|
-
return `0x${hex.padStart(64, "0")}`;
|
|
428
|
-
}
|
|
429
|
-
function normalizePath(path) {
|
|
430
|
-
const p = path.trim();
|
|
431
|
-
if (!p.startsWith("/")) {
|
|
432
|
-
throw new Error("Path must be an absolute path starting with /.");
|
|
433
|
-
}
|
|
434
|
-
if (p.length > 1 && p.endsWith("/"))
|
|
435
|
-
return p.replace(/\/+$/, "");
|
|
436
|
-
return p;
|
|
437
|
-
}
|
|
438
|
-
function normalizeMethod(method) {
|
|
439
|
-
const m = method.trim().toUpperCase();
|
|
440
|
-
if (!m)
|
|
441
|
-
throw new Error("HTTP method is required.");
|
|
442
|
-
return m;
|
|
443
|
-
}
|
|
444
|
-
function normalizeNetwork(network) {
|
|
445
|
-
const n = network.trim().toLowerCase();
|
|
446
|
-
if (n !== "sui:testnet") {
|
|
447
|
-
throw new Error(`Unsupported network: ${network}`);
|
|
448
|
-
}
|
|
449
|
-
return n;
|
|
450
|
-
}
|
|
451
|
-
function normalizeAsset(asset) {
|
|
452
|
-
const trimmed = asset.trim();
|
|
453
|
-
let normalized;
|
|
454
|
-
try {
|
|
455
|
-
normalized = normalizeStructTag2(trimmed);
|
|
456
|
-
} catch {
|
|
457
|
-
throw new Error(`Invalid coin type: ${asset}`);
|
|
458
|
-
}
|
|
459
|
-
if (!supportedAssetByCoinType(normalized)) {
|
|
460
|
-
throw new Error(`Unsupported asset: ${asset}`);
|
|
461
|
-
}
|
|
462
|
-
return normalized;
|
|
463
|
-
}
|
|
464
|
-
function normalizeId(id, label) {
|
|
465
|
-
const v = id.trim();
|
|
466
|
-
if (!v)
|
|
467
|
-
throw new Error(`${label} is required.`);
|
|
468
|
-
if (/[\n\r\0]/.test(v))
|
|
469
|
-
throw new Error(`${label} contains control characters.`);
|
|
470
|
-
return v;
|
|
471
|
-
}
|
|
472
|
-
function normalizeEpoch(epoch) {
|
|
473
|
-
if (!Number.isInteger(epoch) || epoch < 1) {
|
|
474
|
-
throw new Error("authorizationEpoch must be a positive integer.");
|
|
475
|
-
}
|
|
476
|
-
return epoch;
|
|
477
|
-
}
|
|
478
|
-
function canonicalizeTargetFields(input) {
|
|
479
|
-
return {
|
|
480
|
-
network: normalizeNetwork(input.network),
|
|
481
|
-
providerId: normalizeHexAddress(input.providerId),
|
|
482
|
-
serviceId: normalizeId(input.serviceId, "serviceId"),
|
|
483
|
-
endpointId: normalizeId(input.endpointId, "endpointId"),
|
|
484
|
-
method: normalizeMethod(input.method),
|
|
485
|
-
path: normalizePath(input.path),
|
|
486
|
-
asset: normalizeAsset(input.asset),
|
|
487
|
-
recipient: normalizeHexAddress(input.recipient),
|
|
488
|
-
authorizationEpoch: normalizeEpoch(input.authorizationEpoch)
|
|
489
|
-
};
|
|
490
|
-
}
|
|
491
|
-
function authorizationFingerprint(input) {
|
|
492
|
-
const c = canonicalizeTargetFields(input);
|
|
493
|
-
return [
|
|
494
|
-
AUTHORIZATION_PROTOCOL_VERSION,
|
|
495
|
-
c.network,
|
|
496
|
-
c.providerId,
|
|
497
|
-
c.serviceId,
|
|
498
|
-
c.endpointId,
|
|
499
|
-
c.method,
|
|
500
|
-
c.path,
|
|
501
|
-
c.asset,
|
|
502
|
-
c.recipient,
|
|
503
|
-
String(c.authorizationEpoch)
|
|
504
|
-
].join("\n");
|
|
505
|
-
}
|
|
506
|
-
function targetHashFromFingerprint(fingerprint) {
|
|
507
|
-
return createHash("sha256").update(fingerprint, "utf8").digest("hex");
|
|
508
|
-
}
|
|
509
374
|
function targetHashBytes(hexHash) {
|
|
510
375
|
if (!/^[0-9a-f]{64}$/i.test(hexHash)) {
|
|
511
376
|
throw new Error("targetHash must be 32-byte hex.");
|
|
@@ -4467,10 +4332,8 @@ import {
|
|
|
4467
4332
|
fsyncSync,
|
|
4468
4333
|
openSync,
|
|
4469
4334
|
closeSync,
|
|
4470
|
-
writeSync
|
|
4471
|
-
linkSync
|
|
4335
|
+
writeSync
|
|
4472
4336
|
} from "fs";
|
|
4473
|
-
import { decodeSuiPrivateKey } from "@mysten/sui/cryptography";
|
|
4474
4337
|
import { Ed25519Keypair as Ed25519Keypair2 } from "@mysten/sui/keypairs/ed25519";
|
|
4475
4338
|
import { fromBase64 as fromBase644 } from "@mysten/sui/utils";
|
|
4476
4339
|
import { bytesToHex as bytesToHex4 } from "@noble/hashes/utils.js";
|
|
@@ -4484,16 +4347,6 @@ function mcpCredentialsPath() {
|
|
|
4484
4347
|
const home = process.env.HOME ?? process.env.USERPROFILE ?? homedir();
|
|
4485
4348
|
return join(home, ".suipay", "credentials.json");
|
|
4486
4349
|
}
|
|
4487
|
-
var MCP_TARGET_PRESETS = {
|
|
4488
|
-
dev: {
|
|
4489
|
-
consoleUrl: "https://suipay-dev.up.railway.app",
|
|
4490
|
-
gatewayUrl: "https://gateway-dev-ba84.up.railway.app"
|
|
4491
|
-
},
|
|
4492
|
-
local: {
|
|
4493
|
-
consoleUrl: "http://localhost:3000",
|
|
4494
|
-
gatewayUrl: "http://127.0.0.1:4340"
|
|
4495
|
-
}
|
|
4496
|
-
};
|
|
4497
4350
|
var GRPC_URLS = {
|
|
4498
4351
|
mainnet: "https://fullnode.mainnet.sui.io:443",
|
|
4499
4352
|
testnet: "https://fullnode.testnet.sui.io:443",
|
|
@@ -4537,56 +4390,11 @@ function resolveActiveMovePackageId(env = process.env) {
|
|
|
4537
4390
|
}
|
|
4538
4391
|
return configured[0]?.value;
|
|
4539
4392
|
}
|
|
4540
|
-
function
|
|
4541
|
-
let target;
|
|
4542
|
-
for (const arg of argv) {
|
|
4543
|
-
const next = arg === "--local" || arg === "-local" ? "local" : arg === "--dev" || arg === "-dev" ? "dev" : void 0;
|
|
4544
|
-
if (!next) continue;
|
|
4545
|
-
if (target && target !== next) {
|
|
4546
|
-
throw new Error("pass only one of --local or --dev");
|
|
4547
|
-
}
|
|
4548
|
-
target = next;
|
|
4549
|
-
}
|
|
4550
|
-
return target;
|
|
4551
|
-
}
|
|
4552
|
-
function parseMcpTargetName(value) {
|
|
4553
|
-
const trimmed = value?.trim().toLowerCase();
|
|
4554
|
-
if (!trimmed) return void 0;
|
|
4555
|
-
if (trimmed === "dev" || trimmed === "local") return trimmed;
|
|
4556
|
-
throw new Error(`SUIPAY_MCP_TARGET must be "dev" or "local", got ${JSON.stringify(value)}`);
|
|
4557
|
-
}
|
|
4558
|
-
function resolveMcpTarget(env = process.env, argv = []) {
|
|
4559
|
-
return parseMcpTargetFlag(argv) ?? parseMcpTargetName(env.SUIPAY_MCP_TARGET) ?? "dev";
|
|
4560
|
-
}
|
|
4561
|
-
function loopbackKey(url) {
|
|
4562
|
-
if (url.protocol !== "http:" && url.protocol !== "https:") return null;
|
|
4563
|
-
if (url.hostname !== "127.0.0.1" && url.hostname !== "localhost") return null;
|
|
4564
|
-
const port = url.port || (url.protocol === "https:" ? "443" : "80");
|
|
4565
|
-
return `${url.protocol}//loopback:${port}`;
|
|
4566
|
-
}
|
|
4567
|
-
function sameMcpGateway(a, b) {
|
|
4568
|
-
let ua;
|
|
4569
|
-
let ub;
|
|
4570
|
-
try {
|
|
4571
|
-
ua = new URL(a);
|
|
4572
|
-
ub = new URL(b);
|
|
4573
|
-
} catch {
|
|
4574
|
-
return a.replace(/\/+$/, "") === b.replace(/\/+$/, "");
|
|
4575
|
-
}
|
|
4576
|
-
if (ua.origin === ub.origin) return true;
|
|
4577
|
-
const left = loopbackKey(ua);
|
|
4578
|
-
const right = loopbackKey(ub);
|
|
4579
|
-
return left !== null && left === right;
|
|
4580
|
-
}
|
|
4581
|
-
function loadMcpConfig(env = process.env, argv = []) {
|
|
4393
|
+
function loadMcpConfig(env = process.env) {
|
|
4582
4394
|
const network = env.SUIPAY_NETWORK ?? "testnet";
|
|
4583
|
-
const
|
|
4584
|
-
const preset = MCP_TARGET_PRESETS[target];
|
|
4585
|
-
const gatewayUrl = env.SUIPAY_GATEWAY_URL?.trim() || preset.gatewayUrl;
|
|
4586
|
-
const consoleUrl = env.SUIPAY_CONSOLE_URL?.trim() || preset.consoleUrl;
|
|
4395
|
+
const gatewayUrl = env.SUIPAY_GATEWAY_URL ?? "http://localhost:4340";
|
|
4587
4396
|
return {
|
|
4588
|
-
|
|
4589
|
-
consoleUrl,
|
|
4397
|
+
consoleUrl: env.SUIPAY_CONSOLE_URL ?? "http://localhost:3000",
|
|
4590
4398
|
gatewayUrl,
|
|
4591
4399
|
rpcUrl: env.SUIPAY_RPC_URL ?? GRPC_URLS[network],
|
|
4592
4400
|
network,
|
|
@@ -4635,23 +4443,10 @@ function isValid(obj) {
|
|
|
4635
4443
|
const c = obj;
|
|
4636
4444
|
return typeof c.delegatePrivateKey === "string" && HEX64.test(c.delegatePrivateKey) && typeof c.delegatePublicKeyHex === "string" && HEX64.test(c.delegatePublicKeyHex) && typeof c.delegateAddress === "string" && ADDR.test(c.delegateAddress) && typeof c.ownerAddress === "string" && ADDR.test(c.ownerAddress) && typeof c.allowanceId === "string" && ADDR.test(c.allowanceId) && typeof c.packageId === "string" && ADDR.test(c.packageId) && typeof c.recipient === "string" && ADDR.test(c.recipient) && typeof c.coinType === "string" && c.coinType.length > 0 && typeof c.gatewayUrl === "string" && typeof c.network === "string" && typeof c.maxPerPayment === "string" && /^\d+$/.test(c.maxPerPayment) && typeof c.createdAt === "string" && c.version === 1;
|
|
4637
4445
|
}
|
|
4638
|
-
function isHttpUrl(value) {
|
|
4639
|
-
if (typeof value !== "string" || !value.trim()) return false;
|
|
4640
|
-
try {
|
|
4641
|
-
const parsed = new URL(value);
|
|
4642
|
-
return parsed.protocol === "http:" || parsed.protocol === "https:";
|
|
4643
|
-
} catch {
|
|
4644
|
-
return false;
|
|
4645
|
-
}
|
|
4646
|
-
}
|
|
4647
4446
|
function isLocalSigningKeyRecord(obj) {
|
|
4648
4447
|
if (!obj || typeof obj !== "object") return false;
|
|
4649
4448
|
const c = obj;
|
|
4650
|
-
|
|
4651
|
-
return false;
|
|
4652
|
-
}
|
|
4653
|
-
if (c.gatewayUrl !== void 0 && !isHttpUrl(c.gatewayUrl)) return false;
|
|
4654
|
-
return true;
|
|
4449
|
+
return c.version === 2 && typeof c.delegatePrivateKey === "string" && HEX64.test(c.delegatePrivateKey) && typeof c.delegatePublicKeyHex === "string" && HEX64.test(c.delegatePublicKeyHex) && typeof c.delegateAddress === "string" && ADDR.test(c.delegateAddress) && typeof c.createdAt === "string";
|
|
4655
4450
|
}
|
|
4656
4451
|
function readCredentialsJson() {
|
|
4657
4452
|
const path = credsPath();
|
|
@@ -4687,19 +4482,15 @@ function loadCreds() {
|
|
|
4687
4482
|
}
|
|
4688
4483
|
return parsed;
|
|
4689
4484
|
}
|
|
4690
|
-
function
|
|
4485
|
+
function writeCredentialsFile(payload) {
|
|
4486
|
+
const dir = credsDir();
|
|
4487
|
+
mkdirSync(dir, { recursive: true, mode: 448 });
|
|
4691
4488
|
try {
|
|
4692
|
-
|
|
4693
|
-
try {
|
|
4694
|
-
fsyncSync(fd);
|
|
4695
|
-
} finally {
|
|
4696
|
-
closeSync(fd);
|
|
4697
|
-
}
|
|
4489
|
+
chmodSync(dir, 448);
|
|
4698
4490
|
} catch {
|
|
4699
4491
|
}
|
|
4700
|
-
|
|
4701
|
-
|
|
4702
|
-
const tmp = join2(dir, `.credentials.${process.pid}.${randomSuffix()}.tmp`);
|
|
4492
|
+
const path = credsPath();
|
|
4493
|
+
const tmp = join2(dir, `.credentials.${process.pid}.tmp`);
|
|
4703
4494
|
const fd = openSync(tmp, "w", 384);
|
|
4704
4495
|
try {
|
|
4705
4496
|
writeSync(fd, JSON.stringify(payload, null, 2));
|
|
@@ -4707,23 +4498,7 @@ function writeTmpCredentials(dir, payload) {
|
|
|
4707
4498
|
} finally {
|
|
4708
4499
|
closeSync(fd);
|
|
4709
4500
|
}
|
|
4710
|
-
return tmp;
|
|
4711
|
-
}
|
|
4712
|
-
function randomSuffix() {
|
|
4713
|
-
return Math.random().toString(16).slice(2, 10);
|
|
4714
|
-
}
|
|
4715
|
-
function writeCredentialsFile(payload) {
|
|
4716
|
-
const dir = credsDir();
|
|
4717
|
-
mkdirSync(dir, { recursive: true, mode: 448 });
|
|
4718
|
-
try {
|
|
4719
|
-
chmodSync(dir, 448);
|
|
4720
|
-
} catch {
|
|
4721
|
-
}
|
|
4722
|
-
const path = credsPath();
|
|
4723
|
-
const tmp = writeTmpCredentials(dir, payload);
|
|
4724
|
-
fsyncDir(dir);
|
|
4725
4501
|
renameSync(tmp, path);
|
|
4726
|
-
fsyncDir(dir);
|
|
4727
4502
|
try {
|
|
4728
4503
|
chmodSync(path, 384);
|
|
4729
4504
|
} catch {
|
|
@@ -4737,23 +4512,19 @@ function createCredentialsFileExclusive(payload) {
|
|
|
4737
4512
|
} catch {
|
|
4738
4513
|
}
|
|
4739
4514
|
const path = credsPath();
|
|
4740
|
-
|
|
4741
|
-
fsyncDir(dir);
|
|
4515
|
+
let fd;
|
|
4742
4516
|
try {
|
|
4743
|
-
|
|
4517
|
+
fd = openSync(path, "wx", 384);
|
|
4744
4518
|
} catch (error2) {
|
|
4745
|
-
try {
|
|
4746
|
-
unlinkSync(tmp);
|
|
4747
|
-
} catch {
|
|
4748
|
-
}
|
|
4749
4519
|
if (error2.code === "EEXIST") return false;
|
|
4750
4520
|
throw error2;
|
|
4751
4521
|
}
|
|
4752
4522
|
try {
|
|
4753
|
-
|
|
4754
|
-
|
|
4523
|
+
writeSync(fd, JSON.stringify(payload, null, 2));
|
|
4524
|
+
fsyncSync(fd);
|
|
4525
|
+
} finally {
|
|
4526
|
+
closeSync(fd);
|
|
4755
4527
|
}
|
|
4756
|
-
fsyncDir(dir);
|
|
4757
4528
|
try {
|
|
4758
4529
|
chmodSync(path, 384);
|
|
4759
4530
|
} catch {
|
|
@@ -4842,90 +4613,12 @@ function rewriteV1ToLocalSigningKey(creds, opts = {}) {
|
|
|
4842
4613
|
delegatePublicKeyHex: creds.delegatePublicKeyHex,
|
|
4843
4614
|
delegateAddress: creds.delegateAddress,
|
|
4844
4615
|
createdAt: creds.createdAt,
|
|
4845
|
-
...isHttpUrl(creds.gatewayUrl) ? { gatewayUrl: creds.gatewayUrl } : {},
|
|
4846
4616
|
...opts.label?.trim() ? { label: opts.label.trim() } : creds.label?.trim() ? { label: creds.label.trim() } : {}
|
|
4847
4617
|
};
|
|
4848
4618
|
writeCredentialsFile(record);
|
|
4849
4619
|
return identity;
|
|
4850
4620
|
}
|
|
4851
|
-
function keypairFromOperatorSecret(raw) {
|
|
4852
|
-
const key = raw.trim();
|
|
4853
|
-
if (!key) throw new Error("SUIPAY_MCP_KEY is set but empty");
|
|
4854
|
-
try {
|
|
4855
|
-
return /^(0x)?[0-9a-fA-F]{64}$/.test(key) ? Ed25519Keypair2.fromSecretKey(
|
|
4856
|
-
Uint8Array.from(Buffer.from(key.replace(/^0x/i, ""), "hex"))
|
|
4857
|
-
) : Ed25519Keypair2.fromSecretKey(key);
|
|
4858
|
-
} catch {
|
|
4859
|
-
throw new Error(
|
|
4860
|
-
"SUIPAY_MCP_KEY is malformed (expected 64-hex or suiprivkey1\u2026 Ed25519 secret)"
|
|
4861
|
-
);
|
|
4862
|
-
}
|
|
4863
|
-
}
|
|
4864
|
-
function seedHexFromKeypair(kp, operatorSecret) {
|
|
4865
|
-
const hex = operatorSecret.trim().replace(/^0x/i, "");
|
|
4866
|
-
if (/^[0-9a-fA-F]{64}$/.test(hex)) return hex.toLowerCase();
|
|
4867
|
-
try {
|
|
4868
|
-
return bytesToHex4(decodeSuiPrivateKey(kp.getSecretKey()).secretKey);
|
|
4869
|
-
} catch {
|
|
4870
|
-
throw new Error(
|
|
4871
|
-
"SUIPAY_MCP_KEY is malformed (expected 64-hex or suiprivkey1\u2026 Ed25519 secret)"
|
|
4872
|
-
);
|
|
4873
|
-
}
|
|
4874
|
-
}
|
|
4875
|
-
function recordFromKeypair(kp, opts = {}, existing, operatorSecret) {
|
|
4876
|
-
const seed = seedHexFromKeypair(kp, operatorSecret);
|
|
4877
|
-
const publicKeyHex = bytesToHex4(kp.getPublicKey().toRawBytes());
|
|
4878
|
-
const delegateAddress = kp.getPublicKey().toSuiAddress();
|
|
4879
|
-
const label = opts.label?.trim() || existing?.label?.trim();
|
|
4880
|
-
return {
|
|
4881
|
-
version: 2,
|
|
4882
|
-
delegatePrivateKey: seed,
|
|
4883
|
-
delegatePublicKeyHex: publicKeyHex,
|
|
4884
|
-
delegateAddress,
|
|
4885
|
-
createdAt: existing?.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
4886
|
-
...existing && isHttpUrl(existing.gatewayUrl ?? "") ? { gatewayUrl: existing.gatewayUrl } : {},
|
|
4887
|
-
...label ? { label } : {}
|
|
4888
|
-
};
|
|
4889
|
-
}
|
|
4890
|
-
var ENV_KEY_MISMATCH = "SUIPAY_MCP_KEY does not match the stored delegate key - refusing to overwrite";
|
|
4891
|
-
function existingRecordForEnv(parsed) {
|
|
4892
|
-
if (parsed === null) return null;
|
|
4893
|
-
if (isLocalSigningKeyRecord(parsed) || isValid(parsed)) return parsed;
|
|
4894
|
-
throw new Error(
|
|
4895
|
-
`credentials file at ${credsPath()} failed schema validation - refusing to continue`
|
|
4896
|
-
);
|
|
4897
|
-
}
|
|
4898
4621
|
async function ensureLocalSigningKey(opts = {}) {
|
|
4899
|
-
const envKey = process.env.SUIPAY_MCP_KEY?.trim();
|
|
4900
|
-
if (envKey) {
|
|
4901
|
-
const kp2 = keypairFromOperatorSecret(envKey);
|
|
4902
|
-
const parsed2 = readCredentialsJson();
|
|
4903
|
-
const existing = existingRecordForEnv(parsed2);
|
|
4904
|
-
const asV2 = existing ? isLocalSigningKeyRecord(existing) ? existing : {
|
|
4905
|
-
version: 2,
|
|
4906
|
-
delegatePrivateKey: existing.delegatePrivateKey,
|
|
4907
|
-
delegatePublicKeyHex: existing.delegatePublicKeyHex,
|
|
4908
|
-
delegateAddress: existing.delegateAddress,
|
|
4909
|
-
createdAt: existing.createdAt,
|
|
4910
|
-
...isHttpUrl(existing.gatewayUrl) ? { gatewayUrl: existing.gatewayUrl } : {},
|
|
4911
|
-
...existing.label?.trim() ? { label: existing.label.trim() } : {}
|
|
4912
|
-
} : null;
|
|
4913
|
-
const record2 = recordFromKeypair(kp2, opts, asV2, envKey);
|
|
4914
|
-
if (existing) {
|
|
4915
|
-
if (existing.delegateAddress !== record2.delegateAddress || existing.delegatePrivateKey !== record2.delegatePrivateKey) {
|
|
4916
|
-
throw new Error(ENV_KEY_MISMATCH);
|
|
4917
|
-
}
|
|
4918
|
-
if (!isLocalSigningKeyRecord(existing)) {
|
|
4919
|
-
writeCredentialsFile(record2);
|
|
4920
|
-
}
|
|
4921
|
-
} else {
|
|
4922
|
-
writeCredentialsFile(record2);
|
|
4923
|
-
}
|
|
4924
|
-
return {
|
|
4925
|
-
delegateAddress: record2.delegateAddress,
|
|
4926
|
-
publicKeyHex: record2.delegatePublicKeyHex
|
|
4927
|
-
};
|
|
4928
|
-
}
|
|
4929
4622
|
const parsed = readCredentialsJson();
|
|
4930
4623
|
if (parsed !== null) {
|
|
4931
4624
|
if (isLocalSigningKeyRecord(parsed)) {
|
|
@@ -4995,67 +4688,6 @@ function loadLocalSigner() {
|
|
|
4995
4688
|
}
|
|
4996
4689
|
};
|
|
4997
4690
|
}
|
|
4998
|
-
function loadLocalSigningKeyRecord() {
|
|
4999
|
-
const parsed = readCredentialsJson();
|
|
5000
|
-
if (parsed === null) return null;
|
|
5001
|
-
if (isLocalSigningKeyRecord(parsed)) {
|
|
5002
|
-
identityFromStored(
|
|
5003
|
-
parsed.delegatePrivateKey,
|
|
5004
|
-
parsed.delegatePublicKeyHex,
|
|
5005
|
-
parsed.delegateAddress
|
|
5006
|
-
);
|
|
5007
|
-
return parsed;
|
|
5008
|
-
}
|
|
5009
|
-
if (isValid(parsed)) return null;
|
|
5010
|
-
throw new Error(
|
|
5011
|
-
`credentials file at ${credsPath()} failed schema validation - refusing to continue`
|
|
5012
|
-
);
|
|
5013
|
-
}
|
|
5014
|
-
function loadLocalKeypair() {
|
|
5015
|
-
const parsed = readCredentialsJson();
|
|
5016
|
-
if (parsed === null) return null;
|
|
5017
|
-
if (!isLocalSigningKeyRecord(parsed) && !isValid(parsed)) {
|
|
5018
|
-
throw new Error(
|
|
5019
|
-
`credentials file at ${credsPath()} failed schema validation - refusing to continue`
|
|
5020
|
-
);
|
|
5021
|
-
}
|
|
5022
|
-
identityFromStored(
|
|
5023
|
-
parsed.delegatePrivateKey,
|
|
5024
|
-
parsed.delegatePublicKeyHex,
|
|
5025
|
-
parsed.delegateAddress
|
|
5026
|
-
);
|
|
5027
|
-
return Ed25519Keypair2.fromSecretKey(
|
|
5028
|
-
Uint8Array.from(Buffer.from(parsed.delegatePrivateKey, "hex"))
|
|
5029
|
-
);
|
|
5030
|
-
}
|
|
5031
|
-
function persistGatewayUrl(gatewayUrl) {
|
|
5032
|
-
if (!isHttpUrl(gatewayUrl)) {
|
|
5033
|
-
throw new Error("gatewayUrl must be an http(s) origin");
|
|
5034
|
-
}
|
|
5035
|
-
const parsed = readCredentialsJson();
|
|
5036
|
-
if (parsed === null) {
|
|
5037
|
-
throw new Error(
|
|
5038
|
-
`credentials file at ${credsPath()} is missing - refusing to persist gatewayUrl`
|
|
5039
|
-
);
|
|
5040
|
-
}
|
|
5041
|
-
if (isLocalSigningKeyRecord(parsed)) {
|
|
5042
|
-
identityFromStored(
|
|
5043
|
-
parsed.delegatePrivateKey,
|
|
5044
|
-
parsed.delegatePublicKeyHex,
|
|
5045
|
-
parsed.delegateAddress
|
|
5046
|
-
);
|
|
5047
|
-
writeCredentialsFile({ ...parsed, gatewayUrl });
|
|
5048
|
-
return;
|
|
5049
|
-
}
|
|
5050
|
-
if (isValid(parsed)) {
|
|
5051
|
-
rewriteV1ToLocalSigningKey(parsed);
|
|
5052
|
-
persistGatewayUrl(gatewayUrl);
|
|
5053
|
-
return;
|
|
5054
|
-
}
|
|
5055
|
-
throw new Error(
|
|
5056
|
-
`credentials file at ${credsPath()} failed schema validation - refusing to continue`
|
|
5057
|
-
);
|
|
5058
|
-
}
|
|
5059
4691
|
|
|
5060
4692
|
// src/agent-held.ts
|
|
5061
4693
|
var processLedger = null;
|
|
@@ -5083,6 +4715,38 @@ function offerSecret(env) {
|
|
|
5083
4715
|
const secret = env.SUIPAY_SECRET?.trim();
|
|
5084
4716
|
return secret ? secret : null;
|
|
5085
4717
|
}
|
|
4718
|
+
function settlementFromLocalSigner(cfg, local, packageId, env = process.env) {
|
|
4719
|
+
const chain = createSpendAccountChain({
|
|
4720
|
+
packageId,
|
|
4721
|
+
network: cfg.network,
|
|
4722
|
+
url: cfg.rpcUrl
|
|
4723
|
+
});
|
|
4724
|
+
const finality = createSpendAccountFinalityReader({ client: chain.client });
|
|
4725
|
+
const getObjectJson = async (objectId) => {
|
|
4726
|
+
const { object } = await chain.client.core.getObject({
|
|
4727
|
+
objectId,
|
|
4728
|
+
include: { json: true }
|
|
4729
|
+
});
|
|
4730
|
+
return objectWithOwner(object);
|
|
4731
|
+
};
|
|
4732
|
+
const secret = offerSecret(env);
|
|
4733
|
+
return {
|
|
4734
|
+
signer: {
|
|
4735
|
+
address: local.address,
|
|
4736
|
+
signTransaction: local.signTransaction,
|
|
4737
|
+
signPersonalMessage: local.signPersonalMessage
|
|
4738
|
+
},
|
|
4739
|
+
getObjectJson,
|
|
4740
|
+
payerDeps: {
|
|
4741
|
+
...secret ? { offerSecret: secret } : {},
|
|
4742
|
+
paymentLedger: sharedProcessLedger(),
|
|
4743
|
+
sponsor: { sponsorUrl: cfg.gatewayUrl },
|
|
4744
|
+
gasMode: "sponsored",
|
|
4745
|
+
resolveSharedObjectVersions: (ids) => resolveSharedObjectVersionsFromGetObject(getObjectJson, ids),
|
|
4746
|
+
getFinalizedTx: (digest) => finality.getFinalizedTx(digest)
|
|
4747
|
+
}
|
|
4748
|
+
};
|
|
4749
|
+
}
|
|
5086
4750
|
function resolveAgentHeldSettlement(cfg, spendAccountPay, env = process.env) {
|
|
5087
4751
|
const secret = offerSecret(env);
|
|
5088
4752
|
if (!secret) return null;
|
|
@@ -5176,311 +4840,52 @@ function toolAllowedByScope(toolName, grantedScope) {
|
|
|
5176
4840
|
}
|
|
5177
4841
|
|
|
5178
4842
|
// src/tools.ts
|
|
5179
|
-
import { isValidSuiAddress, normalizeSuiAddress as
|
|
4843
|
+
import { isValidSuiAddress, normalizeSuiAddress as normalizeSuiAddress10 } from "@mysten/sui/utils";
|
|
5180
4844
|
|
|
5181
|
-
//
|
|
5182
|
-
|
|
5183
|
-
|
|
5184
|
-
|
|
5185
|
-
|
|
5186
|
-
|
|
5187
|
-
|
|
5188
|
-
|
|
5189
|
-
|
|
5190
|
-
|
|
5191
|
-
|
|
5192
|
-
|
|
5193
|
-
|
|
5194
|
-
|
|
5195
|
-
|
|
5196
|
-
|
|
5197
|
-
|
|
5198
|
-
|
|
5199
|
-
|
|
5200
|
-
|
|
5201
|
-
|
|
5202
|
-
|
|
5203
|
-
|
|
5204
|
-
|
|
5205
|
-
|
|
5206
|
-
|
|
5207
|
-
|
|
5208
|
-
|
|
5209
|
-
|
|
5210
|
-
|
|
5211
|
-
|
|
5212
|
-
|
|
5213
|
-
|
|
5214
|
-
|
|
5215
|
-
|
|
5216
|
-
|
|
5217
|
-
|
|
5218
|
-
|
|
5219
|
-
|
|
5220
|
-
|
|
5221
|
-
|
|
5222
|
-
|
|
5223
|
-
|
|
5224
|
-
|
|
5225
|
-
session: args.session,
|
|
5226
|
-
cfg: args.cfg,
|
|
5227
|
-
deps: args.deps,
|
|
5228
|
-
preparedTerms: {
|
|
5229
|
-
recipient: args.expected.recipient,
|
|
5230
|
-
amount: args.expected.amount,
|
|
5231
|
-
asset: args.signingRequest.coinType,
|
|
5232
|
-
targetHash: args.signingRequest.targetHash
|
|
5233
|
-
},
|
|
5234
|
-
...args.intent ? { intent: args.intent } : {}
|
|
5235
|
-
});
|
|
5236
|
-
}
|
|
5237
|
-
|
|
5238
|
-
// ../sdk/dist/agent-sdk/delegate-agent.js
|
|
5239
|
-
import { normalizeSuiAddress as normalizeSuiAddress10 } from "@mysten/sui/utils";
|
|
5240
|
-
|
|
5241
|
-
// ../sdk/dist/agent-sdk/verify-prepared-settlement.js
|
|
5242
|
-
import { normalizeSuiAddress as normalizeSuiAddress9 } from "@mysten/sui/utils";
|
|
5243
|
-
import { Transaction as Transaction5 } from "@mysten/sui/transactions";
|
|
5244
|
-
import { fromBase64 as fromBase645 } from "@mysten/sui/utils";
|
|
5245
|
-
|
|
5246
|
-
// ../sdk/dist/protocol/prepared-settlement.js
|
|
5247
|
-
var PREPARED_SETTLEMENT_VERSION = 1;
|
|
5248
|
-
function asPreparedSettlement(value) {
|
|
5249
|
-
if (!value || typeof value !== "object")
|
|
5250
|
-
return null;
|
|
5251
|
-
const v = value;
|
|
5252
|
-
if (v.version !== PREPARED_SETTLEMENT_VERSION)
|
|
5253
|
-
return null;
|
|
5254
|
-
const accessMode = v.accessMode;
|
|
5255
|
-
if (accessMode !== "open" && accessMode !== "scoped")
|
|
5256
|
-
return null;
|
|
5257
|
-
const strings = [
|
|
5258
|
-
"transactionBytes",
|
|
5259
|
-
"transactionKindBytes",
|
|
5260
|
-
"sender",
|
|
5261
|
-
"packageId",
|
|
5262
|
-
"moveTarget",
|
|
5263
|
-
"poolObjectId",
|
|
5264
|
-
"grantObjectId",
|
|
5265
|
-
"poolInitialSharedVersion",
|
|
5266
|
-
"grantInitialSharedVersion",
|
|
5267
|
-
"clockInitialSharedVersion",
|
|
5268
|
-
"policyId",
|
|
5269
|
-
"coinType",
|
|
5270
|
-
"amount",
|
|
5271
|
-
"recipient",
|
|
5272
|
-
"payUrl"
|
|
5273
|
-
];
|
|
5274
|
-
for (const key of strings) {
|
|
5275
|
-
const got = v[key];
|
|
5276
|
-
if (typeof got !== "string" || !got.trim())
|
|
5277
|
-
return null;
|
|
5278
|
-
}
|
|
5279
|
-
return {
|
|
5280
|
-
version: PREPARED_SETTLEMENT_VERSION,
|
|
5281
|
-
accessMode,
|
|
5282
|
-
transactionBytes: v.transactionBytes,
|
|
5283
|
-
transactionKindBytes: v.transactionKindBytes,
|
|
5284
|
-
sender: v.sender,
|
|
5285
|
-
packageId: v.packageId,
|
|
5286
|
-
moveTarget: v.moveTarget,
|
|
5287
|
-
poolObjectId: v.poolObjectId,
|
|
5288
|
-
grantObjectId: v.grantObjectId,
|
|
5289
|
-
poolInitialSharedVersion: v.poolInitialSharedVersion,
|
|
5290
|
-
grantInitialSharedVersion: v.grantInitialSharedVersion,
|
|
5291
|
-
clockInitialSharedVersion: v.clockInitialSharedVersion,
|
|
5292
|
-
policyId: v.policyId,
|
|
5293
|
-
coinType: v.coinType,
|
|
5294
|
-
amount: v.amount,
|
|
5295
|
-
recipient: v.recipient,
|
|
5296
|
-
payUrl: v.payUrl
|
|
5297
|
-
};
|
|
5298
|
-
}
|
|
5299
|
-
|
|
5300
|
-
// ../sdk/dist/agent-sdk/verify-prepared-settlement.js
|
|
5301
|
-
var PreparedSettlementRejected = class extends Error {
|
|
5302
|
-
code = "PREPARED_SETTLEMENT_REJECTED";
|
|
5303
|
-
/** Which check failed, for a caller that logs rather than reads prose. */
|
|
5304
|
-
field;
|
|
5305
|
-
constructor(field, message) {
|
|
5306
|
-
super(message);
|
|
5307
|
-
this.name = "PreparedSettlementRejected";
|
|
5308
|
-
this.field = field;
|
|
5309
|
-
}
|
|
5310
|
-
};
|
|
5311
|
-
function reject(field, message) {
|
|
5312
|
-
throw new PreparedSettlementRejected(field, message);
|
|
5313
|
-
}
|
|
5314
|
-
function sameAddress3(a, b) {
|
|
5315
|
-
try {
|
|
5316
|
-
return normalizeSuiAddress9(a) === normalizeSuiAddress9(b);
|
|
5317
|
-
} catch {
|
|
5318
|
-
return false;
|
|
5319
|
-
}
|
|
5320
|
-
}
|
|
5321
|
-
function sameCoinType(a, b) {
|
|
5322
|
-
const norm = (value) => {
|
|
5323
|
-
const [pkg, ...rest] = value.trim().split("::");
|
|
5324
|
-
if (pkg === void 0 || rest.length !== 2)
|
|
5325
|
-
return value.trim();
|
|
5326
|
-
try {
|
|
5327
|
-
return `${normalizeSuiAddress9(pkg)}::${rest.join("::")}`;
|
|
5328
|
-
} catch {
|
|
5329
|
-
return value.trim();
|
|
5330
|
-
}
|
|
5331
|
-
};
|
|
5332
|
-
return norm(a) === norm(b);
|
|
5333
|
-
}
|
|
5334
|
-
function sameAmount(a, b) {
|
|
5335
|
-
try {
|
|
5336
|
-
return BigInt(a) === BigInt(b);
|
|
5337
|
-
} catch {
|
|
5338
|
-
return false;
|
|
5339
|
-
}
|
|
5340
|
-
}
|
|
5341
|
-
async function verifyPreparedPayment(input) {
|
|
5342
|
-
const prepared = asPreparedSettlement(input.prepared);
|
|
5343
|
-
if (!prepared) {
|
|
5344
|
-
reject("shape", "the 402 carried no usable prepared settlement");
|
|
5345
|
-
}
|
|
5346
|
-
const { offer, delegateAddress } = input;
|
|
5347
|
-
if (!sameAddress3(prepared.sender, delegateAddress)) {
|
|
5348
|
-
reject("sender", `prepared sender ${prepared.sender} is not this agent's address`);
|
|
5349
|
-
}
|
|
5350
|
-
let data;
|
|
5351
|
-
try {
|
|
5352
|
-
data = Transaction5.from(fromBase645(prepared.transactionBytes)).getData();
|
|
5353
|
-
} catch {
|
|
5354
|
-
reject("transactionBytes", "prepared transaction bytes are not a Sui transaction");
|
|
5355
|
-
}
|
|
5356
|
-
const gasOwner = data.gasData?.owner;
|
|
5357
|
-
if (!gasOwner || sameAddress3(gasOwner, delegateAddress)) {
|
|
5358
|
-
reject("gasOwner", "prepared transaction is not sponsored: gas would be paid by this agent");
|
|
5359
|
-
}
|
|
5360
|
-
if (input.expectedGrantObjectId && !sameAddress3(prepared.grantObjectId, input.expectedGrantObjectId)) {
|
|
5361
|
-
reject("grantObjectId", `prepared settlement draws on grant ${prepared.grantObjectId}, not the one this agent resolved`);
|
|
5362
|
-
}
|
|
5363
|
-
if (input.expectedPoolObjectId && !sameAddress3(prepared.poolObjectId, input.expectedPoolObjectId)) {
|
|
5364
|
-
reject("poolObjectId", `prepared settlement draws on pool ${prepared.poolObjectId}, not the one this agent resolved`);
|
|
5365
|
-
}
|
|
5366
|
-
if (!offer.packageId || !sameAddress3(prepared.packageId, offer.packageId)) {
|
|
5367
|
-
reject("packageId", "prepared package id does not match the offer");
|
|
5368
|
-
}
|
|
5369
|
-
const wantedTarget = payMoveCallTarget(offer.packageId, prepared.accessMode);
|
|
5370
|
-
if (prepared.moveTarget !== wantedTarget) {
|
|
5371
|
-
reject("moveTarget", `prepared Move target ${prepared.moveTarget} is not ${wantedTarget}`);
|
|
5372
|
-
}
|
|
5373
|
-
if (!sameCoinType(prepared.coinType, offer.asset)) {
|
|
5374
|
-
reject("coinType", "prepared coin type does not match the offer asset");
|
|
5375
|
-
}
|
|
5376
|
-
if (!sameAmount(prepared.amount, offer.amount)) {
|
|
5377
|
-
reject("amount", "prepared amount does not match the offer amount");
|
|
5378
|
-
}
|
|
5379
|
-
if (!sameAddress3(prepared.recipient, offer.payTo)) {
|
|
5380
|
-
reject("recipient", "prepared recipient does not match the offer payTo");
|
|
5381
|
-
}
|
|
5382
|
-
const targetHash = offer.targetHash?.trim();
|
|
5383
|
-
if (!targetHash) {
|
|
5384
|
-
reject("targetHash", "offer carries no targetHash to bind the settlement to");
|
|
5385
|
-
}
|
|
5386
|
-
if (!offer.digest?.trim()) {
|
|
5387
|
-
reject("digest", "offer carries no digest, so its terms hash cannot be derived");
|
|
5388
|
-
}
|
|
5389
|
-
const common = {
|
|
5390
|
-
packageId: offer.packageId,
|
|
5391
|
-
delegate: prepared.sender,
|
|
5392
|
-
coinType: prepared.coinType,
|
|
5393
|
-
poolObjectId: prepared.poolObjectId,
|
|
5394
|
-
grantObjectId: prepared.grantObjectId,
|
|
5395
|
-
poolInitialSharedVersion: prepared.poolInitialSharedVersion,
|
|
5396
|
-
grantInitialSharedVersion: prepared.grantInitialSharedVersion,
|
|
5397
|
-
clockInitialSharedVersion: prepared.clockInitialSharedVersion,
|
|
5398
|
-
targetHash,
|
|
5399
|
-
amount: offer.amount,
|
|
5400
|
-
paymentIdHash: paymentIdHash(offer.challengeId),
|
|
5401
|
-
termsHash: termsHash(offer)
|
|
5402
|
-
};
|
|
5403
|
-
let rebuilt;
|
|
5404
|
-
try {
|
|
5405
|
-
rebuilt = prepared.accessMode === "open" ? (await buildSpendAccountSettleOpenRecipientKind({
|
|
5406
|
-
...common,
|
|
5407
|
-
recipient: prepared.recipient
|
|
5408
|
-
})).kindBytes : (await buildSpendAccountPayKind({
|
|
5409
|
-
...common,
|
|
5410
|
-
policyId: prepared.policyId
|
|
5411
|
-
})).kindBytes;
|
|
5412
|
-
} catch (err) {
|
|
5413
|
-
reject("rebuild", `the prepared settlement could not be rebuilt offline: ${err instanceof Error ? err.message : String(err)}`);
|
|
5414
|
-
}
|
|
5415
|
-
if (rebuilt !== prepared.transactionKindBytes) {
|
|
5416
|
-
reject("transactionKindBytes", "the prepared transaction kind is not the one this offer describes");
|
|
5417
|
-
}
|
|
5418
|
-
try {
|
|
5419
|
-
assertPayTransaction(prepared.transactionBytes, {
|
|
5420
|
-
packageId: offer.packageId,
|
|
5421
|
-
delegate: prepared.sender,
|
|
5422
|
-
poolObjectId: prepared.poolObjectId,
|
|
5423
|
-
grantObjectId: prepared.grantObjectId,
|
|
5424
|
-
policyId: prepared.policyId,
|
|
5425
|
-
targetHash,
|
|
5426
|
-
amount: offer.amount,
|
|
5427
|
-
paymentIdHash: common.paymentIdHash,
|
|
5428
|
-
termsHash: common.termsHash,
|
|
5429
|
-
coinType: prepared.coinType,
|
|
5430
|
-
kindBytes: rebuilt,
|
|
5431
|
-
accessMode: prepared.accessMode,
|
|
5432
|
-
recipient: prepared.recipient
|
|
5433
|
-
});
|
|
5434
|
-
} catch (err) {
|
|
5435
|
-
reject("transactionBytes", `prepared transaction does not reconstruct the intended settlement: ${err instanceof Error ? err.message : String(err)}`);
|
|
5436
|
-
}
|
|
5437
|
-
return prepared;
|
|
5438
|
-
}
|
|
5439
|
-
|
|
5440
|
-
// ../sdk/dist/agent-sdk/manifest.js
|
|
5441
|
-
var MANIFEST_ERROR_CODES = {
|
|
5442
|
-
MALFORMED_MANIFEST: "MALFORMED_MANIFEST",
|
|
5443
|
-
MISSING_NETWORK: "MISSING_NETWORK",
|
|
5444
|
-
MISSING_PACKAGE: "MISSING_PACKAGE",
|
|
5445
|
-
MISSING_DELEGATE: "MISSING_DELEGATE",
|
|
5446
|
-
NO_GRANTS: "NO_GRANTS",
|
|
5447
|
-
MISSING_COIN_TYPE: "MISSING_COIN_TYPE",
|
|
5448
|
-
MISSING_POOL_OBJECT: "MISSING_POOL_OBJECT",
|
|
5449
|
-
MISSING_GRANT_OBJECT: "MISSING_GRANT_OBJECT",
|
|
5450
|
-
MISSING_POLICY_BINDING: "MISSING_POLICY_BINDING",
|
|
5451
|
-
INVALID_ACCESS_MODE: "INVALID_ACCESS_MODE",
|
|
5452
|
-
GRANT_NOT_FOUND: "GRANT_NOT_FOUND",
|
|
5453
|
-
AMBIGUOUS_GRANT: "AMBIGUOUS_GRANT",
|
|
5454
|
-
/**
|
|
5455
|
-
* The chain does not agree that this grant belongs to this delegate. The
|
|
5456
|
-
* gateway said it did; an unsigned `getObject` says otherwise, and the chain
|
|
5457
|
-
* wins (ADR-0016 §3).
|
|
5458
|
-
*/
|
|
5459
|
-
GRANT_NOT_VERIFIED: "GRANT_NOT_VERIFIED",
|
|
5460
|
-
/**
|
|
5461
|
-
* The on-chain check could not run to a verdict — the fullnode read was
|
|
5462
|
-
* unreachable or returned nothing. "Could not verify" is not "verified and it
|
|
5463
|
-
* failed": both refuse, but only one is a security event.
|
|
5464
|
-
*/
|
|
5465
|
-
GRANT_VERIFY_UNAVAILABLE: "GRANT_VERIFY_UNAVAILABLE"
|
|
5466
|
-
};
|
|
5467
|
-
function fail(code, detail) {
|
|
5468
|
-
return { ok: false, error: { code, detail } };
|
|
5469
|
-
}
|
|
5470
|
-
var OBJECT_ID_RE = /^0x[0-9a-fA-F]{1,64}$/;
|
|
5471
|
-
var HEX32_RE = /^(0x)?[0-9a-fA-F]{64}$/;
|
|
5472
|
-
var U64_MAX = (1n << 64n) - 1n;
|
|
5473
|
-
function isNonEmptyString(v) {
|
|
5474
|
-
return typeof v === "string" && v.trim().length > 0;
|
|
5475
|
-
}
|
|
5476
|
-
function isObjectId(v) {
|
|
5477
|
-
return typeof v === "string" && OBJECT_ID_RE.test(v.trim());
|
|
5478
|
-
}
|
|
5479
|
-
function parseU64PolicyId(onChain) {
|
|
5480
|
-
if (typeof onChain === "number") {
|
|
5481
|
-
if (!Number.isSafeInteger(onChain) || onChain < 0)
|
|
5482
|
-
return null;
|
|
5483
|
-
return String(onChain);
|
|
4845
|
+
// ../sdk/dist/agent-sdk/manifest.js
|
|
4846
|
+
var MANIFEST_ERROR_CODES = {
|
|
4847
|
+
MALFORMED_MANIFEST: "MALFORMED_MANIFEST",
|
|
4848
|
+
MISSING_NETWORK: "MISSING_NETWORK",
|
|
4849
|
+
MISSING_PACKAGE: "MISSING_PACKAGE",
|
|
4850
|
+
MISSING_DELEGATE: "MISSING_DELEGATE",
|
|
4851
|
+
NO_GRANTS: "NO_GRANTS",
|
|
4852
|
+
MISSING_COIN_TYPE: "MISSING_COIN_TYPE",
|
|
4853
|
+
MISSING_POOL_OBJECT: "MISSING_POOL_OBJECT",
|
|
4854
|
+
MISSING_GRANT_OBJECT: "MISSING_GRANT_OBJECT",
|
|
4855
|
+
MISSING_POLICY_BINDING: "MISSING_POLICY_BINDING",
|
|
4856
|
+
INVALID_ACCESS_MODE: "INVALID_ACCESS_MODE",
|
|
4857
|
+
GRANT_NOT_FOUND: "GRANT_NOT_FOUND",
|
|
4858
|
+
AMBIGUOUS_GRANT: "AMBIGUOUS_GRANT",
|
|
4859
|
+
/**
|
|
4860
|
+
* The chain does not agree that this grant belongs to this delegate. The
|
|
4861
|
+
* gateway said it did; an unsigned `getObject` says otherwise, and the chain
|
|
4862
|
+
* wins (ADR-0016 §3).
|
|
4863
|
+
*/
|
|
4864
|
+
GRANT_NOT_VERIFIED: "GRANT_NOT_VERIFIED",
|
|
4865
|
+
/**
|
|
4866
|
+
* The on-chain check could not run to a verdict — the fullnode read was
|
|
4867
|
+
* unreachable or returned nothing. "Could not verify" is not "verified and it
|
|
4868
|
+
* failed": both refuse, but only one is a security event.
|
|
4869
|
+
*/
|
|
4870
|
+
GRANT_VERIFY_UNAVAILABLE: "GRANT_VERIFY_UNAVAILABLE"
|
|
4871
|
+
};
|
|
4872
|
+
function fail(code, detail) {
|
|
4873
|
+
return { ok: false, error: { code, detail } };
|
|
4874
|
+
}
|
|
4875
|
+
var OBJECT_ID_RE = /^0x[0-9a-fA-F]{1,64}$/;
|
|
4876
|
+
var HEX32_RE = /^(0x)?[0-9a-fA-F]{64}$/;
|
|
4877
|
+
var U64_MAX = (1n << 64n) - 1n;
|
|
4878
|
+
function isNonEmptyString(v) {
|
|
4879
|
+
return typeof v === "string" && v.trim().length > 0;
|
|
4880
|
+
}
|
|
4881
|
+
function isObjectId(v) {
|
|
4882
|
+
return typeof v === "string" && OBJECT_ID_RE.test(v.trim());
|
|
4883
|
+
}
|
|
4884
|
+
function parseU64PolicyId(onChain) {
|
|
4885
|
+
if (typeof onChain === "number") {
|
|
4886
|
+
if (!Number.isSafeInteger(onChain) || onChain < 0)
|
|
4887
|
+
return null;
|
|
4888
|
+
return String(onChain);
|
|
5484
4889
|
}
|
|
5485
4890
|
if (typeof onChain === "string" && /^\d+$/.test(onChain.trim())) {
|
|
5486
4891
|
const s = onChain.trim();
|
|
@@ -5867,489 +5272,71 @@ async function resolvePayerSession(options) {
|
|
|
5867
5272
|
});
|
|
5868
5273
|
}
|
|
5869
5274
|
|
|
5870
|
-
//
|
|
5871
|
-
|
|
5872
|
-
"privateKey",
|
|
5873
|
-
"private_key",
|
|
5874
|
-
"privateKeyHex",
|
|
5875
|
-
"secretKey",
|
|
5876
|
-
"secret_key",
|
|
5877
|
-
"secret",
|
|
5878
|
-
"seed",
|
|
5879
|
-
"seedPhrase",
|
|
5880
|
-
"seed_phrase",
|
|
5881
|
-
"mnemonic",
|
|
5882
|
-
"privkey",
|
|
5883
|
-
"suiprivkey"
|
|
5884
|
-
];
|
|
5885
|
-
var FORBIDDEN_SECRET_KEY_SET = new Set(FORBIDDEN_SECRET_KEYS.map((k) => k.toLowerCase()));
|
|
5886
|
-
|
|
5887
|
-
// ../sdk/dist/agent-sdk/smoke.js
|
|
5888
|
-
function mergeObjectEnvelope(object) {
|
|
5889
|
-
if (!object || typeof object.json !== "object" || object.json === null) {
|
|
5890
|
-
return null;
|
|
5891
|
-
}
|
|
5892
|
-
return { ...object.json, owner: object.owner };
|
|
5893
|
-
}
|
|
5894
|
-
var SECRET_KEY_SET = new Set(FORBIDDEN_SECRET_KEYS.map((k) => k.toLowerCase()));
|
|
5275
|
+
// src/tools.ts
|
|
5276
|
+
import open from "open";
|
|
5895
5277
|
|
|
5896
|
-
//
|
|
5897
|
-
async function
|
|
5898
|
-
let state;
|
|
5899
|
-
try {
|
|
5900
|
-
state = await readPublicGrantState(input.getObjectJson, input.grantObjectId);
|
|
5901
|
-
} catch {
|
|
5902
|
-
return {
|
|
5903
|
-
ok: false,
|
|
5904
|
-
error: {
|
|
5905
|
-
code: MANIFEST_ERROR_CODES.GRANT_VERIFY_UNAVAILABLE,
|
|
5906
|
-
detail: `the on-chain read of grant ${input.grantObjectId} did not complete, so this session was not verified and no payment was prepared`
|
|
5907
|
-
}
|
|
5908
|
-
};
|
|
5909
|
-
}
|
|
5910
|
-
if (!state) {
|
|
5911
|
-
return {
|
|
5912
|
-
ok: false,
|
|
5913
|
-
error: {
|
|
5914
|
-
code: MANIFEST_ERROR_CODES.GRANT_VERIFY_UNAVAILABLE,
|
|
5915
|
-
detail: `grant ${input.grantObjectId} returned no readable on-chain state`
|
|
5916
|
-
}
|
|
5917
|
-
};
|
|
5918
|
-
}
|
|
5919
|
-
const notVerified = (detail) => ({
|
|
5920
|
-
ok: false,
|
|
5921
|
-
error: { code: MANIFEST_ERROR_CODES.GRANT_NOT_VERIFIED, detail }
|
|
5922
|
-
});
|
|
5923
|
-
if (!state.delegateAddress || normalizeSuiAddress10(state.delegateAddress) !== normalizeSuiAddress10(input.delegateAddress)) {
|
|
5924
|
-
return notVerified(`grant ${input.grantObjectId} does not name this delegate on chain`);
|
|
5925
|
-
}
|
|
5926
|
-
if (!state.poolObjectId || normalizeSuiAddress10(state.poolObjectId) !== normalizeSuiAddress10(input.poolObjectId)) {
|
|
5927
|
-
return notVerified(`grant ${input.grantObjectId} draws on a different pool than the one resolved`);
|
|
5928
|
-
}
|
|
5929
|
-
if (state.revoked === true) {
|
|
5930
|
-
return notVerified(`grant ${input.grantObjectId} is revoked on chain`);
|
|
5931
|
-
}
|
|
5932
|
-
if (state.paused === true) {
|
|
5933
|
-
return notVerified(`grant ${input.grantObjectId} is paused on chain`);
|
|
5934
|
-
}
|
|
5935
|
-
return { ok: true, value: null };
|
|
5936
|
-
}
|
|
5937
|
-
async function createPayer(options) {
|
|
5938
|
-
const keypair = options.keypair;
|
|
5939
|
-
const address = keypair.getPublicKey().toSuiAddress();
|
|
5940
|
-
const resolved = await resolvePayerSession({
|
|
5941
|
-
gatewayUrl: options.gatewayUrl,
|
|
5942
|
-
delegateAddress: address,
|
|
5943
|
-
// The bootstrap signature and the payment signature come from the same key.
|
|
5944
|
-
// A caller never wires a second signer for either this or the sponsor personal-message.
|
|
5945
|
-
signPersonalMessage: async (message) => {
|
|
5946
|
-
const { signature } = await keypair.signPersonalMessage(message);
|
|
5947
|
-
return signature;
|
|
5948
|
-
},
|
|
5949
|
-
signer: keypair,
|
|
5950
|
-
...options.select ? { select: options.select } : {},
|
|
5951
|
-
...options.deployment ? { deployment: options.deployment } : {},
|
|
5952
|
-
...options.fetchImpl ? { fetchImpl: options.fetchImpl } : {}
|
|
5953
|
-
});
|
|
5954
|
-
if (!resolved.ok)
|
|
5955
|
-
return resolved;
|
|
5956
|
-
const { session, binding } = resolved.value;
|
|
5957
|
-
const deployment = {
|
|
5958
|
-
network: session.network,
|
|
5959
|
-
packageId: session.packageId
|
|
5960
|
-
};
|
|
5961
|
-
let network;
|
|
5962
|
-
try {
|
|
5963
|
-
network = toGrpcNetwork(deployment.network);
|
|
5964
|
-
} catch {
|
|
5965
|
-
return {
|
|
5966
|
-
ok: false,
|
|
5967
|
-
error: {
|
|
5968
|
-
code: MANIFEST_ERROR_CODES.MISSING_NETWORK,
|
|
5969
|
-
detail: `the gateway reports network "${deployment.network}", which has no Sui fullnode this SDK can read; pass fullnodeUrl to name one`
|
|
5970
|
-
}
|
|
5971
|
-
};
|
|
5972
|
-
}
|
|
5973
|
-
let chain;
|
|
5278
|
+
// src/settle.ts
|
|
5279
|
+
async function settlePreparedMcpPayment(args) {
|
|
5974
5280
|
try {
|
|
5975
|
-
|
|
5976
|
-
|
|
5977
|
-
|
|
5978
|
-
|
|
5979
|
-
|
|
5980
|
-
|
|
5981
|
-
|
|
5982
|
-
|
|
5983
|
-
|
|
5984
|
-
|
|
5985
|
-
|
|
5986
|
-
|
|
5987
|
-
}
|
|
5988
|
-
};
|
|
5989
|
-
}
|
|
5990
|
-
const finality = createSpendAccountFinalityReader({ client: chain.client });
|
|
5991
|
-
const getObjectJson = async (objectId) => {
|
|
5992
|
-
const { object } = await chain.client.core.getObject({
|
|
5993
|
-
objectId,
|
|
5994
|
-
include: { json: true }
|
|
5281
|
+
await verifyPreparedSettlementKind({
|
|
5282
|
+
kindBytes: args.signingRequest.unsignedKindBytes,
|
|
5283
|
+
expected: {
|
|
5284
|
+
packageId: args.signingRequest.packageId,
|
|
5285
|
+
delegateAddress: args.expected.delegateAddress,
|
|
5286
|
+
amount: args.expected.amount,
|
|
5287
|
+
recipient: args.expected.recipient,
|
|
5288
|
+
accessMode: args.signingRequest.submit.moveCallTarget.endsWith(
|
|
5289
|
+
"settle_open_recipient_payment"
|
|
5290
|
+
) ? "open" : "scoped"
|
|
5291
|
+
},
|
|
5292
|
+
getObjectJson: args.getObjectJson
|
|
5995
5293
|
});
|
|
5996
|
-
|
|
5997
|
-
|
|
5998
|
-
const verified = await verifyGrantOnChain({
|
|
5999
|
-
getObjectJson,
|
|
6000
|
-
grantObjectId: binding.grantObjectId,
|
|
6001
|
-
poolObjectId: binding.poolObjectId,
|
|
6002
|
-
delegateAddress: address
|
|
6003
|
-
});
|
|
6004
|
-
if (!verified.ok)
|
|
6005
|
-
return verified;
|
|
6006
|
-
const deps = {
|
|
6007
|
-
paymentLedger: options.paymentLedger ?? createMemoryLedger(),
|
|
6008
|
-
// Sponsorship IS a gateway service: the agent holds no SUI for gas, and the
|
|
6009
|
-
// sponsor can only attach gas to a transaction this agent signed.
|
|
6010
|
-
sponsor: { sponsorUrl: options.gatewayUrl },
|
|
6011
|
-
gasMode: "sponsored",
|
|
6012
|
-
resolveSharedObjectVersions: (ids) => resolveSharedObjectVersionsFromGetObject(getObjectJson, ids),
|
|
6013
|
-
getFinalizedTx: finality.getFinalizedTx,
|
|
6014
|
-
...options.offerSecret ? { offerSecret: options.offerSecret } : {},
|
|
6015
|
-
...options.fetchImpl ? { fetchImpl: options.fetchImpl } : {},
|
|
6016
|
-
...options.logError ? { logError: options.logError } : {}
|
|
6017
|
-
};
|
|
6018
|
-
async function pay2(url, input) {
|
|
6019
|
-
let request;
|
|
6020
|
-
try {
|
|
6021
|
-
request = normalizePaidHttpRequest({
|
|
6022
|
-
url,
|
|
6023
|
-
method: input.method,
|
|
6024
|
-
...input.body !== void 0 ? { body: input.body } : {},
|
|
6025
|
-
...input.contentType !== void 0 ? { contentType: input.contentType } : {}
|
|
6026
|
-
});
|
|
6027
|
-
} catch (err) {
|
|
6028
|
-
options.logError?.("normalize paid request failed", err);
|
|
5294
|
+
} catch (err) {
|
|
5295
|
+
if (err instanceof PreparedVerificationUnavailableError) {
|
|
6029
5296
|
return {
|
|
6030
5297
|
status: "failed",
|
|
6031
5298
|
paid: false,
|
|
6032
|
-
code: DELEGATED_PAY_ERROR_CODES.
|
|
6033
|
-
detail:
|
|
5299
|
+
code: DELEGATED_PAY_ERROR_CODES.PREPARED_VERIFY_UNAVAILABLE,
|
|
5300
|
+
detail: err.message
|
|
6034
5301
|
};
|
|
6035
5302
|
}
|
|
6036
|
-
return
|
|
6037
|
-
|
|
6038
|
-
|
|
6039
|
-
|
|
6040
|
-
|
|
6041
|
-
|
|
6042
|
-
intent: input.intent,
|
|
6043
|
-
...input.preparedTerms ? { preparedTerms: input.preparedTerms } : {}
|
|
6044
|
-
});
|
|
6045
|
-
}
|
|
6046
|
-
async function verifySettlement(prepared, offer) {
|
|
6047
|
-
return verifyPreparedPayment({
|
|
6048
|
-
prepared,
|
|
6049
|
-
offer,
|
|
6050
|
-
delegateAddress: address,
|
|
6051
|
-
expectedGrantObjectId: binding.grantObjectId,
|
|
6052
|
-
expectedPoolObjectId: binding.poolObjectId
|
|
6053
|
-
});
|
|
6054
|
-
}
|
|
6055
|
-
return {
|
|
6056
|
-
ok: true,
|
|
6057
|
-
value: { address, deployment, grant: binding, session, pay: pay2, verifySettlement }
|
|
6058
|
-
};
|
|
6059
|
-
}
|
|
6060
|
-
|
|
6061
|
-
// src/stdio-login.ts
|
|
6062
|
-
import { createServer } from "http";
|
|
6063
|
-
import { randomBytes as randomBytes3, timingSafeEqual as timingSafeEqual2 } from "crypto";
|
|
6064
|
-
import open from "open";
|
|
6065
|
-
var HEX642 = /^[0-9a-f]{64}$/;
|
|
6066
|
-
var PUBHEX = /^[0-9a-fA-F]{64}$/;
|
|
6067
|
-
function isPreflight(obj) {
|
|
6068
|
-
if (!obj || typeof obj !== "object") return false;
|
|
6069
|
-
const o = obj;
|
|
6070
|
-
return typeof o.state === "string" && HEX642.test(o.state) && typeof o.publicKey === "string" && PUBHEX.test(o.publicKey) && typeof o.delegateAddress === "string" && /^0x[0-9a-fA-F]{64}$/.test(o.delegateAddress) && typeof o.gateway === "string" && o.gateway.length > 0;
|
|
6071
|
-
}
|
|
6072
|
-
function isCallback(obj) {
|
|
6073
|
-
if (!obj || typeof obj !== "object") return false;
|
|
6074
|
-
const o = obj;
|
|
6075
|
-
return typeof o.state === "string" && HEX642.test(o.state) && typeof o.gatewayUrl === "string" && o.gatewayUrl.length > 0;
|
|
6076
|
-
}
|
|
6077
|
-
function stateEquals(a, b) {
|
|
6078
|
-
if (a.length !== b.length) return false;
|
|
6079
|
-
try {
|
|
6080
|
-
return timingSafeEqual2(Buffer.from(a, "hex"), Buffer.from(b, "hex"));
|
|
6081
|
-
} catch {
|
|
6082
|
-
return false;
|
|
6083
|
-
}
|
|
6084
|
-
}
|
|
6085
|
-
function normalizeOrigin(url) {
|
|
6086
|
-
try {
|
|
6087
|
-
return new URL(url).origin;
|
|
6088
|
-
} catch {
|
|
6089
|
-
return url.replace(/\/+$/, "");
|
|
6090
|
-
}
|
|
6091
|
-
}
|
|
6092
|
-
function loopbackAlternateOrigin(origin) {
|
|
6093
|
-
try {
|
|
6094
|
-
const url = new URL(origin);
|
|
6095
|
-
if (url.protocol !== "http:" && url.protocol !== "https:") return null;
|
|
6096
|
-
if (url.hostname === "localhost") {
|
|
6097
|
-
url.hostname = "127.0.0.1";
|
|
6098
|
-
return url.origin;
|
|
6099
|
-
}
|
|
6100
|
-
if (url.hostname === "127.0.0.1") {
|
|
6101
|
-
url.hostname = "localhost";
|
|
6102
|
-
return url.origin;
|
|
6103
|
-
}
|
|
6104
|
-
return null;
|
|
6105
|
-
} catch {
|
|
6106
|
-
return null;
|
|
6107
|
-
}
|
|
6108
|
-
}
|
|
6109
|
-
function normalizeUrl(url) {
|
|
6110
|
-
return url.replace(/\/+$/, "");
|
|
6111
|
-
}
|
|
6112
|
-
function readBody2(req, maxBytes = 16 * 1024) {
|
|
6113
|
-
return new Promise((resolve, reject2) => {
|
|
6114
|
-
const chunks = [];
|
|
6115
|
-
let len = 0;
|
|
6116
|
-
req.on("data", (c) => {
|
|
6117
|
-
len += c.length;
|
|
6118
|
-
if (len > maxBytes) {
|
|
6119
|
-
reject2(new Error("body too large"));
|
|
6120
|
-
req.destroy();
|
|
6121
|
-
return;
|
|
6122
|
-
}
|
|
6123
|
-
chunks.push(c);
|
|
6124
|
-
});
|
|
6125
|
-
req.on("end", () => resolve(Buffer.concat(chunks).toString("utf8")));
|
|
6126
|
-
req.on("error", reject2);
|
|
6127
|
-
});
|
|
6128
|
-
}
|
|
6129
|
-
var SECURITY_HEADERS = {
|
|
6130
|
-
"cache-control": "no-store",
|
|
6131
|
-
"x-content-type-options": "nosniff",
|
|
6132
|
-
"referrer-policy": "no-referrer",
|
|
6133
|
-
"content-security-policy": "default-src 'none'"
|
|
6134
|
-
};
|
|
6135
|
-
var SUCCESS_HTML = `<!doctype html><meta charset=utf-8><title>SuiPay MCP - Connected</title><body style="font:16px system-ui;max-width:480px;margin:80px auto;padding:0 24px"><h1>\u2713 SuiPay MCP connected</h1><p>Credentials saved to <code>~/.suipay/credentials.json</code>. You can close this tab \u2014 your MCP client will pick up the new credentials automatically.</p>`;
|
|
6136
|
-
var failHtml = (m) => `<!doctype html><meta charset=utf-8><title>SuiPay MCP - Failed</title><body style="font:16px system-ui;max-width:480px;margin:80px auto;padding:0 24px"><h1>\xD7 Login failed</h1><p>${m}</p>`;
|
|
6137
|
-
async function startStdioLogin(opts) {
|
|
6138
|
-
const timeoutMs = opts.timeoutMs ?? 5 * 6e4;
|
|
6139
|
-
const openBrowser = opts.openBrowser ?? false;
|
|
6140
|
-
const label = opts.label ?? "SuiPay MCP";
|
|
6141
|
-
const expectedOrigin = normalizeOrigin(opts.consoleUrl);
|
|
6142
|
-
const allowedCorsOrigins = /* @__PURE__ */ new Set([expectedOrigin]);
|
|
6143
|
-
const loopbackAlt = loopbackAlternateOrigin(expectedOrigin);
|
|
6144
|
-
if (loopbackAlt) allowedCorsOrigins.add(loopbackAlt);
|
|
6145
|
-
const expectedGateway = normalizeUrl(opts.gatewayUrl);
|
|
6146
|
-
const connectState = randomBytes3(32).toString("hex");
|
|
6147
|
-
const server = createServer();
|
|
6148
|
-
server.requestTimeout = 1e4;
|
|
6149
|
-
server.headersTimeout = 1e4;
|
|
6150
|
-
server.keepAliveTimeout = 5e3;
|
|
6151
|
-
await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve));
|
|
6152
|
-
const port = server.address().port;
|
|
6153
|
-
const allowedHosts = /* @__PURE__ */ new Set([`127.0.0.1:${port}`, `localhost:${port}`]);
|
|
6154
|
-
const base = opts.consoleUrl.replace(/\/+$/, "");
|
|
6155
|
-
const connectUrl = `${base}/connect/agent?port=${port}&publicKey=${encodeURIComponent(opts.identity.publicKeyHex)}&delegateAddress=${encodeURIComponent(opts.identity.delegateAddress)}&label=${encodeURIComponent(label)}&gateway=${encodeURIComponent(opts.gatewayUrl)}&connectState=${connectState}`;
|
|
6156
|
-
try {
|
|
6157
|
-
opts.onUrl?.(connectUrl);
|
|
6158
|
-
} catch {
|
|
5303
|
+
return {
|
|
5304
|
+
status: "failed",
|
|
5305
|
+
paid: false,
|
|
5306
|
+
code: DELEGATED_PAY_ERROR_CODES.TX_MISMATCH,
|
|
5307
|
+
detail: err instanceof Error ? err.message : "prepared transaction mismatch"
|
|
5308
|
+
};
|
|
6159
5309
|
}
|
|
6160
|
-
|
|
6161
|
-
|
|
6162
|
-
|
|
6163
|
-
|
|
6164
|
-
|
|
6165
|
-
|
|
6166
|
-
|
|
6167
|
-
|
|
6168
|
-
resolve();
|
|
6169
|
-
}
|
|
6170
|
-
}, timeoutMs);
|
|
6171
|
-
timer.unref?.();
|
|
6172
|
-
server.on("request", async (req, res) => {
|
|
6173
|
-
const hostHeader = (req.headers.host ?? "").toLowerCase();
|
|
6174
|
-
const origin = req.headers.origin;
|
|
6175
|
-
const url = new URL(req.url ?? "/", `http://127.0.0.1:${port}`);
|
|
6176
|
-
const okHost = allowedHosts.has(hostHeader);
|
|
6177
|
-
let requestOrigin = null;
|
|
6178
|
-
if (typeof origin === "string") {
|
|
6179
|
-
try {
|
|
6180
|
-
requestOrigin = new URL(origin).origin;
|
|
6181
|
-
} catch {
|
|
6182
|
-
requestOrigin = null;
|
|
6183
|
-
}
|
|
6184
|
-
}
|
|
6185
|
-
const okOrigin = requestOrigin !== null && allowedCorsOrigins.has(requestOrigin);
|
|
6186
|
-
const cors = {
|
|
6187
|
-
"access-control-allow-origin": okOrigin && requestOrigin ? requestOrigin : expectedOrigin,
|
|
6188
|
-
"access-control-allow-methods": "POST, OPTIONS",
|
|
6189
|
-
"access-control-allow-headers": "content-type",
|
|
6190
|
-
vary: "origin"
|
|
6191
|
-
};
|
|
6192
|
-
const send = (code, body, type = "text/plain", extra = {}) => {
|
|
6193
|
-
res.writeHead(code, { "content-type": type, ...SECURITY_HEADERS, ...extra });
|
|
6194
|
-
res.end(body);
|
|
6195
|
-
};
|
|
6196
|
-
const okPath = url.pathname === "/preflight" || url.pathname === "/callback";
|
|
6197
|
-
if (req.method === "OPTIONS") {
|
|
6198
|
-
const extra = { ...cors };
|
|
6199
|
-
if (okHost && okOrigin && okPath && req.headers["access-control-request-private-network"] === "true") {
|
|
6200
|
-
extra["access-control-allow-private-network"] = "true";
|
|
6201
|
-
}
|
|
6202
|
-
res.writeHead(204, { ...SECURITY_HEADERS, ...extra });
|
|
6203
|
-
res.end();
|
|
6204
|
-
return;
|
|
6205
|
-
}
|
|
6206
|
-
if (req.method !== "POST" || !okPath) {
|
|
6207
|
-
send(404, "Not found");
|
|
6208
|
-
return;
|
|
6209
|
-
}
|
|
6210
|
-
if (!okHost) {
|
|
6211
|
-
send(403, "Forbidden");
|
|
6212
|
-
return;
|
|
6213
|
-
}
|
|
6214
|
-
if (!okOrigin) {
|
|
6215
|
-
send(403, "Forbidden");
|
|
6216
|
-
return;
|
|
6217
|
-
}
|
|
6218
|
-
const ct = (req.headers["content-type"] ?? "").toLowerCase();
|
|
6219
|
-
if (!ct.startsWith("application/json")) {
|
|
6220
|
-
send(415, "Content-Type must be application/json");
|
|
6221
|
-
return;
|
|
6222
|
-
}
|
|
6223
|
-
try {
|
|
6224
|
-
const parsed = JSON.parse(await readBody2(req));
|
|
6225
|
-
if (url.pathname === "/preflight") {
|
|
6226
|
-
if (!isPreflight(parsed)) {
|
|
6227
|
-
res.writeHead(400, {
|
|
6228
|
-
"content-type": "application/json",
|
|
6229
|
-
...cors,
|
|
6230
|
-
...SECURITY_HEADERS
|
|
6231
|
-
});
|
|
6232
|
-
res.end(JSON.stringify({ ok: false, error: "invalid preflight payload" }));
|
|
6233
|
-
return;
|
|
6234
|
-
}
|
|
6235
|
-
const derived = deriveSuiAddress(
|
|
6236
|
-
Uint8Array.from(Buffer.from(parsed.publicKey, "hex"))
|
|
6237
|
-
);
|
|
6238
|
-
const valid = stateEquals(parsed.state, connectState) && stateEquals(
|
|
6239
|
-
parsed.publicKey.toLowerCase(),
|
|
6240
|
-
opts.identity.publicKeyHex.toLowerCase()
|
|
6241
|
-
) && parsed.delegateAddress.toLowerCase() === opts.identity.delegateAddress.toLowerCase() && derived.toLowerCase() === opts.identity.delegateAddress.toLowerCase() && normalizeUrl(parsed.gateway) === expectedGateway;
|
|
6242
|
-
if (!valid) {
|
|
6243
|
-
res.writeHead(403, {
|
|
6244
|
-
"content-type": "application/json",
|
|
6245
|
-
...cors,
|
|
6246
|
-
...SECURITY_HEADERS
|
|
6247
|
-
});
|
|
6248
|
-
res.end(JSON.stringify({ ok: false, error: "preflight mismatch" }));
|
|
6249
|
-
return;
|
|
6250
|
-
}
|
|
6251
|
-
preflightVerified = true;
|
|
6252
|
-
res.writeHead(200, {
|
|
6253
|
-
"content-type": "application/json",
|
|
6254
|
-
...cors,
|
|
6255
|
-
...SECURITY_HEADERS
|
|
6256
|
-
});
|
|
6257
|
-
res.end(
|
|
6258
|
-
JSON.stringify({
|
|
6259
|
-
ok: true,
|
|
6260
|
-
publicKey: opts.identity.publicKeyHex,
|
|
6261
|
-
label,
|
|
6262
|
-
gateway: opts.gatewayUrl
|
|
6263
|
-
})
|
|
6264
|
-
);
|
|
6265
|
-
return;
|
|
6266
|
-
}
|
|
6267
|
-
if (!preflightVerified) {
|
|
6268
|
-
res.writeHead(428, {
|
|
6269
|
-
"content-type": "text/html",
|
|
6270
|
-
...cors,
|
|
6271
|
-
...SECURITY_HEADERS
|
|
6272
|
-
});
|
|
6273
|
-
res.end(
|
|
6274
|
-
failHtml("Local MCP bridge verification is required before the callback.")
|
|
6275
|
-
);
|
|
6276
|
-
return;
|
|
6277
|
-
}
|
|
6278
|
-
if (!isCallback(parsed)) {
|
|
6279
|
-
res.writeHead(400, {
|
|
6280
|
-
"content-type": "text/html",
|
|
6281
|
-
...cors,
|
|
6282
|
-
...SECURITY_HEADERS
|
|
6283
|
-
});
|
|
6284
|
-
res.end(failHtml("Callback payload missing required fields."));
|
|
6285
|
-
return;
|
|
6286
|
-
}
|
|
6287
|
-
if (!stateEquals(parsed.state, connectState)) {
|
|
6288
|
-
res.writeHead(403, {
|
|
6289
|
-
"content-type": "text/html",
|
|
6290
|
-
...cors,
|
|
6291
|
-
...SECURITY_HEADERS
|
|
6292
|
-
});
|
|
6293
|
-
res.end(failHtml("Callback state mismatch \u2014 refusing to save."));
|
|
6294
|
-
return;
|
|
6295
|
-
}
|
|
6296
|
-
if (normalizeUrl(parsed.gatewayUrl) !== expectedGateway) {
|
|
6297
|
-
res.writeHead(403, {
|
|
6298
|
-
"content-type": "text/html",
|
|
6299
|
-
...cors,
|
|
6300
|
-
...SECURITY_HEADERS
|
|
6301
|
-
});
|
|
6302
|
-
res.end(failHtml("Callback gateway mismatch \u2014 refusing to save."));
|
|
6303
|
-
return;
|
|
6304
|
-
}
|
|
6305
|
-
preflightVerified = false;
|
|
6306
|
-
persistGatewayUrl(opts.gatewayUrl);
|
|
6307
|
-
completed = true;
|
|
6308
|
-
res.writeHead(200, {
|
|
6309
|
-
"content-type": "text/html",
|
|
6310
|
-
...cors,
|
|
6311
|
-
...SECURITY_HEADERS
|
|
6312
|
-
});
|
|
6313
|
-
res.end(SUCCESS_HTML);
|
|
6314
|
-
setTimeout(() => server.close(), 100);
|
|
6315
|
-
resolve();
|
|
6316
|
-
} catch (err) {
|
|
6317
|
-
error2 = err instanceof Error ? err : new Error(String(err));
|
|
6318
|
-
res.writeHead(500, {
|
|
6319
|
-
"content-type": "text/html",
|
|
6320
|
-
...cors,
|
|
6321
|
-
...SECURITY_HEADERS
|
|
6322
|
-
});
|
|
6323
|
-
res.end(failHtml("Internal error parsing callback."));
|
|
6324
|
-
setTimeout(() => server.close(), 100);
|
|
6325
|
-
resolve();
|
|
6326
|
-
}
|
|
6327
|
-
});
|
|
6328
|
-
});
|
|
6329
|
-
if (openBrowser) {
|
|
6330
|
-
open(connectUrl).catch(() => {
|
|
6331
|
-
});
|
|
5310
|
+
const prepared = args.signingRequest.resource;
|
|
5311
|
+
if (args.request.url !== prepared.url || args.request.method !== prepared.method) {
|
|
5312
|
+
return {
|
|
5313
|
+
status: "failed",
|
|
5314
|
+
paid: false,
|
|
5315
|
+
code: DELEGATED_PAY_ERROR_CODES.UNSUPPORTED_REQUEST,
|
|
5316
|
+
detail: "prepared resource request cannot be replayed"
|
|
5317
|
+
};
|
|
6332
5318
|
}
|
|
6333
|
-
|
|
6334
|
-
|
|
6335
|
-
|
|
5319
|
+
return payDelegatedSpendAccount({
|
|
5320
|
+
url: args.signingRequest.resource.url,
|
|
5321
|
+
request: args.request,
|
|
5322
|
+
session: args.session,
|
|
5323
|
+
cfg: args.cfg,
|
|
5324
|
+
deps: args.deps,
|
|
5325
|
+
preparedTerms: {
|
|
5326
|
+
recipient: args.expected.recipient,
|
|
5327
|
+
amount: args.expected.amount,
|
|
5328
|
+
asset: args.signingRequest.coinType,
|
|
5329
|
+
targetHash: args.signingRequest.targetHash
|
|
5330
|
+
},
|
|
5331
|
+
...args.intent ? { intent: args.intent } : {}
|
|
6336
5332
|
});
|
|
6337
|
-
return {
|
|
6338
|
-
connectUrl,
|
|
6339
|
-
port,
|
|
6340
|
-
connectState,
|
|
6341
|
-
done,
|
|
6342
|
-
close: () => {
|
|
6343
|
-
server.close();
|
|
6344
|
-
}
|
|
6345
|
-
};
|
|
6346
5333
|
}
|
|
6347
5334
|
|
|
6348
5335
|
// src/spend-account-pay.ts
|
|
6349
|
-
import { Transaction as
|
|
5336
|
+
import { Transaction as Transaction5 } from "@mysten/sui/transactions";
|
|
6350
5337
|
import {
|
|
6351
|
-
fromBase64 as
|
|
6352
|
-
normalizeSuiAddress as
|
|
5338
|
+
fromBase64 as fromBase645,
|
|
5339
|
+
normalizeSuiAddress as normalizeSuiAddress9,
|
|
6353
5340
|
toBase64 as toBase644
|
|
6354
5341
|
} from "@mysten/sui/utils";
|
|
6355
5342
|
var SUI_CLOCK3 = "0x6";
|
|
@@ -6387,11 +5374,11 @@ async function buildSpendAccountPayKind2(input) {
|
|
|
6387
5374
|
if (!input.termsHash.length) throw new Error("termsHash must be non-empty");
|
|
6388
5375
|
if (!input.paymentIdHash.length) throw new Error("paymentIdHash must be non-empty");
|
|
6389
5376
|
const auth = defaultAuthorityForRail("delegate", input.packageId);
|
|
6390
|
-
const target = `${
|
|
5377
|
+
const target = `${normalizeSuiAddress9(auth.packageId)}::${auth.module}::${auth.payFunction}`;
|
|
6391
5378
|
const hashHex = input.targetHash.replace(/^0x/i, "").toLowerCase();
|
|
6392
5379
|
if (!/^[0-9a-f]{64}$/.test(hashHex)) throw new Error("targetHash must be 32-byte hex");
|
|
6393
5380
|
const hash = Uint8Array.from(Buffer.from(hashHex, "hex"));
|
|
6394
|
-
const tx = new
|
|
5381
|
+
const tx = new Transaction5();
|
|
6395
5382
|
tx.setSender(input.delegate);
|
|
6396
5383
|
tx.moveCall({
|
|
6397
5384
|
target,
|
|
@@ -6425,29 +5412,29 @@ async function buildSpendAccountPayKind2(input) {
|
|
|
6425
5412
|
function verifySpendAccountPayReconstructs(bytes, expected) {
|
|
6426
5413
|
let data;
|
|
6427
5414
|
try {
|
|
6428
|
-
data =
|
|
5415
|
+
data = Transaction5.from(fromBase645(bytes)).getData();
|
|
6429
5416
|
} catch {
|
|
6430
5417
|
throw new Error("sponsored transaction is not valid Sui transaction bytes");
|
|
6431
5418
|
}
|
|
6432
|
-
if (!data.sender ||
|
|
5419
|
+
if (!data.sender || normalizeSuiAddress9(data.sender) !== normalizeSuiAddress9(expected.delegate)) {
|
|
6433
5420
|
throw new Error("sponsored transaction sender does not match delegate");
|
|
6434
5421
|
}
|
|
6435
5422
|
if (!data.commands || data.commands.length !== 1) {
|
|
6436
5423
|
throw new Error("sponsored transaction must contain exactly one command");
|
|
6437
5424
|
}
|
|
6438
5425
|
const auth = defaultAuthorityForRail("delegate", expected.packageId);
|
|
6439
|
-
const wanted = `${
|
|
5426
|
+
const wanted = `${normalizeSuiAddress9(auth.packageId)}::${auth.module}::${auth.payFunction}`;
|
|
6440
5427
|
const cmd = data.commands[0];
|
|
6441
5428
|
if (cmd.$kind !== "MoveCall" || !cmd.MoveCall) {
|
|
6442
5429
|
throw new Error("sponsored transaction is not a MoveCall");
|
|
6443
5430
|
}
|
|
6444
|
-
const got = `${
|
|
5431
|
+
const got = `${normalizeSuiAddress9(String(cmd.MoveCall.package))}::${cmd.MoveCall.module}::${cmd.MoveCall.function}`;
|
|
6445
5432
|
if (got !== wanted) {
|
|
6446
5433
|
throw new Error(`sponsored transaction does not reconstruct ${wanted}`);
|
|
6447
5434
|
}
|
|
6448
5435
|
let expectedKind;
|
|
6449
5436
|
try {
|
|
6450
|
-
expectedKind =
|
|
5437
|
+
expectedKind = Transaction5.fromKind(fromBase645(expected.kindBytes)).getData();
|
|
6451
5438
|
} catch {
|
|
6452
5439
|
throw new Error("sponsored transaction kind baseline invalid");
|
|
6453
5440
|
}
|
|
@@ -6604,7 +5591,7 @@ async function prepareSpendAccountPayment(args) {
|
|
|
6604
5591
|
const signingRequest = {
|
|
6605
5592
|
unsignedKindBytes: kindBytes,
|
|
6606
5593
|
delegateAddress: resolved.delegateAddress,
|
|
6607
|
-
packageId:
|
|
5594
|
+
packageId: normalizeSuiAddress9(authority.packageId),
|
|
6608
5595
|
poolObjectId: resolved.poolObjectId,
|
|
6609
5596
|
grantObjectId: resolved.grantObjectId,
|
|
6610
5597
|
policyId: resolved.onChainPolicyId,
|
|
@@ -6685,203 +5672,28 @@ function mapErrorCode(code) {
|
|
|
6685
5672
|
}
|
|
6686
5673
|
}
|
|
6687
5674
|
|
|
6688
|
-
// src/
|
|
6689
|
-
|
|
6690
|
-
|
|
6691
|
-
|
|
6692
|
-
|
|
6693
|
-
|
|
6694
|
-
|
|
6695
|
-
|
|
6696
|
-
|
|
6697
|
-
|
|
6698
|
-
|
|
6699
|
-
|
|
6700
|
-
|
|
6701
|
-
|
|
6702
|
-
}
|
|
6703
|
-
if (typeof value !== "string") return null;
|
|
6704
|
-
const raw = value.trim();
|
|
6705
|
-
if (!/^[0-9]+$/.test(raw)) return null;
|
|
6706
|
-
return BigInt(raw);
|
|
6707
|
-
}
|
|
6708
|
-
function catalogPriceAtomic(row) {
|
|
6709
|
-
return parseAtomic(row.price);
|
|
6710
|
-
}
|
|
6711
|
-
function catalogDecimals(row) {
|
|
6712
|
-
if (typeof row.decimals !== "number" || !Number.isInteger(row.decimals)) return null;
|
|
6713
|
-
if (row.decimals < 0 || row.decimals > 18) return null;
|
|
6714
|
-
return row.decimals;
|
|
6715
|
-
}
|
|
6716
|
-
function formatAtomic(amount, decimals) {
|
|
6717
|
-
if (!Number.isInteger(decimals) || decimals < 0 || decimals > 18) {
|
|
6718
|
-
return amount.toString();
|
|
6719
|
-
}
|
|
6720
|
-
const negative = amount < 0n;
|
|
6721
|
-
const abs = negative ? -amount : amount;
|
|
6722
|
-
const scale = 10n ** BigInt(decimals);
|
|
6723
|
-
const whole = abs / scale;
|
|
6724
|
-
const frac = abs % scale;
|
|
6725
|
-
const sign = negative ? "-" : "";
|
|
6726
|
-
if (decimals === 0 || frac === 0n) return `${sign}${whole.toString()}`;
|
|
6727
|
-
const fracStr = frac.toString().padStart(decimals, "0").replace(/0+$/, "");
|
|
6728
|
-
return `${sign}${whole.toString()}.${fracStr}`;
|
|
6729
|
-
}
|
|
6730
|
-
function minAtomic(left, right) {
|
|
6731
|
-
if (left == null) return right;
|
|
6732
|
-
if (right == null) return left;
|
|
6733
|
-
return left <= right ? left : right;
|
|
6734
|
-
}
|
|
6735
|
-
function isAffordable(price, budget, opts = {}) {
|
|
6736
|
-
if (price == null) return void 0;
|
|
6737
|
-
if (!sameCoinType2(opts.asset, budget.coinType)) return void 0;
|
|
6738
|
-
const remaining = parseAtomic(budget.sessionRemaining);
|
|
6739
|
-
if (remaining == null) return void 0;
|
|
6740
|
-
if (price > remaining) return false;
|
|
6741
|
-
const cap = minAtomic(
|
|
6742
|
-
parseAtomic(budget.maxPerPayment),
|
|
6743
|
-
parseAtomic(opts.targetMaxPerPayment ?? null)
|
|
6744
|
-
);
|
|
6745
|
-
if (cap != null && price > cap) return false;
|
|
6746
|
-
return true;
|
|
6747
|
-
}
|
|
6748
|
-
function withCatalogAffordability(service, budget) {
|
|
6749
|
-
if (!budget) return service;
|
|
6750
|
-
const price = catalogPriceAtomic(service);
|
|
6751
|
-
const decimals = catalogDecimals(service);
|
|
6752
|
-
const targetMax = typeof service.maxPerPayment === "string" ? service.maxPerPayment : null;
|
|
6753
|
-
const sameAsset = sameCoinType2(service.asset, budget.coinType);
|
|
6754
|
-
const affordable = isAffordable(price, budget, {
|
|
6755
|
-
targetMaxPerPayment: targetMax,
|
|
6756
|
-
asset: service.asset
|
|
6757
|
-
});
|
|
6758
|
-
const remaining = sameAsset ? parseAtomic(budget.sessionRemaining) : null;
|
|
6759
|
-
const out = { ...service };
|
|
6760
|
-
if (price != null) out.price = price.toString();
|
|
6761
|
-
if (decimals != null) out.decimals = decimals;
|
|
6762
|
-
if (remaining != null) out.sessionRemaining = remaining.toString();
|
|
6763
|
-
if (affordable !== void 0) out.affordable = affordable;
|
|
6764
|
-
if (decimals != null && price != null) out.priceDisplay = formatAtomic(price, decimals);
|
|
6765
|
-
if (decimals != null && remaining != null) {
|
|
6766
|
-
out.remainingDisplay = formatAtomic(remaining, decimals);
|
|
6767
|
-
}
|
|
6768
|
-
return out;
|
|
6769
|
-
}
|
|
6770
|
-
|
|
6771
|
-
// src/discover-catalog.ts
|
|
6772
|
-
function asString(value) {
|
|
6773
|
-
return typeof value === "string" ? value.trim() : "";
|
|
6774
|
-
}
|
|
6775
|
-
function catalogRows(catalog) {
|
|
6776
|
-
if (Array.isArray(catalog)) return catalog.filter((row) => row && typeof row === "object");
|
|
6777
|
-
if (!catalog || typeof catalog !== "object") return [];
|
|
6778
|
-
const services = catalog.services;
|
|
6779
|
-
if (!Array.isArray(services)) return [];
|
|
6780
|
-
return services.filter((row) => row && typeof row === "object");
|
|
6781
|
-
}
|
|
6782
|
-
function authorizationEpochOf(row) {
|
|
6783
|
-
if (typeof row.authorizationEpoch === "number" && Number.isInteger(row.authorizationEpoch)) {
|
|
6784
|
-
return row.authorizationEpoch;
|
|
6785
|
-
}
|
|
6786
|
-
const state = row.state;
|
|
6787
|
-
if (state && typeof state === "object") {
|
|
6788
|
-
const revision = state.revision;
|
|
6789
|
-
if (typeof revision?.authorizationEpoch === "number" && Number.isInteger(revision.authorizationEpoch)) {
|
|
6790
|
-
return revision.authorizationEpoch;
|
|
6791
|
-
}
|
|
6792
|
-
}
|
|
6793
|
-
return 1;
|
|
6794
|
-
}
|
|
6795
|
-
function catalogRowTargetHash(row) {
|
|
6796
|
-
const id = asString(row.id);
|
|
6797
|
-
const path = asString(row.path);
|
|
6798
|
-
const method = asString(row.method) || "GET";
|
|
6799
|
-
const network = asString(row.network);
|
|
6800
|
-
const asset = asString(row.asset);
|
|
6801
|
-
const recipient = asString(row.payTo) || asString(row.recipient);
|
|
6802
|
-
const providerId = asString(row.ownerAddress) || asString(row.providerId);
|
|
6803
|
-
const endpointId = asString(row.endpointId) || id;
|
|
6804
|
-
if (!id || !path || !network || !asset || !recipient || !providerId) return null;
|
|
6805
|
-
try {
|
|
6806
|
-
return targetHashFromFingerprint(
|
|
6807
|
-
authorizationFingerprint({
|
|
6808
|
-
network,
|
|
6809
|
-
providerId,
|
|
6810
|
-
serviceId: id,
|
|
6811
|
-
endpointId,
|
|
6812
|
-
method,
|
|
6813
|
-
path,
|
|
6814
|
-
asset,
|
|
6815
|
-
recipient,
|
|
6816
|
-
authorizationEpoch: authorizationEpochOf(row)
|
|
6817
|
-
})
|
|
6818
|
-
).toLowerCase();
|
|
6819
|
-
} catch {
|
|
6820
|
-
return null;
|
|
6821
|
-
}
|
|
6822
|
-
}
|
|
6823
|
-
function catalogRowResourceUrl(row, gatewayUrl) {
|
|
6824
|
-
const listed = asString(row.resourceUrl);
|
|
6825
|
-
if (/^https?:\/\//i.test(listed)) return listed.replace(/\/+$/, "");
|
|
6826
|
-
const path = asString(row.path);
|
|
6827
|
-
const base = gatewayUrl.replace(/\/+$/, "");
|
|
6828
|
-
if (!path) return base;
|
|
6829
|
-
return `${base}${path.startsWith("/") ? path : `/${path}`}`;
|
|
6830
|
-
}
|
|
6831
|
-
function matchesQuery(row, query) {
|
|
6832
|
-
if (!query) return true;
|
|
6833
|
-
return JSON.stringify(row).toLowerCase().includes(query);
|
|
6834
|
-
}
|
|
6835
|
-
function joinDiscoverCatalog(opts) {
|
|
6836
|
-
const query = opts.query?.trim().toLowerCase() ?? "";
|
|
6837
|
-
const rows = catalogRows(opts.catalog);
|
|
6838
|
-
const scoped = Array.isArray(opts.policies);
|
|
6839
|
-
const byHash = /* @__PURE__ */ new Map();
|
|
6840
|
-
const allowedTargetHashes = [];
|
|
6841
|
-
if (scoped) {
|
|
6842
|
-
for (const policy of opts.policies) {
|
|
6843
|
-
for (const target of policy.targets) {
|
|
6844
|
-
const hash = asString(target.targetHash).toLowerCase();
|
|
6845
|
-
if (!hash) continue;
|
|
6846
|
-
allowedTargetHashes.push(hash);
|
|
6847
|
-
if (!byHash.has(hash)) byHash.set(hash, { ...policy, target });
|
|
6848
|
-
}
|
|
6849
|
-
}
|
|
6850
|
-
}
|
|
6851
|
-
const services = [];
|
|
6852
|
-
for (const row of rows) {
|
|
6853
|
-
const targetHash = catalogRowTargetHash(row);
|
|
6854
|
-
const url = catalogRowResourceUrl(row, opts.gatewayUrl);
|
|
6855
|
-
const bound = targetHash && scoped ? byHash.get(targetHash) : void 0;
|
|
6856
|
-
if (scoped && !bound) continue;
|
|
6857
|
-
const service = {
|
|
6858
|
-
...row,
|
|
6859
|
-
...targetHash ? { targetHash } : {},
|
|
6860
|
-
url
|
|
6861
|
-
};
|
|
6862
|
-
if (bound) {
|
|
6863
|
-
service.policyId = bound.policyId;
|
|
6864
|
-
service.onChainPolicyId = bound.onChainPolicyId;
|
|
6865
|
-
service.policyStatus = bound.status;
|
|
6866
|
-
if (bound.target.recipient) service.recipient = bound.target.recipient;
|
|
6867
|
-
if (bound.target.maxPerPayment) service.maxPerPayment = bound.target.maxPerPayment;
|
|
6868
|
-
}
|
|
6869
|
-
if (!matchesQuery(service, query)) continue;
|
|
6870
|
-
services.push(withCatalogAffordability(service, opts.budget));
|
|
6871
|
-
}
|
|
6872
|
-
return scoped ? { accessMode: "scoped", count: services.length, services, allowedTargetHashes } : { accessMode: "open", count: services.length, services };
|
|
5675
|
+
// src/tools.ts
|
|
5676
|
+
function payerTrace(deps) {
|
|
5677
|
+
const hooks = deps.trace;
|
|
5678
|
+
const buyerAccountId = deps.auth?.buyerAccountId;
|
|
5679
|
+
if (!hooks || !buyerAccountId) return null;
|
|
5680
|
+
return {
|
|
5681
|
+
sink: hooks.sink,
|
|
5682
|
+
traceId: hooks.context.traceId,
|
|
5683
|
+
requestId: hooks.context.requestId,
|
|
5684
|
+
buyerAccountId,
|
|
5685
|
+
connectionId: hooks.runtime.connectionId,
|
|
5686
|
+
...hooks.runtime.secret ? { secret: hooks.runtime.secret } : {},
|
|
5687
|
+
...hooks.runtime.now ? { now: hooks.runtime.now } : {}
|
|
5688
|
+
};
|
|
6873
5689
|
}
|
|
6874
|
-
|
|
6875
|
-
// src/tool-result.ts
|
|
6876
|
-
var MAX_MCP_IMAGE_BYTES = 5 * 1024 * 1024;
|
|
5690
|
+
var AUTH_REQUIRED = "No SuiPay credentials - run suipay_login to create a local delegate key, then bind a spend-account grant in the console.";
|
|
6877
5691
|
function text(value) {
|
|
6878
5692
|
return {
|
|
6879
|
-
content: [
|
|
6880
|
-
|
|
6881
|
-
|
|
6882
|
-
|
|
6883
|
-
}
|
|
6884
|
-
]
|
|
5693
|
+
content: [{
|
|
5694
|
+
type: "text",
|
|
5695
|
+
text: typeof value === "string" ? value : JSON.stringify(value, null, 2)
|
|
5696
|
+
}]
|
|
6885
5697
|
};
|
|
6886
5698
|
}
|
|
6887
5699
|
function error(value) {
|
|
@@ -6893,6 +5705,7 @@ function closedError(code, detail) {
|
|
|
6893
5705
|
function closedPayError(code, detail) {
|
|
6894
5706
|
return error({ status: "failed", paid: false, code, detail });
|
|
6895
5707
|
}
|
|
5708
|
+
var MAX_MCP_IMAGE_BYTES = 5 * 1024 * 1024;
|
|
6896
5709
|
function paidResult(result) {
|
|
6897
5710
|
if (!isBinaryBody(result.body)) return text(result);
|
|
6898
5711
|
const body = result.body;
|
|
@@ -6913,137 +5726,6 @@ function paidResult(result) {
|
|
|
6913
5726
|
}
|
|
6914
5727
|
return { content };
|
|
6915
5728
|
}
|
|
6916
|
-
function settlePayResult(result) {
|
|
6917
|
-
if (result.paid === true) return paidResult(result);
|
|
6918
|
-
return error(result);
|
|
6919
|
-
}
|
|
6920
|
-
|
|
6921
|
-
// src/pay-file.ts
|
|
6922
|
-
import { isAbsolute } from "path";
|
|
6923
|
-
import { openSync as openSync2, closeSync as closeSync2, fstatSync, readFileSync as readFileSync2, constants } from "fs";
|
|
6924
|
-
var MCP_MAX_FILE_FIELD_BYTES = 32 * 1024;
|
|
6925
|
-
var FIELD = /^[A-Za-z][A-Za-z0-9_]{0,63}$/;
|
|
6926
|
-
var FORBIDDEN = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
|
|
6927
|
-
function fail2(code, detail) {
|
|
6928
|
-
return { code, detail };
|
|
6929
|
-
}
|
|
6930
|
-
function parseField(value) {
|
|
6931
|
-
if (typeof value !== "string" || !FIELD.test(value) || FORBIDDEN.has(value)) {
|
|
6932
|
-
return fail2("INVALID_FILE_FIELD", "fileField must be a simple JSON key");
|
|
6933
|
-
}
|
|
6934
|
-
return value;
|
|
6935
|
-
}
|
|
6936
|
-
function parsePath(value) {
|
|
6937
|
-
if (typeof value !== "string" || value.length === 0 || value.includes("\0")) {
|
|
6938
|
-
return fail2("INVALID_FILE", "file must be a non-empty absolute path");
|
|
6939
|
-
}
|
|
6940
|
-
if (!isAbsolute(value) || value.includes("://")) {
|
|
6941
|
-
return fail2("INVALID_FILE", "file must be an absolute filesystem path, not a URL");
|
|
6942
|
-
}
|
|
6943
|
-
return value;
|
|
6944
|
-
}
|
|
6945
|
-
function readRegularFile(path) {
|
|
6946
|
-
let fd;
|
|
6947
|
-
try {
|
|
6948
|
-
const flags = constants.O_RDONLY | (constants.O_NOFOLLOW !== void 0 ? constants.O_NOFOLLOW : 0);
|
|
6949
|
-
fd = openSync2(path, flags);
|
|
6950
|
-
const st = fstatSync(fd);
|
|
6951
|
-
if (!st.isFile()) {
|
|
6952
|
-
return fail2("INVALID_FILE", "file must be a regular file");
|
|
6953
|
-
}
|
|
6954
|
-
if (st.size === 0) {
|
|
6955
|
-
return fail2("EMPTY_FILE", "file is empty");
|
|
6956
|
-
}
|
|
6957
|
-
if (st.size > MCP_MAX_FILE_FIELD_BYTES) {
|
|
6958
|
-
return fail2(
|
|
6959
|
-
"FILE_TOO_LARGE",
|
|
6960
|
-
`file exceeds ${MCP_MAX_FILE_FIELD_BYTES} decoded bytes`
|
|
6961
|
-
);
|
|
6962
|
-
}
|
|
6963
|
-
const bytes = readFileSync2(fd);
|
|
6964
|
-
if (bytes.byteLength === 0) {
|
|
6965
|
-
return fail2("EMPTY_FILE", "file is empty");
|
|
6966
|
-
}
|
|
6967
|
-
if (bytes.byteLength > MCP_MAX_FILE_FIELD_BYTES) {
|
|
6968
|
-
return fail2(
|
|
6969
|
-
"FILE_TOO_LARGE",
|
|
6970
|
-
`file exceeds ${MCP_MAX_FILE_FIELD_BYTES} decoded bytes`
|
|
6971
|
-
);
|
|
6972
|
-
}
|
|
6973
|
-
return bytes;
|
|
6974
|
-
} catch (err) {
|
|
6975
|
-
const code = err.code;
|
|
6976
|
-
if (code === "ENOENT") return fail2("FILE_NOT_FOUND", "file does not exist");
|
|
6977
|
-
if (code === "ELOOP" || code === "EMLINK") {
|
|
6978
|
-
return fail2("INVALID_FILE", "file must be a regular file");
|
|
6979
|
-
}
|
|
6980
|
-
return fail2("INVALID_FILE", "file could not be read");
|
|
6981
|
-
} finally {
|
|
6982
|
-
if (fd !== void 0) closeSync2(fd);
|
|
6983
|
-
}
|
|
6984
|
-
}
|
|
6985
|
-
function toCanonicalBase64(bytes) {
|
|
6986
|
-
return Buffer.from(bytes).toString("base64");
|
|
6987
|
-
}
|
|
6988
|
-
function jsonWithLocalFile(input) {
|
|
6989
|
-
const hasFile = input.file !== void 0 && input.file !== null && input.file !== "";
|
|
6990
|
-
const hasField = input.fileField !== void 0 && input.fileField !== null && input.fileField !== "";
|
|
6991
|
-
if (!hasFile && !hasField) return { json: input.json };
|
|
6992
|
-
if (hasFile !== hasField) {
|
|
6993
|
-
return {
|
|
6994
|
-
error: fail2(
|
|
6995
|
-
"INVALID_FILE_ARGS",
|
|
6996
|
-
"file and fileField are required together"
|
|
6997
|
-
)
|
|
6998
|
-
};
|
|
6999
|
-
}
|
|
7000
|
-
const method = input.method === void 0 || input.method === null || input.method === "" ? "GET" : String(input.method).trim().toUpperCase();
|
|
7001
|
-
if (method !== "POST") {
|
|
7002
|
-
return { error: fail2("INVALID_FILE_ARGS", "file is only valid on POST") };
|
|
7003
|
-
}
|
|
7004
|
-
const field = parseField(input.fileField);
|
|
7005
|
-
if (typeof field !== "string") return { error: field };
|
|
7006
|
-
const path = parsePath(input.file);
|
|
7007
|
-
if (typeof path !== "string") return { error: path };
|
|
7008
|
-
let json;
|
|
7009
|
-
if (input.json === void 0) {
|
|
7010
|
-
json = {};
|
|
7011
|
-
} else if (typeof input.json === "object" && input.json !== null && !Array.isArray(input.json)) {
|
|
7012
|
-
json = { ...input.json };
|
|
7013
|
-
} else {
|
|
7014
|
-
return { error: fail2("MALFORMED_REQUEST", "POST requires json object") };
|
|
7015
|
-
}
|
|
7016
|
-
if (Object.prototype.hasOwnProperty.call(json, field)) {
|
|
7017
|
-
return {
|
|
7018
|
-
error: fail2(
|
|
7019
|
-
"FILE_FIELD_EXISTS",
|
|
7020
|
-
"json already has fileField; refuse to overwrite"
|
|
7021
|
-
)
|
|
7022
|
-
};
|
|
7023
|
-
}
|
|
7024
|
-
const bytes = readRegularFile(path);
|
|
7025
|
-
if (!(bytes instanceof Uint8Array)) return { error: bytes };
|
|
7026
|
-
json[field] = toCanonicalBase64(bytes);
|
|
7027
|
-
return { json };
|
|
7028
|
-
}
|
|
7029
|
-
|
|
7030
|
-
// src/tools.ts
|
|
7031
|
-
function payerTrace(deps) {
|
|
7032
|
-
const hooks = deps.trace;
|
|
7033
|
-
const buyerAccountId = deps.auth?.buyerAccountId;
|
|
7034
|
-
if (!hooks || !buyerAccountId) return null;
|
|
7035
|
-
return {
|
|
7036
|
-
sink: hooks.sink,
|
|
7037
|
-
traceId: hooks.context.traceId,
|
|
7038
|
-
requestId: hooks.context.requestId,
|
|
7039
|
-
buyerAccountId,
|
|
7040
|
-
connectionId: hooks.runtime.connectionId,
|
|
7041
|
-
...hooks.runtime.secret ? { secret: hooks.runtime.secret } : {},
|
|
7042
|
-
...hooks.runtime.now ? { now: hooks.runtime.now } : {}
|
|
7043
|
-
};
|
|
7044
|
-
}
|
|
7045
|
-
var AUTH_REQUIRED = "No SuiPay credentials - call suipay_login first. It returns a console URL; approve the spend-account grant in the browser. The next tool call picks up ~/.suipay/credentials.json automatically.";
|
|
7046
|
-
var LOGIN_BG_TIMEOUT_MS = 5 * 6e4;
|
|
7047
5729
|
function preparedResult(result) {
|
|
7048
5730
|
return text({
|
|
7049
5731
|
status: "prepared",
|
|
@@ -7063,7 +5745,7 @@ function normalizePayIntent(args) {
|
|
|
7063
5745
|
if (!/^0x[0-9a-fA-F]{1,64}$/.test(raw)) {
|
|
7064
5746
|
throw new Error("recipient must be a 0x-prefixed Sui address");
|
|
7065
5747
|
}
|
|
7066
|
-
const normalized =
|
|
5748
|
+
const normalized = normalizeSuiAddress10(raw);
|
|
7067
5749
|
if (!isValidSuiAddress(normalized) || /^0x0+$/.test(normalized)) {
|
|
7068
5750
|
throw new Error("recipient must be a non-zero Sui address");
|
|
7069
5751
|
}
|
|
@@ -7082,7 +5764,7 @@ function intentViolation(intent, prepared) {
|
|
|
7082
5764
|
if (intent.recipient) {
|
|
7083
5765
|
let preparedRecipient;
|
|
7084
5766
|
try {
|
|
7085
|
-
preparedRecipient =
|
|
5767
|
+
preparedRecipient = normalizeSuiAddress10(prepared.recipient);
|
|
7086
5768
|
} catch {
|
|
7087
5769
|
return "prepared payment does not name a valid recipient";
|
|
7088
5770
|
}
|
|
@@ -7105,27 +5787,15 @@ function intentViolation(intent, prepared) {
|
|
|
7105
5787
|
}
|
|
7106
5788
|
async function pay(args, cfg, deps = {}) {
|
|
7107
5789
|
if (!args.url) return error("url is required");
|
|
7108
|
-
const hasFileArg = args.file !== void 0 && args.file !== null && args.file !== "" || args.fileField !== void 0 && args.fileField !== null && args.fileField !== "";
|
|
7109
|
-
if (hasFileArg && deps.auth) {
|
|
7110
|
-
return closedPayError(
|
|
7111
|
-
"FILE_STDIO_ONLY",
|
|
7112
|
-
"file is stdio only; hosted /mcp does not read the buyer disk"
|
|
7113
|
-
);
|
|
7114
|
-
}
|
|
7115
5790
|
let intent;
|
|
7116
5791
|
try {
|
|
7117
5792
|
intent = normalizePayIntent(args);
|
|
7118
5793
|
} catch {
|
|
7119
5794
|
return closedPayError("INVALID_INTENT", "payment intent is invalid");
|
|
7120
5795
|
}
|
|
7121
|
-
const attached = jsonWithLocalFile(args);
|
|
7122
|
-
if ("error" in attached) {
|
|
7123
|
-
return closedPayError(attached.error.code, attached.error.detail);
|
|
7124
|
-
}
|
|
7125
|
-
const paidArgs = { ...args, json: attached.json };
|
|
7126
5796
|
let request;
|
|
7127
5797
|
try {
|
|
7128
|
-
request = normalizeMcpPaidHttpRequest(
|
|
5798
|
+
request = normalizeMcpPaidHttpRequest(args);
|
|
7129
5799
|
} catch (err) {
|
|
7130
5800
|
const raw = err instanceof Error ? err.message : "";
|
|
7131
5801
|
const detail = /method/i.test(raw) ? "method must be GET or POST" : /json/i.test(raw) ? "POST requires json" : "request could not be normalized";
|
|
@@ -7200,10 +5870,10 @@ async function pay(args, cfg, deps = {}) {
|
|
|
7200
5870
|
...deps.fetchImpl && !deps.agentHeld.payerDeps.fetchImpl ? { fetchImpl: deps.fetchImpl } : {}
|
|
7201
5871
|
}
|
|
7202
5872
|
});
|
|
7203
|
-
return
|
|
5873
|
+
return settled.status === "ok" ? paidResult(settled) : error(settled);
|
|
7204
5874
|
}
|
|
7205
5875
|
if (result.status === "prepared") return preparedResult(result);
|
|
7206
|
-
return
|
|
5876
|
+
return result.status === "ok" ? paidResult(result) : error(result);
|
|
7207
5877
|
}
|
|
7208
5878
|
if (deps.auth) {
|
|
7209
5879
|
return error(
|
|
@@ -7218,259 +5888,122 @@ async function pay(args, cfg, deps = {}) {
|
|
|
7218
5888
|
}
|
|
7219
5889
|
const local = loadLocalSigner();
|
|
7220
5890
|
if (!local) return error(AUTH_REQUIRED);
|
|
7221
|
-
return payStdioWithLocalKey({ request, intent, cfg, deps });
|
|
5891
|
+
return payStdioWithLocalKey({ request, intent, cfg, deps, local });
|
|
7222
5892
|
}
|
|
7223
|
-
function
|
|
7224
|
-
return
|
|
5893
|
+
function snapshotFromBinding(binding, delegateAddress) {
|
|
5894
|
+
return {
|
|
5895
|
+
grantId: binding.grantObjectId,
|
|
5896
|
+
status: "active",
|
|
5897
|
+
grantObjectId: binding.grantObjectId,
|
|
5898
|
+
poolObjectId: binding.poolObjectId,
|
|
5899
|
+
coinType: binding.coinType,
|
|
5900
|
+
delegateAddress,
|
|
5901
|
+
policies: binding.policies,
|
|
5902
|
+
accessMode: binding.accessMode,
|
|
5903
|
+
...binding.maxPerPayment != null ? { maxPerPayment: binding.maxPerPayment } : {},
|
|
5904
|
+
...binding.sessionCap != null ? { sessionCap: binding.sessionCap } : {}
|
|
5905
|
+
};
|
|
7225
5906
|
}
|
|
7226
|
-
function
|
|
7227
|
-
|
|
7228
|
-
|
|
7229
|
-
|
|
7230
|
-
|
|
7231
|
-
|
|
7232
|
-
|
|
7233
|
-
|
|
7234
|
-
|
|
7235
|
-
|
|
7236
|
-
const live = [];
|
|
7237
|
-
for (const grant of grants) {
|
|
7238
|
-
if (!grant || typeof grant !== "object") continue;
|
|
7239
|
-
const g = grant;
|
|
7240
|
-
const id = g.grantObjectId;
|
|
7241
|
-
if (typeof id !== "string" || !id.trim()) continue;
|
|
7242
|
-
named += 1;
|
|
7243
|
-
if (isExpiredBootstrapGrant(g.expiresAtMs, nowMs)) continue;
|
|
7244
|
-
live.push(id.trim());
|
|
7245
|
-
}
|
|
7246
|
-
if (live.length === 1) return { kind: "live", grantObjectId: live[0] };
|
|
7247
|
-
if (live.length > 1) return { kind: "ambiguous" };
|
|
7248
|
-
if (named > 0) return { kind: "absent" };
|
|
7249
|
-
if (grants.length > 0) return { kind: "unknown", detail: "delegate grants list was not usable" };
|
|
7250
|
-
return { kind: "absent" };
|
|
7251
|
-
}
|
|
7252
|
-
async function checkLiveDelegateGrant(args) {
|
|
7253
|
-
const deployment = await fetchGatewayDeployment({
|
|
7254
|
-
gatewayUrl: args.gatewayUrl,
|
|
7255
|
-
...args.fetchImpl ? { fetchImpl: args.fetchImpl } : {}
|
|
7256
|
-
});
|
|
7257
|
-
if (!deployment.ok) {
|
|
7258
|
-
return {
|
|
7259
|
-
kind: "unknown",
|
|
7260
|
-
detail: "gateway deployment identity could not be verified"
|
|
7261
|
-
};
|
|
7262
|
-
}
|
|
7263
|
-
const listed = await bootstrapPayerGrants({
|
|
7264
|
-
gatewayUrl: args.gatewayUrl,
|
|
7265
|
-
context: {
|
|
7266
|
-
network: deployment.value.network,
|
|
7267
|
-
packageId: deployment.value.packageId,
|
|
7268
|
-
delegateAddress: args.delegateAddress
|
|
5907
|
+
async function payStdioWithLocalKey(args) {
|
|
5908
|
+
const { request, intent, cfg, deps, local } = args;
|
|
5909
|
+
const resolved = await resolvePayerSession({
|
|
5910
|
+
gatewayUrl: cfg.gatewayUrl,
|
|
5911
|
+
delegateAddress: local.address,
|
|
5912
|
+
signPersonalMessage: local.signPersonalMessage,
|
|
5913
|
+
signer: {
|
|
5914
|
+
address: local.address,
|
|
5915
|
+
signTransaction: local.signTransaction,
|
|
5916
|
+
signPersonalMessage: local.signPersonalMessage
|
|
7269
5917
|
},
|
|
7270
|
-
|
|
7271
|
-
...args.fetchImpl ? { fetchImpl: args.fetchImpl } : {}
|
|
5918
|
+
...deps.fetchImpl ? { fetchImpl: deps.fetchImpl } : {}
|
|
7272
5919
|
});
|
|
7273
|
-
if (!
|
|
7274
|
-
return
|
|
7275
|
-
kind: "unknown",
|
|
7276
|
-
detail: `delegate grants read ${listed.error} (HTTP ${listed.status})`
|
|
7277
|
-
};
|
|
7278
|
-
}
|
|
7279
|
-
if (!Array.isArray(listed.data.grants) || listed.data.grants.length === 0) {
|
|
7280
|
-
return { kind: "absent" };
|
|
7281
|
-
}
|
|
7282
|
-
return grantObjectIdFromBootstrap(listed.data.grants, Date.now());
|
|
7283
|
-
}
|
|
7284
|
-
async function payStdioWithLocalKey(args) {
|
|
7285
|
-
const { request, intent, cfg, deps } = args;
|
|
7286
|
-
const built = await stdioPayerAndReader(cfg, deps);
|
|
7287
|
-
if (!built.ok) return built.result;
|
|
7288
|
-
const payer = built.payer;
|
|
7289
|
-
const grantCeiling = payer.grant.maxPerPayment;
|
|
7290
|
-
const maxAmount = intent.maxAmount !== void 0 ? intent.maxAmount.toString() : grantCeiling != null && grantCeiling !== "" ? grantCeiling : null;
|
|
7291
|
-
if (maxAmount === null) {
|
|
7292
|
-
return error({
|
|
7293
|
-
status: "failed",
|
|
7294
|
-
paid: false,
|
|
7295
|
-
code: "POLICY_REJECTED",
|
|
7296
|
-
detail: "stdio pay requires maxAmount or a grant maxPerPayment; refusing to default the ceiling to the offer"
|
|
7297
|
-
});
|
|
5920
|
+
if (!resolved.ok) {
|
|
5921
|
+
return closedPayError(resolved.error.code, resolved.error.detail);
|
|
7298
5922
|
}
|
|
7299
|
-
const
|
|
7300
|
-
|
|
7301
|
-
|
|
7302
|
-
|
|
7303
|
-
|
|
7304
|
-
|
|
7305
|
-
|
|
7306
|
-
|
|
5923
|
+
const snapshot = snapshotFromBinding(resolved.value.binding, local.address);
|
|
5924
|
+
const packageId = resolved.value.session.packageId;
|
|
5925
|
+
const result = await prepareSpendAccountPayment({
|
|
5926
|
+
url: request.url,
|
|
5927
|
+
request,
|
|
5928
|
+
session: {
|
|
5929
|
+
snapshot,
|
|
5930
|
+
packageId,
|
|
5931
|
+
...resolved.value.session.assertLive ? { assertLive: resolved.value.session.assertLive } : {}
|
|
5932
|
+
},
|
|
5933
|
+
cfg,
|
|
5934
|
+
deps: {
|
|
5935
|
+
...deps.fetchImpl ? { fetchImpl: deps.fetchImpl } : {},
|
|
5936
|
+
...deps.buildPayKind ? { buildPayKind: deps.buildPayKind } : {}
|
|
7307
5937
|
}
|
|
7308
5938
|
});
|
|
7309
|
-
|
|
7310
|
-
|
|
7311
|
-
|
|
7312
|
-
|
|
7313
|
-
|
|
7314
|
-
|
|
7315
|
-
|
|
7316
|
-
|
|
7317
|
-
|
|
7318
|
-
if (local && shouldSelect) {
|
|
7319
|
-
const check = await checkLiveDelegateGrant({
|
|
7320
|
-
gatewayUrl,
|
|
7321
|
-
delegateAddress: local.address,
|
|
7322
|
-
signPersonalMessage: local.signPersonalMessage,
|
|
7323
|
-
...deps.fetchImpl ? { fetchImpl: deps.fetchImpl } : {}
|
|
7324
|
-
});
|
|
7325
|
-
if (check.kind === "ambiguous") {
|
|
7326
|
-
return {
|
|
7327
|
-
ok: false,
|
|
7328
|
-
result: closedError(
|
|
7329
|
-
"AMBIGUOUS_GRANT",
|
|
7330
|
-
"multiple live grants name this key; pass a selector (grantObjectId / asset / coinType)"
|
|
7331
|
-
)
|
|
7332
|
-
};
|
|
5939
|
+
if (result.status === "prepared") {
|
|
5940
|
+
const violation = intentViolation(intent, result.signingRequest);
|
|
5941
|
+
if (violation) {
|
|
5942
|
+
return error({
|
|
5943
|
+
status: "failed",
|
|
5944
|
+
paid: false,
|
|
5945
|
+
code: "POLICY_REJECTED",
|
|
5946
|
+
detail: violation
|
|
5947
|
+
});
|
|
7333
5948
|
}
|
|
7334
|
-
|
|
7335
|
-
|
|
7336
|
-
|
|
7337
|
-
|
|
7338
|
-
|
|
7339
|
-
|
|
7340
|
-
|
|
7341
|
-
|
|
7342
|
-
|
|
7343
|
-
|
|
7344
|
-
|
|
7345
|
-
|
|
7346
|
-
|
|
5949
|
+
const agentHeld = deps.agentHeld ?? settlementFromLocalSigner(cfg, local, packageId);
|
|
5950
|
+
const grantCeiling = resolved.value.binding.maxPerPayment;
|
|
5951
|
+
const maxAmount = intent.maxAmount !== void 0 ? intent.maxAmount.toString() : grantCeiling != null && grantCeiling !== "" ? grantCeiling : null;
|
|
5952
|
+
if (maxAmount === null) {
|
|
5953
|
+
return error({
|
|
5954
|
+
status: "failed",
|
|
5955
|
+
paid: false,
|
|
5956
|
+
code: "POLICY_REJECTED",
|
|
5957
|
+
detail: "stdio pay requires maxAmount or a grant maxPerPayment; refusing to default the ceiling to the offer"
|
|
5958
|
+
});
|
|
5959
|
+
}
|
|
5960
|
+
const spendIntent = {
|
|
5961
|
+
maxAmount,
|
|
5962
|
+
asset: resolved.value.binding.coinType,
|
|
5963
|
+
...intent.recipient ? { recipient: intent.recipient } : {}
|
|
7347
5964
|
};
|
|
5965
|
+
const settled = await settlePreparedMcpPayment({
|
|
5966
|
+
signingRequest: result.signingRequest,
|
|
5967
|
+
request,
|
|
5968
|
+
expected: {
|
|
5969
|
+
amount: result.signingRequest.amount,
|
|
5970
|
+
recipient: intent.recipient ?? result.signingRequest.recipient,
|
|
5971
|
+
delegateAddress: local.address
|
|
5972
|
+
},
|
|
5973
|
+
signer: local,
|
|
5974
|
+
getObjectJson: agentHeld.getObjectJson,
|
|
5975
|
+
session: {
|
|
5976
|
+
...resolved.value.session,
|
|
5977
|
+
signer: local
|
|
5978
|
+
},
|
|
5979
|
+
cfg: { gatewayUrl: cfg.gatewayUrl },
|
|
5980
|
+
deps: {
|
|
5981
|
+
...agentHeld.payerDeps,
|
|
5982
|
+
...deps.fetchImpl && !agentHeld.payerDeps.fetchImpl ? { fetchImpl: deps.fetchImpl } : {}
|
|
5983
|
+
},
|
|
5984
|
+
intent: spendIntent
|
|
5985
|
+
});
|
|
5986
|
+
return settled.status === "ok" ? paidResult(settled) : error(settled);
|
|
7348
5987
|
}
|
|
7349
|
-
return
|
|
7350
|
-
}
|
|
7351
|
-
function stdioReaderForPayer(cfg, deps, payer) {
|
|
7352
|
-
return (deps.createReader ?? createReader)({
|
|
7353
|
-
gatewayUrl: stdioGatewayUrl(cfg),
|
|
7354
|
-
context: {
|
|
7355
|
-
network: payer.deployment.network,
|
|
7356
|
-
packageId: payer.deployment.packageId,
|
|
7357
|
-
grantObjectId: payer.grant.grantObjectId,
|
|
7358
|
-
delegateAddress: payer.address
|
|
7359
|
-
},
|
|
7360
|
-
signPersonalMessage: async (message) => {
|
|
7361
|
-
const { signature } = await loadLocalKeypair().signPersonalMessage(message);
|
|
7362
|
-
return signature;
|
|
7363
|
-
},
|
|
7364
|
-
...deps.fetchImpl ? { fetchImpl: deps.fetchImpl } : {}
|
|
7365
|
-
});
|
|
7366
|
-
}
|
|
7367
|
-
function budgetFromBalance(balance, grant) {
|
|
7368
|
-
if (!balance.ok || !balance.data) return void 0;
|
|
7369
|
-
return {
|
|
7370
|
-
sessionRemaining: balance.data.sessionRemaining ?? null,
|
|
7371
|
-
maxPerPayment: balance.data.maxPerPayment ?? grant.maxPerPayment ?? null,
|
|
7372
|
-
coinType: balance.data.coinType ?? grant.coinType ?? null
|
|
7373
|
-
};
|
|
7374
|
-
}
|
|
7375
|
-
async function fetchPublicCatalog(gatewayUrl, fetchImpl) {
|
|
7376
|
-
const catalogUrl = new URL("/v1/services", gatewayUrl);
|
|
7377
|
-
const response = await (fetchImpl ?? fetch)(catalogUrl);
|
|
7378
|
-
if (!response.ok) return { ok: false, result: error(`gateway unavailable: HTTP ${response.status}`) };
|
|
7379
|
-
return { ok: true, catalog: await response.json() };
|
|
7380
|
-
}
|
|
7381
|
-
async function publicCatalog(gatewayUrl, query, fetchImpl) {
|
|
7382
|
-
const loaded = await fetchPublicCatalog(gatewayUrl, fetchImpl);
|
|
7383
|
-
if (!loaded.ok) return loaded.result;
|
|
7384
|
-
return text(
|
|
7385
|
-
joinDiscoverCatalog({
|
|
7386
|
-
catalog: loaded.catalog,
|
|
7387
|
-
gatewayUrl,
|
|
7388
|
-
query
|
|
7389
|
-
})
|
|
7390
|
-
);
|
|
5988
|
+
return result.status === "ok" ? paidResult(result) : error(result);
|
|
7391
5989
|
}
|
|
7392
5990
|
async function discover(args, cfg, deps = {}) {
|
|
7393
|
-
|
|
7394
|
-
|
|
5991
|
+
const url = new URL("/v1/services", cfg.gatewayUrl);
|
|
5992
|
+
if (args.query) url.searchParams.set("q", args.query);
|
|
5993
|
+
const response = await (deps.fetchImpl ?? fetch)(url);
|
|
5994
|
+
if (!response.ok) return error(`gateway unavailable: HTTP ${response.status}`);
|
|
5995
|
+
return text(await response.json());
|
|
5996
|
+
}
|
|
5997
|
+
async function accessContext(deps = {}) {
|
|
5998
|
+
if (!deps.auth) return error(AUTH_REQUIRED);
|
|
5999
|
+
if (!deps.auth.access) {
|
|
6000
|
+
return error(
|
|
6001
|
+
"SuiPay access context unavailable. Reconnect this MCP session or inspect the connection in buyer console."
|
|
6002
|
+
);
|
|
7395
6003
|
}
|
|
7396
|
-
|
|
7397
|
-
if (local) {
|
|
7398
|
-
const check = await checkLiveDelegateGrant({
|
|
7399
|
-
gatewayUrl: stdioGatewayUrl(cfg),
|
|
7400
|
-
delegateAddress: local.address,
|
|
7401
|
-
signPersonalMessage: local.signPersonalMessage,
|
|
7402
|
-
...deps.fetchImpl ? { fetchImpl: deps.fetchImpl } : {}
|
|
7403
|
-
});
|
|
7404
|
-
if (check.kind !== "live") {
|
|
7405
|
-
return publicCatalog(stdioGatewayUrl(cfg), args.query, deps.fetchImpl);
|
|
7406
|
-
}
|
|
7407
|
-
}
|
|
7408
|
-
const resolved = await stdioPayerAndReader(cfg, deps);
|
|
7409
|
-
if (!resolved.ok) return resolved.result;
|
|
7410
|
-
const gatewayUrl = stdioGatewayUrl(cfg);
|
|
7411
|
-
const loaded = await fetchPublicCatalog(gatewayUrl, deps.fetchImpl);
|
|
7412
|
-
if (!loaded.ok) return loaded.result;
|
|
7413
|
-
const scoped = resolved.payer.grant.accessMode === "scoped";
|
|
7414
|
-
const reader = stdioReaderForPayer(cfg, deps, resolved.payer);
|
|
7415
|
-
const [policies, balance] = await Promise.all([
|
|
7416
|
-
scoped ? reader.policies() : Promise.resolve({ ok: true, data: { policies: void 0 } }),
|
|
7417
|
-
reader.balance()
|
|
7418
|
-
]);
|
|
7419
|
-
if (scoped && !policies.ok) {
|
|
7420
|
-
return closedError("POLICIES_UNAVAILABLE", "grant policies could not be loaded");
|
|
7421
|
-
}
|
|
7422
|
-
return text(
|
|
7423
|
-
joinDiscoverCatalog({
|
|
7424
|
-
catalog: loaded.catalog,
|
|
7425
|
-
gatewayUrl,
|
|
7426
|
-
query: args.query,
|
|
7427
|
-
...scoped && policies.ok ? { policies: policies.data.policies } : {},
|
|
7428
|
-
budget: budgetFromBalance(balance, resolved.payer.grant)
|
|
7429
|
-
})
|
|
7430
|
-
);
|
|
7431
|
-
}
|
|
7432
|
-
async function accessContext(deps = {}, cfg) {
|
|
7433
|
-
if (deps.auth) {
|
|
7434
|
-
if (!deps.auth.access) {
|
|
7435
|
-
return error(
|
|
7436
|
-
"SuiPay access context unavailable. Reconnect this MCP session or inspect the connection in buyer console."
|
|
7437
|
-
);
|
|
7438
|
-
}
|
|
7439
|
-
return text(deps.auth.access);
|
|
7440
|
-
}
|
|
7441
|
-
if (!cfg) return error(AUTH_REQUIRED);
|
|
7442
|
-
const resolved = await stdioPayerAndReader(cfg, deps);
|
|
7443
|
-
if (!resolved.ok) return resolved.result;
|
|
7444
|
-
const gatewayUrl = stdioGatewayUrl(cfg);
|
|
7445
|
-
const reader = stdioReaderForPayer(cfg, deps, resolved.payer);
|
|
7446
|
-
const scoped = resolved.payer.grant.accessMode === "scoped";
|
|
7447
|
-
const [policies, balance, loaded] = await Promise.all([
|
|
7448
|
-
reader.policies(),
|
|
7449
|
-
reader.balance(),
|
|
7450
|
-
fetchPublicCatalog(gatewayUrl, deps.fetchImpl)
|
|
7451
|
-
]);
|
|
7452
|
-
const budget = budgetFromBalance(balance, resolved.payer.grant);
|
|
7453
|
-
const joined = loaded.ok && (!scoped || policies.ok) ? joinDiscoverCatalog({
|
|
7454
|
-
catalog: loaded.catalog,
|
|
7455
|
-
gatewayUrl,
|
|
7456
|
-
...scoped && policies.ok ? { policies: policies.data.policies } : {},
|
|
7457
|
-
budget
|
|
7458
|
-
}) : null;
|
|
7459
|
-
return text({
|
|
7460
|
-
accessMode: resolved.payer.grant.accessMode,
|
|
7461
|
-
grant: {
|
|
7462
|
-
id: resolved.payer.grant.grantObjectId,
|
|
7463
|
-
poolObjectId: resolved.payer.grant.poolObjectId,
|
|
7464
|
-
coinType: resolved.payer.grant.coinType,
|
|
7465
|
-
maxPerPayment: resolved.payer.grant.maxPerPayment ?? null,
|
|
7466
|
-
sessionCap: resolved.payer.grant.sessionCap ?? null
|
|
7467
|
-
},
|
|
7468
|
-
policies: policies.ok ? policies.data : { error: policies.error },
|
|
7469
|
-
balance: balance.ok ? balance.data : { error: balance.error },
|
|
7470
|
-
...joined ? { services: joined.services, count: joined.count } : {}
|
|
7471
|
-
});
|
|
6004
|
+
return text(deps.auth.access);
|
|
7472
6005
|
}
|
|
7473
|
-
async function receipts(args,
|
|
6006
|
+
async function receipts(args, _cfg, deps = {}) {
|
|
7474
6007
|
if (deps.auth) {
|
|
7475
6008
|
if (!deps.auth.listReceipts) {
|
|
7476
6009
|
return error(
|
|
@@ -7484,144 +6017,28 @@ async function receipts(args, cfg, deps = {}) {
|
|
|
7484
6017
|
return closedError("RECEIPTS_UNAVAILABLE", "receipts could not be loaded");
|
|
7485
6018
|
}
|
|
7486
6019
|
}
|
|
7487
|
-
if (profile(deps))
|
|
7488
|
-
|
|
7489
|
-
|
|
7490
|
-
|
|
7491
|
-
}
|
|
7492
|
-
const resolved = await stdioPayerAndReader(cfg, deps);
|
|
7493
|
-
if (!resolved.ok) return resolved.result;
|
|
7494
|
-
const reader = (deps.createReader ?? createReader)({
|
|
7495
|
-
gatewayUrl: stdioGatewayUrl(cfg),
|
|
7496
|
-
context: {
|
|
7497
|
-
network: resolved.payer.deployment.network,
|
|
7498
|
-
packageId: resolved.payer.deployment.packageId,
|
|
7499
|
-
grantObjectId: resolved.payer.grant.grantObjectId,
|
|
7500
|
-
delegateAddress: resolved.payer.address
|
|
7501
|
-
},
|
|
7502
|
-
signPersonalMessage: async (message) => {
|
|
7503
|
-
const { signature } = await loadLocalKeypair().signPersonalMessage(message);
|
|
7504
|
-
return signature;
|
|
7505
|
-
},
|
|
7506
|
-
...deps.fetchImpl ? { fetchImpl: deps.fetchImpl } : {}
|
|
7507
|
-
});
|
|
7508
|
-
const listed = await reader.receipts();
|
|
7509
|
-
if (!listed.ok) {
|
|
7510
|
-
return closedError("RECEIPTS_UNAVAILABLE", "receipts could not be loaded");
|
|
7511
|
-
}
|
|
7512
|
-
const receiptsOut = listed.data.receipts;
|
|
7513
|
-
const filtered = args.challengeId && Array.isArray(receiptsOut) ? receiptsOut.filter((row) => {
|
|
7514
|
-
if (!row || typeof row !== "object") return false;
|
|
7515
|
-
const rec = row;
|
|
7516
|
-
return rec.challengeId === args.challengeId || rec.receipt?.challengeId === args.challengeId;
|
|
7517
|
-
}) : receiptsOut;
|
|
7518
|
-
return text({ count: Array.isArray(filtered) ? filtered.length : 0, receipts: filtered });
|
|
7519
|
-
}
|
|
7520
|
-
function parseLoginTarget(value) {
|
|
7521
|
-
if (value === void 0 || value === null || value === "") return void 0;
|
|
7522
|
-
if (typeof value !== "string") return { error: 'target must be "dev" or "local"' };
|
|
7523
|
-
const target = value.trim().toLowerCase();
|
|
7524
|
-
if (target === "dev" || target === "local") return target;
|
|
7525
|
-
return { error: 'target must be "dev" or "local"' };
|
|
6020
|
+
if (!profile(deps)) return error(AUTH_REQUIRED);
|
|
6021
|
+
return error(
|
|
6022
|
+
"receipts is available on remote OAuth (/mcp) sessions only; the local allowance profile has no settlement feed access"
|
|
6023
|
+
);
|
|
7526
6024
|
}
|
|
7527
6025
|
async function suipayLogin(args, cfg, deps = {}) {
|
|
7528
|
-
const parsedTarget = parseLoginTarget(args.target);
|
|
7529
|
-
if (parsedTarget && typeof parsedTarget === "object" && "error" in parsedTarget) {
|
|
7530
|
-
return error({
|
|
7531
|
-
status: "failed",
|
|
7532
|
-
code: "INVALID_TARGET",
|
|
7533
|
-
detail: parsedTarget.error
|
|
7534
|
-
});
|
|
7535
|
-
}
|
|
7536
|
-
const target = parsedTarget;
|
|
7537
6026
|
const label = args.label?.trim() || cfg.label.trim();
|
|
7538
6027
|
const identity = await ensureLocalSigningKey(label ? { label } : {});
|
|
7539
|
-
const
|
|
7540
|
-
|
|
7541
|
-
const
|
|
7542
|
-
|
|
7543
|
-
|
|
7544
|
-
|
|
7545
|
-
|
|
7546
|
-
const live = await checkLiveDelegateGrant({
|
|
7547
|
-
gatewayUrl: endpoints.gatewayUrl,
|
|
7548
|
-
delegateAddress: local.address,
|
|
7549
|
-
signPersonalMessage: local.signPersonalMessage,
|
|
7550
|
-
...deps.fetchImpl ? { fetchImpl: deps.fetchImpl } : {}
|
|
7551
|
-
});
|
|
7552
|
-
if (live.kind === "ambiguous") {
|
|
7553
|
-
return error({
|
|
7554
|
-
status: "failed",
|
|
7555
|
-
code: "AMBIGUOUS_GRANT",
|
|
7556
|
-
detail: "multiple live grants name this key; revoke extras or pass a selector before paying"
|
|
7557
|
-
});
|
|
7558
|
-
}
|
|
7559
|
-
if (live.kind === "live") {
|
|
7560
|
-
const storedUrl = stored?.gatewayUrl;
|
|
7561
|
-
if (!storedUrl || storedUrl !== endpoints.gatewayUrl && (sameMcpGateway(storedUrl, endpoints.gatewayUrl) || target !== void 0)) {
|
|
7562
|
-
persistGatewayUrl(endpoints.gatewayUrl);
|
|
7563
|
-
}
|
|
7564
|
-
return text({
|
|
7565
|
-
status: "ok",
|
|
7566
|
-
reused: true,
|
|
7567
|
-
target: target ?? cfg.target,
|
|
7568
|
-
delegateAddress: identity.delegateAddress,
|
|
7569
|
-
gatewayUrl: loadLocalSigningKeyRecord()?.gatewayUrl ?? endpoints.gatewayUrl,
|
|
7570
|
-
grantObjectId: live.grantObjectId,
|
|
7571
|
-
message: "Existing spend-account grant is live for this key. No browser round-trip."
|
|
7572
|
-
});
|
|
7573
|
-
}
|
|
7574
|
-
if (live.kind === "unknown") {
|
|
7575
|
-
return error({
|
|
7576
|
-
status: "failed",
|
|
7577
|
-
code: "GRANT_STATUS_UNKNOWN",
|
|
7578
|
-
detail: "Could not verify the existing grant. Credentials were not modified. Retry later; do not start a new consent (that would mint a second grant)."
|
|
7579
|
-
});
|
|
7580
|
-
}
|
|
6028
|
+
const connect = new URL("/connect/agent", cfg.consoleUrl);
|
|
6029
|
+
connect.searchParams.set("delegateAddress", identity.delegateAddress);
|
|
6030
|
+
const url = connect.toString();
|
|
6031
|
+
deps.onUrl?.(url);
|
|
6032
|
+
try {
|
|
6033
|
+
await open(url);
|
|
6034
|
+
} catch {
|
|
7581
6035
|
}
|
|
7582
|
-
|
|
7583
|
-
|
|
7584
|
-
|
|
7585
|
-
|
|
7586
|
-
|
|
7587
|
-
timeoutMs: LOGIN_BG_TIMEOUT_MS,
|
|
7588
|
-
openBrowser: false,
|
|
7589
|
-
onUrl: deps.onUrl
|
|
6036
|
+
return text({
|
|
6037
|
+
status: "ok",
|
|
6038
|
+
delegateAddress: identity.delegateAddress,
|
|
6039
|
+
connectUrl: url,
|
|
6040
|
+
message: "Open the console to bind a spend-account grant to this delegate address. The private key never leaves this process."
|
|
7590
6041
|
});
|
|
7591
|
-
const url = started.connectUrl;
|
|
7592
|
-
return text(
|
|
7593
|
-
[
|
|
7594
|
-
`## ACTION REQUIRED: User must click this URL to sign in`,
|
|
7595
|
-
``,
|
|
7596
|
-
`**Target:** ${target ?? cfg.target ?? "custom"} \xB7 ${endpoints.gatewayUrl}`,
|
|
7597
|
-
``,
|
|
7598
|
-
`**URL:** ${url}`,
|
|
7599
|
-
``,
|
|
7600
|
-
"```",
|
|
7601
|
-
url,
|
|
7602
|
-
"```",
|
|
7603
|
-
``,
|
|
7604
|
-
`[Click here to open SuiPay sign-in](${url})`,
|
|
7605
|
-
``,
|
|
7606
|
-
`**IMPORTANT for the assistant**: do NOT summarize or omit the URL above.`,
|
|
7607
|
-
`The user CANNOT proceed without seeing the exact URL. Surface it verbatim.`,
|
|
7608
|
-
``,
|
|
7609
|
-
`1. Open the URL in any browser`,
|
|
7610
|
-
`2. Connect the owner wallet and approve the spend-account grant (Access keys / policies)`,
|
|
7611
|
-
`3. Once Connected appears, retry the original request \u2014 credentials at \`~/.suipay/credentials.json\` are picked up on the next tool call`,
|
|
7612
|
-
``,
|
|
7613
|
-
`_The login link stays valid for 5 minutes. If it expires, call \`suipay_login\` again._`,
|
|
7614
|
-
``,
|
|
7615
|
-
JSON.stringify({
|
|
7616
|
-
status: "ok",
|
|
7617
|
-
target: target ?? cfg.target,
|
|
7618
|
-
delegateAddress: identity.delegateAddress,
|
|
7619
|
-
connectUrl: url,
|
|
7620
|
-
consoleUrl: endpoints.consoleUrl,
|
|
7621
|
-
gatewayUrl: endpoints.gatewayUrl
|
|
7622
|
-
})
|
|
7623
|
-
].join("\n")
|
|
7624
|
-
);
|
|
7625
6042
|
}
|
|
7626
6043
|
async function logout(args, _cfg, deps = {}) {
|
|
7627
6044
|
if (args.force) {
|
|
@@ -7661,14 +6078,6 @@ var TOOLS = [
|
|
|
7661
6078
|
type: "object",
|
|
7662
6079
|
description: "JSON request body for a POST. Sent as application/json and bound to the payment."
|
|
7663
6080
|
},
|
|
7664
|
-
file: {
|
|
7665
|
-
type: "string",
|
|
7666
|
-
description: "Absolute local path whose bytes fill json[fileField] as canonical base64. Stdio only; requires fileField. GET must omit this."
|
|
7667
|
-
},
|
|
7668
|
-
fileField: {
|
|
7669
|
-
type: "string",
|
|
7670
|
-
description: "JSON key to set from file (e.g. contentBase64). Required with file. Must not already be present on json."
|
|
7671
|
-
},
|
|
7672
6081
|
recipient: {
|
|
7673
6082
|
type: "string",
|
|
7674
6083
|
description: "Sui address you expect to be paid. The payment is refused, unsigned, if the prepared transaction pays anyone else."
|
|
@@ -7683,7 +6092,7 @@ var TOOLS = [
|
|
|
7683
6092
|
},
|
|
7684
6093
|
{
|
|
7685
6094
|
name: "discover",
|
|
7686
|
-
description: "Search SuiPay gateway resources.
|
|
6095
|
+
description: "Search SuiPay gateway resources.",
|
|
7687
6096
|
inputSchema: {
|
|
7688
6097
|
type: "object",
|
|
7689
6098
|
properties: {
|
|
@@ -7709,16 +6118,11 @@ var TOOLS = [
|
|
|
7709
6118
|
},
|
|
7710
6119
|
{
|
|
7711
6120
|
name: "suipay_login",
|
|
7712
|
-
description:
|
|
6121
|
+
description: "Mint or reuse the local delegate key (version-2, mode 0600) and open the console with the public address only so the owner can bind a spend-account grant. Stdio only; hosted /mcp never holds a spendable key.",
|
|
7713
6122
|
inputSchema: {
|
|
7714
6123
|
type: "object",
|
|
7715
6124
|
properties: {
|
|
7716
6125
|
label: { type: "string", description: "Label shown in SuiPay console." },
|
|
7717
|
-
target: {
|
|
7718
|
-
type: "string",
|
|
7719
|
-
enum: ["dev", "local"],
|
|
7720
|
-
description: 'Where to bind the grant. Default is Railway suipay-dev. Pass "local" for localhost:3000 / 127.0.0.1:4340.'
|
|
7721
|
-
},
|
|
7722
6126
|
gatewayResourceUrl: {
|
|
7723
6127
|
type: "string",
|
|
7724
6128
|
description: "Gateway resource URL whose 402 defines recipient, asset, and package."
|
|
@@ -7774,7 +6178,7 @@ function toolError(value) {
|
|
|
7774
6178
|
isError: true
|
|
7775
6179
|
};
|
|
7776
6180
|
}
|
|
7777
|
-
function
|
|
6181
|
+
function createServer(cfg, auth, trace, options = {}) {
|
|
7778
6182
|
const resolveAgentHeld = options.resolveAgentHeld ?? resolveAgentHeldSettlement;
|
|
7779
6183
|
const server = new Server(
|
|
7780
6184
|
{ name: "suipay", version: "1.0.0" },
|
|
@@ -7799,7 +6203,7 @@ function createServer2(cfg, auth, trace, options = {}) {
|
|
|
7799
6203
|
}
|
|
7800
6204
|
switch (name) {
|
|
7801
6205
|
case "access_context":
|
|
7802
|
-
return await accessContext({ auth }
|
|
6206
|
+
return await accessContext({ auth });
|
|
7803
6207
|
case "pay": {
|
|
7804
6208
|
const agentHeld = auth?.spendAccountPay ? resolveAgentHeld(cfg, auth.spendAccountPay) : null;
|
|
7805
6209
|
return await pay(
|
|
@@ -7807,8 +6211,6 @@ function createServer2(cfg, auth, trace, options = {}) {
|
|
|
7807
6211
|
url: String(args.url ?? ""),
|
|
7808
6212
|
...args.method !== void 0 ? { method: args.method } : {},
|
|
7809
6213
|
...args.json !== void 0 ? { json: args.json } : {},
|
|
7810
|
-
...args.file !== void 0 ? { file: args.file } : {},
|
|
7811
|
-
...args.fileField !== void 0 ? { fileField: args.fileField } : {},
|
|
7812
6214
|
...args.recipient !== void 0 ? { recipient: args.recipient } : {},
|
|
7813
6215
|
...args.maxAmount !== void 0 ? { maxAmount: args.maxAmount } : {}
|
|
7814
6216
|
},
|
|
@@ -7845,7 +6247,6 @@ function createServer2(cfg, auth, trace, options = {}) {
|
|
|
7845
6247
|
return await suipayLogin(
|
|
7846
6248
|
{
|
|
7847
6249
|
label: typeof args.label === "string" ? args.label : void 0,
|
|
7848
|
-
target: typeof args.target === "string" ? args.target : void 0,
|
|
7849
6250
|
gatewayResourceUrl: typeof args.gatewayResourceUrl === "string" ? args.gatewayResourceUrl : void 0
|
|
7850
6251
|
},
|
|
7851
6252
|
cfg,
|
|
@@ -8344,7 +6745,7 @@ async function handleSuipayMcpHttpRequest(req, cfg, auth, trace, options) {
|
|
|
8344
6745
|
sessionIdGenerator: void 0,
|
|
8345
6746
|
enableJsonResponse: true
|
|
8346
6747
|
});
|
|
8347
|
-
const server =
|
|
6748
|
+
const server = createServer(cfg, auth, hooks, options);
|
|
8348
6749
|
await server.connect(transport);
|
|
8349
6750
|
return transport.handleRequest(request);
|
|
8350
6751
|
}
|
|
@@ -8353,15 +6754,10 @@ async function handleSuipayMcpHttpRequest(req, cfg, auth, trace, options) {
|
|
|
8353
6754
|
|
|
8354
6755
|
export {
|
|
8355
6756
|
resolveGrantTarget,
|
|
8356
|
-
MCP_TARGET_PRESETS,
|
|
8357
|
-
parseMcpTargetFlag,
|
|
8358
|
-
resolveMcpTarget,
|
|
8359
|
-
sameMcpGateway,
|
|
8360
6757
|
loadMcpConfig,
|
|
8361
6758
|
loadPaymentProfile,
|
|
8362
6759
|
ensureLocalSigningKey,
|
|
8363
6760
|
loadLocalSigner,
|
|
8364
|
-
loadLocalSigningKeyRecord,
|
|
8365
6761
|
resetAgentHeldProcessLedger,
|
|
8366
6762
|
resolveAgentHeldSettlement,
|
|
8367
6763
|
settlePreparedMcpPayment,
|
|
@@ -8372,7 +6768,7 @@ export {
|
|
|
8372
6768
|
receipts,
|
|
8373
6769
|
PACKAGE_NAME,
|
|
8374
6770
|
TOOLS,
|
|
8375
|
-
|
|
6771
|
+
createServer,
|
|
8376
6772
|
canonicalMcpRequestId,
|
|
8377
6773
|
inspectMcpEnvelope,
|
|
8378
6774
|
traceMcpHttpRequest,
|