@cmdoss/suipay-mcp 0.2.2-dev.4 → 0.2.2-dev.5
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 +49 -8
- package/dist/bin/suipay.js +2 -2
- package/dist/chunk-P54ZYBPN.js +46 -0
- package/dist/{chunk-KDOGSB5I.js → chunk-THLDEMGS.js} +1872 -315
- package/dist/{http-C8xW9F1c.d.ts → http-B4YTVw0k.d.ts} +29 -4
- package/dist/http.d.ts +1 -1
- package/dist/http.js +1 -1
- package/dist/index.d.ts +34 -9
- package/dist/index.js +10 -2
- package/package.json +1 -1
- package/dist/chunk-XAM2YQC6.js +0 -39
|
@@ -151,6 +151,46 @@ 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
|
+
}
|
|
154
194
|
|
|
155
195
|
// ../core/dist/service-state/index.js
|
|
156
196
|
var STATE_DIMENSIONS = Object.freeze([
|
|
@@ -371,6 +411,101 @@ var SERVICE_STATE_TRANSITIONS = Object.freeze([
|
|
|
371
411
|
]);
|
|
372
412
|
|
|
373
413
|
// ../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
|
+
}
|
|
374
509
|
function targetHashBytes(hexHash) {
|
|
375
510
|
if (!/^[0-9a-f]{64}$/i.test(hexHash)) {
|
|
376
511
|
throw new Error("targetHash must be 32-byte hex.");
|
|
@@ -4332,8 +4467,10 @@ import {
|
|
|
4332
4467
|
fsyncSync,
|
|
4333
4468
|
openSync,
|
|
4334
4469
|
closeSync,
|
|
4335
|
-
writeSync
|
|
4470
|
+
writeSync,
|
|
4471
|
+
linkSync
|
|
4336
4472
|
} from "fs";
|
|
4473
|
+
import { decodeSuiPrivateKey } from "@mysten/sui/cryptography";
|
|
4337
4474
|
import { Ed25519Keypair as Ed25519Keypair2 } from "@mysten/sui/keypairs/ed25519";
|
|
4338
4475
|
import { fromBase64 as fromBase644 } from "@mysten/sui/utils";
|
|
4339
4476
|
import { bytesToHex as bytesToHex4 } from "@noble/hashes/utils.js";
|
|
@@ -4347,6 +4484,16 @@ function mcpCredentialsPath() {
|
|
|
4347
4484
|
const home = process.env.HOME ?? process.env.USERPROFILE ?? homedir();
|
|
4348
4485
|
return join(home, ".suipay", "credentials.json");
|
|
4349
4486
|
}
|
|
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
|
+
};
|
|
4350
4497
|
var GRPC_URLS = {
|
|
4351
4498
|
mainnet: "https://fullnode.mainnet.sui.io:443",
|
|
4352
4499
|
testnet: "https://fullnode.testnet.sui.io:443",
|
|
@@ -4390,11 +4537,56 @@ function resolveActiveMovePackageId(env = process.env) {
|
|
|
4390
4537
|
}
|
|
4391
4538
|
return configured[0]?.value;
|
|
4392
4539
|
}
|
|
4393
|
-
function
|
|
4540
|
+
function parseMcpTargetFlag(argv) {
|
|
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 = []) {
|
|
4394
4582
|
const network = env.SUIPAY_NETWORK ?? "testnet";
|
|
4395
|
-
const
|
|
4583
|
+
const target = resolveMcpTarget(env, argv);
|
|
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;
|
|
4396
4587
|
return {
|
|
4397
|
-
|
|
4588
|
+
target,
|
|
4589
|
+
consoleUrl,
|
|
4398
4590
|
gatewayUrl,
|
|
4399
4591
|
rpcUrl: env.SUIPAY_RPC_URL ?? GRPC_URLS[network],
|
|
4400
4592
|
network,
|
|
@@ -4443,10 +4635,23 @@ function isValid(obj) {
|
|
|
4443
4635
|
const c = obj;
|
|
4444
4636
|
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;
|
|
4445
4637
|
}
|
|
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
|
+
}
|
|
4446
4647
|
function isLocalSigningKeyRecord(obj) {
|
|
4447
4648
|
if (!obj || typeof obj !== "object") return false;
|
|
4448
4649
|
const c = obj;
|
|
4449
|
-
|
|
4650
|
+
if (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") {
|
|
4651
|
+
return false;
|
|
4652
|
+
}
|
|
4653
|
+
if (c.gatewayUrl !== void 0 && !isHttpUrl(c.gatewayUrl)) return false;
|
|
4654
|
+
return true;
|
|
4450
4655
|
}
|
|
4451
4656
|
function readCredentialsJson() {
|
|
4452
4657
|
const path = credsPath();
|
|
@@ -4482,15 +4687,19 @@ function loadCreds() {
|
|
|
4482
4687
|
}
|
|
4483
4688
|
return parsed;
|
|
4484
4689
|
}
|
|
4485
|
-
function
|
|
4486
|
-
const dir = credsDir();
|
|
4487
|
-
mkdirSync(dir, { recursive: true, mode: 448 });
|
|
4690
|
+
function fsyncDir(dir) {
|
|
4488
4691
|
try {
|
|
4489
|
-
|
|
4692
|
+
const fd = openSync(dir, "r");
|
|
4693
|
+
try {
|
|
4694
|
+
fsyncSync(fd);
|
|
4695
|
+
} finally {
|
|
4696
|
+
closeSync(fd);
|
|
4697
|
+
}
|
|
4490
4698
|
} catch {
|
|
4491
4699
|
}
|
|
4492
|
-
|
|
4493
|
-
|
|
4700
|
+
}
|
|
4701
|
+
function writeTmpCredentials(dir, payload) {
|
|
4702
|
+
const tmp = join2(dir, `.credentials.${process.pid}.${randomSuffix()}.tmp`);
|
|
4494
4703
|
const fd = openSync(tmp, "w", 384);
|
|
4495
4704
|
try {
|
|
4496
4705
|
writeSync(fd, JSON.stringify(payload, null, 2));
|
|
@@ -4498,7 +4707,23 @@ function writeCredentialsFile(payload) {
|
|
|
4498
4707
|
} finally {
|
|
4499
4708
|
closeSync(fd);
|
|
4500
4709
|
}
|
|
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);
|
|
4501
4725
|
renameSync(tmp, path);
|
|
4726
|
+
fsyncDir(dir);
|
|
4502
4727
|
try {
|
|
4503
4728
|
chmodSync(path, 384);
|
|
4504
4729
|
} catch {
|
|
@@ -4512,19 +4737,23 @@ function createCredentialsFileExclusive(payload) {
|
|
|
4512
4737
|
} catch {
|
|
4513
4738
|
}
|
|
4514
4739
|
const path = credsPath();
|
|
4515
|
-
|
|
4740
|
+
const tmp = writeTmpCredentials(dir, payload);
|
|
4741
|
+
fsyncDir(dir);
|
|
4516
4742
|
try {
|
|
4517
|
-
|
|
4743
|
+
linkSync(tmp, path);
|
|
4518
4744
|
} catch (error2) {
|
|
4745
|
+
try {
|
|
4746
|
+
unlinkSync(tmp);
|
|
4747
|
+
} catch {
|
|
4748
|
+
}
|
|
4519
4749
|
if (error2.code === "EEXIST") return false;
|
|
4520
4750
|
throw error2;
|
|
4521
4751
|
}
|
|
4522
4752
|
try {
|
|
4523
|
-
|
|
4524
|
-
|
|
4525
|
-
} finally {
|
|
4526
|
-
closeSync(fd);
|
|
4753
|
+
unlinkSync(tmp);
|
|
4754
|
+
} catch {
|
|
4527
4755
|
}
|
|
4756
|
+
fsyncDir(dir);
|
|
4528
4757
|
try {
|
|
4529
4758
|
chmodSync(path, 384);
|
|
4530
4759
|
} catch {
|
|
@@ -4613,12 +4842,90 @@ function rewriteV1ToLocalSigningKey(creds, opts = {}) {
|
|
|
4613
4842
|
delegatePublicKeyHex: creds.delegatePublicKeyHex,
|
|
4614
4843
|
delegateAddress: creds.delegateAddress,
|
|
4615
4844
|
createdAt: creds.createdAt,
|
|
4845
|
+
...isHttpUrl(creds.gatewayUrl) ? { gatewayUrl: creds.gatewayUrl } : {},
|
|
4616
4846
|
...opts.label?.trim() ? { label: opts.label.trim() } : creds.label?.trim() ? { label: creds.label.trim() } : {}
|
|
4617
4847
|
};
|
|
4618
4848
|
writeCredentialsFile(record);
|
|
4619
4849
|
return identity;
|
|
4620
4850
|
}
|
|
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
|
+
}
|
|
4621
4898
|
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
|
+
}
|
|
4622
4929
|
const parsed = readCredentialsJson();
|
|
4623
4930
|
if (parsed !== null) {
|
|
4624
4931
|
if (isLocalSigningKeyRecord(parsed)) {
|
|
@@ -4688,6 +4995,67 @@ function loadLocalSigner() {
|
|
|
4688
4995
|
}
|
|
4689
4996
|
};
|
|
4690
4997
|
}
|
|
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
|
+
}
|
|
4691
5059
|
|
|
4692
5060
|
// src/agent-held.ts
|
|
4693
5061
|
var processLedger = null;
|
|
@@ -4715,38 +5083,6 @@ function offerSecret(env) {
|
|
|
4715
5083
|
const secret = env.SUIPAY_SECRET?.trim();
|
|
4716
5084
|
return secret ? secret : null;
|
|
4717
5085
|
}
|
|
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
|
-
}
|
|
4750
5086
|
function resolveAgentHeldSettlement(cfg, spendAccountPay, env = process.env) {
|
|
4751
5087
|
const secret = offerSecret(env);
|
|
4752
5088
|
if (!secret) return null;
|
|
@@ -4840,66 +5176,325 @@ function toolAllowedByScope(toolName, grantedScope) {
|
|
|
4840
5176
|
}
|
|
4841
5177
|
|
|
4842
5178
|
// src/tools.ts
|
|
4843
|
-
import { isValidSuiAddress, normalizeSuiAddress as
|
|
5179
|
+
import { isValidSuiAddress, normalizeSuiAddress as normalizeSuiAddress12 } from "@mysten/sui/utils";
|
|
4844
5180
|
|
|
4845
|
-
//
|
|
4846
|
-
|
|
4847
|
-
|
|
4848
|
-
|
|
4849
|
-
|
|
4850
|
-
|
|
4851
|
-
|
|
4852
|
-
|
|
4853
|
-
|
|
4854
|
-
|
|
4855
|
-
|
|
4856
|
-
|
|
4857
|
-
|
|
4858
|
-
|
|
4859
|
-
|
|
4860
|
-
|
|
4861
|
-
|
|
4862
|
-
|
|
4863
|
-
|
|
4864
|
-
|
|
4865
|
-
|
|
4866
|
-
|
|
4867
|
-
|
|
4868
|
-
|
|
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);
|
|
4889
|
-
}
|
|
4890
|
-
if (typeof onChain === "string" && /^\d+$/.test(onChain.trim())) {
|
|
4891
|
-
const s = onChain.trim();
|
|
4892
|
-
try {
|
|
4893
|
-
if (BigInt(s) <= U64_MAX)
|
|
4894
|
-
return s;
|
|
4895
|
-
} catch {
|
|
4896
|
-
return null;
|
|
5181
|
+
// src/settle.ts
|
|
5182
|
+
async function settlePreparedMcpPayment(args) {
|
|
5183
|
+
try {
|
|
5184
|
+
await verifyPreparedSettlementKind({
|
|
5185
|
+
kindBytes: args.signingRequest.unsignedKindBytes,
|
|
5186
|
+
expected: {
|
|
5187
|
+
packageId: args.signingRequest.packageId,
|
|
5188
|
+
delegateAddress: args.expected.delegateAddress,
|
|
5189
|
+
amount: args.expected.amount,
|
|
5190
|
+
recipient: args.expected.recipient,
|
|
5191
|
+
accessMode: args.signingRequest.submit.moveCallTarget.endsWith(
|
|
5192
|
+
"settle_open_recipient_payment"
|
|
5193
|
+
) ? "open" : "scoped"
|
|
5194
|
+
},
|
|
5195
|
+
getObjectJson: args.getObjectJson
|
|
5196
|
+
});
|
|
5197
|
+
} catch (err) {
|
|
5198
|
+
if (err instanceof PreparedVerificationUnavailableError) {
|
|
5199
|
+
return {
|
|
5200
|
+
status: "failed",
|
|
5201
|
+
paid: false,
|
|
5202
|
+
code: DELEGATED_PAY_ERROR_CODES.PREPARED_VERIFY_UNAVAILABLE,
|
|
5203
|
+
detail: err.message
|
|
5204
|
+
};
|
|
4897
5205
|
}
|
|
4898
|
-
|
|
4899
|
-
|
|
4900
|
-
|
|
4901
|
-
|
|
4902
|
-
|
|
5206
|
+
return {
|
|
5207
|
+
status: "failed",
|
|
5208
|
+
paid: false,
|
|
5209
|
+
code: DELEGATED_PAY_ERROR_CODES.TX_MISMATCH,
|
|
5210
|
+
detail: err instanceof Error ? err.message : "prepared transaction mismatch"
|
|
5211
|
+
};
|
|
5212
|
+
}
|
|
5213
|
+
const prepared = args.signingRequest.resource;
|
|
5214
|
+
if (args.request.url !== prepared.url || args.request.method !== prepared.method) {
|
|
5215
|
+
return {
|
|
5216
|
+
status: "failed",
|
|
5217
|
+
paid: false,
|
|
5218
|
+
code: DELEGATED_PAY_ERROR_CODES.UNSUPPORTED_REQUEST,
|
|
5219
|
+
detail: "prepared resource request cannot be replayed"
|
|
5220
|
+
};
|
|
5221
|
+
}
|
|
5222
|
+
return payDelegatedSpendAccount({
|
|
5223
|
+
url: args.signingRequest.resource.url,
|
|
5224
|
+
request: args.request,
|
|
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);
|
|
5484
|
+
}
|
|
5485
|
+
if (typeof onChain === "string" && /^\d+$/.test(onChain.trim())) {
|
|
5486
|
+
const s = onChain.trim();
|
|
5487
|
+
try {
|
|
5488
|
+
if (BigInt(s) <= U64_MAX)
|
|
5489
|
+
return s;
|
|
5490
|
+
} catch {
|
|
5491
|
+
return null;
|
|
5492
|
+
}
|
|
5493
|
+
}
|
|
5494
|
+
return null;
|
|
5495
|
+
}
|
|
5496
|
+
function normalizePolicyBinding(raw) {
|
|
5497
|
+
if (!raw || typeof raw !== "object") {
|
|
4903
5498
|
return fail(MANIFEST_ERROR_CODES.MISSING_POLICY_BINDING, "policy is not an object");
|
|
4904
5499
|
}
|
|
4905
5500
|
const p = raw;
|
|
@@ -5272,71 +5867,489 @@ async function resolvePayerSession(options) {
|
|
|
5272
5867
|
});
|
|
5273
5868
|
}
|
|
5274
5869
|
|
|
5275
|
-
//
|
|
5276
|
-
|
|
5870
|
+
// ../sdk/dist/buyer/transport.js
|
|
5871
|
+
var FORBIDDEN_SECRET_KEYS = [
|
|
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()));
|
|
5277
5886
|
|
|
5278
|
-
//
|
|
5279
|
-
|
|
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()));
|
|
5895
|
+
|
|
5896
|
+
// ../sdk/dist/agent-sdk/delegate-agent.js
|
|
5897
|
+
async function verifyGrantOnChain(input) {
|
|
5898
|
+
let state;
|
|
5280
5899
|
try {
|
|
5281
|
-
await
|
|
5282
|
-
|
|
5283
|
-
|
|
5284
|
-
|
|
5285
|
-
|
|
5286
|
-
|
|
5287
|
-
|
|
5288
|
-
|
|
5289
|
-
|
|
5290
|
-
|
|
5291
|
-
|
|
5292
|
-
|
|
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;
|
|
5974
|
+
try {
|
|
5975
|
+
chain = createSpendAccountChain({
|
|
5976
|
+
packageId: deployment.packageId,
|
|
5977
|
+
network,
|
|
5978
|
+
...options.fullnodeUrl ? { url: options.fullnodeUrl } : {},
|
|
5979
|
+
...options.client ? { client: options.client } : {}
|
|
5293
5980
|
});
|
|
5294
|
-
} catch
|
|
5295
|
-
|
|
5981
|
+
} catch {
|
|
5982
|
+
return {
|
|
5983
|
+
ok: false,
|
|
5984
|
+
error: {
|
|
5985
|
+
code: MANIFEST_ERROR_CODES.GRANT_VERIFY_UNAVAILABLE,
|
|
5986
|
+
detail: "the agent could not open a Sui fullnode client, so this grant was not verified and no payment was prepared"
|
|
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 }
|
|
5995
|
+
});
|
|
5996
|
+
return mergeObjectEnvelope(object);
|
|
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);
|
|
5296
6029
|
return {
|
|
5297
6030
|
status: "failed",
|
|
5298
6031
|
paid: false,
|
|
5299
|
-
code: DELEGATED_PAY_ERROR_CODES.
|
|
5300
|
-
detail:
|
|
6032
|
+
code: DELEGATED_PAY_ERROR_CODES.UNSUPPORTED_REQUEST,
|
|
6033
|
+
detail: "request cannot be paid for"
|
|
5301
6034
|
};
|
|
5302
6035
|
}
|
|
5303
|
-
return {
|
|
5304
|
-
|
|
5305
|
-
|
|
5306
|
-
|
|
5307
|
-
|
|
5308
|
-
|
|
6036
|
+
return payDelegatedSpendAccount({
|
|
6037
|
+
url,
|
|
6038
|
+
request,
|
|
6039
|
+
session,
|
|
6040
|
+
cfg: { gatewayUrl: options.gatewayUrl },
|
|
6041
|
+
deps,
|
|
6042
|
+
intent: input.intent,
|
|
6043
|
+
...input.preparedTerms ? { preparedTerms: input.preparedTerms } : {}
|
|
6044
|
+
});
|
|
5309
6045
|
}
|
|
5310
|
-
|
|
5311
|
-
|
|
5312
|
-
|
|
5313
|
-
|
|
5314
|
-
|
|
5315
|
-
|
|
5316
|
-
|
|
5317
|
-
};
|
|
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 {
|
|
6159
|
+
}
|
|
6160
|
+
let preflightVerified = false;
|
|
6161
|
+
let completed = false;
|
|
6162
|
+
let error2 = null;
|
|
6163
|
+
const done = new Promise((resolve) => {
|
|
6164
|
+
const timer = setTimeout(() => {
|
|
6165
|
+
if (!completed) {
|
|
6166
|
+
error2 = new Error(`login timed out after ${timeoutMs}ms`);
|
|
6167
|
+
server.close();
|
|
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
|
+
});
|
|
5318
6332
|
}
|
|
5319
|
-
|
|
5320
|
-
|
|
5321
|
-
|
|
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 } : {}
|
|
6333
|
+
void done.then(() => {
|
|
6334
|
+
if (error2) {
|
|
6335
|
+
}
|
|
5332
6336
|
});
|
|
6337
|
+
return {
|
|
6338
|
+
connectUrl,
|
|
6339
|
+
port,
|
|
6340
|
+
connectState,
|
|
6341
|
+
done,
|
|
6342
|
+
close: () => {
|
|
6343
|
+
server.close();
|
|
6344
|
+
}
|
|
6345
|
+
};
|
|
5333
6346
|
}
|
|
5334
6347
|
|
|
5335
6348
|
// src/spend-account-pay.ts
|
|
5336
|
-
import { Transaction as
|
|
6349
|
+
import { Transaction as Transaction6 } from "@mysten/sui/transactions";
|
|
5337
6350
|
import {
|
|
5338
|
-
fromBase64 as
|
|
5339
|
-
normalizeSuiAddress as
|
|
6351
|
+
fromBase64 as fromBase646,
|
|
6352
|
+
normalizeSuiAddress as normalizeSuiAddress11,
|
|
5340
6353
|
toBase64 as toBase644
|
|
5341
6354
|
} from "@mysten/sui/utils";
|
|
5342
6355
|
var SUI_CLOCK3 = "0x6";
|
|
@@ -5374,11 +6387,11 @@ async function buildSpendAccountPayKind2(input) {
|
|
|
5374
6387
|
if (!input.termsHash.length) throw new Error("termsHash must be non-empty");
|
|
5375
6388
|
if (!input.paymentIdHash.length) throw new Error("paymentIdHash must be non-empty");
|
|
5376
6389
|
const auth = defaultAuthorityForRail("delegate", input.packageId);
|
|
5377
|
-
const target = `${
|
|
6390
|
+
const target = `${normalizeSuiAddress11(auth.packageId)}::${auth.module}::${auth.payFunction}`;
|
|
5378
6391
|
const hashHex = input.targetHash.replace(/^0x/i, "").toLowerCase();
|
|
5379
6392
|
if (!/^[0-9a-f]{64}$/.test(hashHex)) throw new Error("targetHash must be 32-byte hex");
|
|
5380
6393
|
const hash = Uint8Array.from(Buffer.from(hashHex, "hex"));
|
|
5381
|
-
const tx = new
|
|
6394
|
+
const tx = new Transaction6();
|
|
5382
6395
|
tx.setSender(input.delegate);
|
|
5383
6396
|
tx.moveCall({
|
|
5384
6397
|
target,
|
|
@@ -5412,29 +6425,29 @@ async function buildSpendAccountPayKind2(input) {
|
|
|
5412
6425
|
function verifySpendAccountPayReconstructs(bytes, expected) {
|
|
5413
6426
|
let data;
|
|
5414
6427
|
try {
|
|
5415
|
-
data =
|
|
6428
|
+
data = Transaction6.from(fromBase646(bytes)).getData();
|
|
5416
6429
|
} catch {
|
|
5417
6430
|
throw new Error("sponsored transaction is not valid Sui transaction bytes");
|
|
5418
6431
|
}
|
|
5419
|
-
if (!data.sender ||
|
|
6432
|
+
if (!data.sender || normalizeSuiAddress11(data.sender) !== normalizeSuiAddress11(expected.delegate)) {
|
|
5420
6433
|
throw new Error("sponsored transaction sender does not match delegate");
|
|
5421
6434
|
}
|
|
5422
6435
|
if (!data.commands || data.commands.length !== 1) {
|
|
5423
6436
|
throw new Error("sponsored transaction must contain exactly one command");
|
|
5424
6437
|
}
|
|
5425
6438
|
const auth = defaultAuthorityForRail("delegate", expected.packageId);
|
|
5426
|
-
const wanted = `${
|
|
6439
|
+
const wanted = `${normalizeSuiAddress11(auth.packageId)}::${auth.module}::${auth.payFunction}`;
|
|
5427
6440
|
const cmd = data.commands[0];
|
|
5428
6441
|
if (cmd.$kind !== "MoveCall" || !cmd.MoveCall) {
|
|
5429
6442
|
throw new Error("sponsored transaction is not a MoveCall");
|
|
5430
6443
|
}
|
|
5431
|
-
const got = `${
|
|
6444
|
+
const got = `${normalizeSuiAddress11(String(cmd.MoveCall.package))}::${cmd.MoveCall.module}::${cmd.MoveCall.function}`;
|
|
5432
6445
|
if (got !== wanted) {
|
|
5433
6446
|
throw new Error(`sponsored transaction does not reconstruct ${wanted}`);
|
|
5434
6447
|
}
|
|
5435
6448
|
let expectedKind;
|
|
5436
6449
|
try {
|
|
5437
|
-
expectedKind =
|
|
6450
|
+
expectedKind = Transaction6.fromKind(fromBase646(expected.kindBytes)).getData();
|
|
5438
6451
|
} catch {
|
|
5439
6452
|
throw new Error("sponsored transaction kind baseline invalid");
|
|
5440
6453
|
}
|
|
@@ -5591,7 +6604,7 @@ async function prepareSpendAccountPayment(args) {
|
|
|
5591
6604
|
const signingRequest = {
|
|
5592
6605
|
unsignedKindBytes: kindBytes,
|
|
5593
6606
|
delegateAddress: resolved.delegateAddress,
|
|
5594
|
-
packageId:
|
|
6607
|
+
packageId: normalizeSuiAddress11(authority.packageId),
|
|
5595
6608
|
poolObjectId: resolved.poolObjectId,
|
|
5596
6609
|
grantObjectId: resolved.grantObjectId,
|
|
5597
6610
|
policyId: resolved.onChainPolicyId,
|
|
@@ -5672,28 +6685,256 @@ function mapErrorCode(code) {
|
|
|
5672
6685
|
}
|
|
5673
6686
|
}
|
|
5674
6687
|
|
|
5675
|
-
// src/
|
|
5676
|
-
|
|
5677
|
-
|
|
5678
|
-
|
|
5679
|
-
|
|
5680
|
-
|
|
5681
|
-
|
|
5682
|
-
|
|
5683
|
-
|
|
5684
|
-
|
|
5685
|
-
|
|
5686
|
-
|
|
5687
|
-
|
|
5688
|
-
|
|
6688
|
+
// src/discover-afford.ts
|
|
6689
|
+
import { normalizeStructTag as normalizeStructTag6 } from "@mysten/sui/utils";
|
|
6690
|
+
function sameCoinType2(rowAsset, grantCoin) {
|
|
6691
|
+
if (typeof rowAsset !== "string" || !grantCoin) return false;
|
|
6692
|
+
try {
|
|
6693
|
+
return normalizeStructTag6(rowAsset) === normalizeStructTag6(grantCoin);
|
|
6694
|
+
} catch {
|
|
6695
|
+
return false;
|
|
6696
|
+
}
|
|
6697
|
+
}
|
|
6698
|
+
function parseAtomic(value) {
|
|
6699
|
+
if (typeof value === "bigint") return value >= 0n ? value : null;
|
|
6700
|
+
if (typeof value === "number" && Number.isInteger(value) && value >= 0) {
|
|
6701
|
+
return BigInt(value);
|
|
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 };
|
|
6873
|
+
}
|
|
6874
|
+
|
|
6875
|
+
// src/tool-result.ts
|
|
6876
|
+
import { pathToFileURL } from "url";
|
|
6877
|
+
|
|
6878
|
+
// src/delivery-file.ts
|
|
6879
|
+
import { mkdirSync as mkdirSync2, writeFileSync, chmodSync as chmodSync2 } from "fs";
|
|
6880
|
+
import { dirname as dirname2, join as join3 } from "path";
|
|
6881
|
+
function mcpDeliveriesDir() {
|
|
6882
|
+
return join3(dirname2(mcpCredentialsPath()), "deliveries");
|
|
6883
|
+
}
|
|
6884
|
+
var EXT = {
|
|
6885
|
+
"image/png": ".png",
|
|
6886
|
+
"image/jpeg": ".jpg",
|
|
6887
|
+
"image/jpg": ".jpg",
|
|
6888
|
+
"image/webp": ".webp",
|
|
6889
|
+
"image/gif": ".gif",
|
|
6890
|
+
"image/svg+xml": ".svg",
|
|
6891
|
+
"audio/mpeg": ".mp3",
|
|
6892
|
+
"audio/wav": ".wav",
|
|
6893
|
+
"audio/ogg": ".ogg",
|
|
6894
|
+
"video/mp4": ".mp4",
|
|
6895
|
+
"application/pdf": ".pdf",
|
|
6896
|
+
"application/json": ".json",
|
|
6897
|
+
"text/plain": ".txt"
|
|
6898
|
+
};
|
|
6899
|
+
function extensionFor(contentType) {
|
|
6900
|
+
const type = contentType.split(";")[0]?.trim().toLowerCase() ?? "";
|
|
6901
|
+
return EXT[type] ?? ".bin";
|
|
6902
|
+
}
|
|
6903
|
+
function fileStem(digest, challengeId) {
|
|
6904
|
+
const raw = (digest ?? challengeId ?? `delivery-${Date.now()}`).trim();
|
|
6905
|
+
const safe = raw.replace(/[^A-Za-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
6906
|
+
return (safe.slice(0, 80) || `delivery-${Date.now()}`).replace(/^\.+$/, "delivery");
|
|
6907
|
+
}
|
|
6908
|
+
function saveDeliveredBody(input) {
|
|
6909
|
+
if (input.bytes.byteLength === 0) return void 0;
|
|
6910
|
+
const dir = mcpDeliveriesDir();
|
|
6911
|
+
mkdirSync2(dir, { recursive: true, mode: 448 });
|
|
6912
|
+
try {
|
|
6913
|
+
chmodSync2(dir, 448);
|
|
6914
|
+
} catch {
|
|
6915
|
+
}
|
|
6916
|
+
const path = join3(
|
|
6917
|
+
dir,
|
|
6918
|
+
`${fileStem(input.digest, input.challengeId)}${extensionFor(input.contentType)}`
|
|
6919
|
+
);
|
|
6920
|
+
writeFileSync(path, input.bytes, { mode: 384 });
|
|
6921
|
+
try {
|
|
6922
|
+
chmodSync2(path, 384);
|
|
6923
|
+
} catch {
|
|
6924
|
+
}
|
|
6925
|
+
return path;
|
|
5689
6926
|
}
|
|
5690
|
-
|
|
6927
|
+
|
|
6928
|
+
// src/tool-result.ts
|
|
6929
|
+
var MAX_MCP_IMAGE_BYTES = 5 * 1024 * 1024;
|
|
5691
6930
|
function text(value) {
|
|
5692
6931
|
return {
|
|
5693
|
-
content: [
|
|
5694
|
-
|
|
5695
|
-
|
|
5696
|
-
|
|
6932
|
+
content: [
|
|
6933
|
+
{
|
|
6934
|
+
type: "text",
|
|
6935
|
+
text: typeof value === "string" ? value : JSON.stringify(value, null, 2)
|
|
6936
|
+
}
|
|
6937
|
+
]
|
|
5697
6938
|
};
|
|
5698
6939
|
}
|
|
5699
6940
|
function error(value) {
|
|
@@ -5705,7 +6946,12 @@ function closedError(code, detail) {
|
|
|
5705
6946
|
function closedPayError(code, detail) {
|
|
5706
6947
|
return error({ status: "failed", paid: false, code, detail });
|
|
5707
6948
|
}
|
|
5708
|
-
|
|
6949
|
+
function receiptChallengeId(receipt) {
|
|
6950
|
+
if (!receipt || typeof receipt !== "object") return void 0;
|
|
6951
|
+
const id = receipt.challengeId;
|
|
6952
|
+
return typeof id === "string" && id.length > 0 ? id : void 0;
|
|
6953
|
+
}
|
|
6954
|
+
var MCP_SAVE_ERROR = "delivery file was not written; do not call pay again";
|
|
5709
6955
|
function paidResult(result) {
|
|
5710
6956
|
if (!isBinaryBody(result.body)) return text(result);
|
|
5711
6957
|
const body = result.body;
|
|
@@ -5714,8 +6960,33 @@ function paidResult(result) {
|
|
|
5714
6960
|
contentType: body.contentType,
|
|
5715
6961
|
byteLength: body.byteLength
|
|
5716
6962
|
};
|
|
6963
|
+
let savedTo;
|
|
6964
|
+
try {
|
|
6965
|
+
savedTo = saveDeliveredBody({
|
|
6966
|
+
bytes: body.bytes,
|
|
6967
|
+
contentType: body.contentType,
|
|
6968
|
+
digest: result.txDigest,
|
|
6969
|
+
challengeId: receiptChallengeId(result.receipt)
|
|
6970
|
+
});
|
|
6971
|
+
} catch {
|
|
6972
|
+
savedTo = void 0;
|
|
6973
|
+
}
|
|
6974
|
+
const payload = savedTo ? {
|
|
6975
|
+
savedTo,
|
|
6976
|
+
savedToUrl: pathToFileURL(savedTo).href,
|
|
6977
|
+
...result,
|
|
6978
|
+
body: shape
|
|
6979
|
+
} : {
|
|
6980
|
+
savedTo: null,
|
|
6981
|
+
saveError: MCP_SAVE_ERROR,
|
|
6982
|
+
...result,
|
|
6983
|
+
body: shape
|
|
6984
|
+
};
|
|
5717
6985
|
const content = [
|
|
5718
|
-
{
|
|
6986
|
+
{
|
|
6987
|
+
type: "text",
|
|
6988
|
+
text: JSON.stringify(payload, null, 2)
|
|
6989
|
+
}
|
|
5719
6990
|
];
|
|
5720
6991
|
if (body.contentType.startsWith("image/") && body.bytes.byteLength > 0 && body.bytes.byteLength <= MAX_MCP_IMAGE_BYTES) {
|
|
5721
6992
|
content.push({
|
|
@@ -5726,6 +6997,28 @@ function paidResult(result) {
|
|
|
5726
6997
|
}
|
|
5727
6998
|
return { content };
|
|
5728
6999
|
}
|
|
7000
|
+
function settlePayResult(result) {
|
|
7001
|
+
if (result.paid === true) return paidResult(result);
|
|
7002
|
+
return error(result);
|
|
7003
|
+
}
|
|
7004
|
+
|
|
7005
|
+
// src/tools.ts
|
|
7006
|
+
function payerTrace(deps) {
|
|
7007
|
+
const hooks = deps.trace;
|
|
7008
|
+
const buyerAccountId = deps.auth?.buyerAccountId;
|
|
7009
|
+
if (!hooks || !buyerAccountId) return null;
|
|
7010
|
+
return {
|
|
7011
|
+
sink: hooks.sink,
|
|
7012
|
+
traceId: hooks.context.traceId,
|
|
7013
|
+
requestId: hooks.context.requestId,
|
|
7014
|
+
buyerAccountId,
|
|
7015
|
+
connectionId: hooks.runtime.connectionId,
|
|
7016
|
+
...hooks.runtime.secret ? { secret: hooks.runtime.secret } : {},
|
|
7017
|
+
...hooks.runtime.now ? { now: hooks.runtime.now } : {}
|
|
7018
|
+
};
|
|
7019
|
+
}
|
|
7020
|
+
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.";
|
|
7021
|
+
var LOGIN_BG_TIMEOUT_MS = 5 * 6e4;
|
|
5729
7022
|
function preparedResult(result) {
|
|
5730
7023
|
return text({
|
|
5731
7024
|
status: "prepared",
|
|
@@ -5745,7 +7038,7 @@ function normalizePayIntent(args) {
|
|
|
5745
7038
|
if (!/^0x[0-9a-fA-F]{1,64}$/.test(raw)) {
|
|
5746
7039
|
throw new Error("recipient must be a 0x-prefixed Sui address");
|
|
5747
7040
|
}
|
|
5748
|
-
const normalized =
|
|
7041
|
+
const normalized = normalizeSuiAddress12(raw);
|
|
5749
7042
|
if (!isValidSuiAddress(normalized) || /^0x0+$/.test(normalized)) {
|
|
5750
7043
|
throw new Error("recipient must be a non-zero Sui address");
|
|
5751
7044
|
}
|
|
@@ -5764,7 +7057,7 @@ function intentViolation(intent, prepared) {
|
|
|
5764
7057
|
if (intent.recipient) {
|
|
5765
7058
|
let preparedRecipient;
|
|
5766
7059
|
try {
|
|
5767
|
-
preparedRecipient =
|
|
7060
|
+
preparedRecipient = normalizeSuiAddress12(prepared.recipient);
|
|
5768
7061
|
} catch {
|
|
5769
7062
|
return "prepared payment does not name a valid recipient";
|
|
5770
7063
|
}
|
|
@@ -5870,10 +7163,10 @@ async function pay(args, cfg, deps = {}) {
|
|
|
5870
7163
|
...deps.fetchImpl && !deps.agentHeld.payerDeps.fetchImpl ? { fetchImpl: deps.fetchImpl } : {}
|
|
5871
7164
|
}
|
|
5872
7165
|
});
|
|
5873
|
-
return
|
|
7166
|
+
return settlePayResult(settled);
|
|
5874
7167
|
}
|
|
5875
7168
|
if (result.status === "prepared") return preparedResult(result);
|
|
5876
|
-
return
|
|
7169
|
+
return settlePayResult(result);
|
|
5877
7170
|
}
|
|
5878
7171
|
if (deps.auth) {
|
|
5879
7172
|
return error(
|
|
@@ -5888,122 +7181,259 @@ async function pay(args, cfg, deps = {}) {
|
|
|
5888
7181
|
}
|
|
5889
7182
|
const local = loadLocalSigner();
|
|
5890
7183
|
if (!local) return error(AUTH_REQUIRED);
|
|
5891
|
-
return payStdioWithLocalKey({ request, intent, cfg, deps
|
|
7184
|
+
return payStdioWithLocalKey({ request, intent, cfg, deps });
|
|
5892
7185
|
}
|
|
5893
|
-
function
|
|
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
|
-
};
|
|
7186
|
+
function stdioGatewayUrl(cfg) {
|
|
7187
|
+
return loadLocalSigningKeyRecord()?.gatewayUrl ?? cfg.gatewayUrl;
|
|
5906
7188
|
}
|
|
5907
|
-
|
|
5908
|
-
|
|
5909
|
-
|
|
5910
|
-
|
|
5911
|
-
|
|
5912
|
-
|
|
5913
|
-
|
|
5914
|
-
|
|
5915
|
-
|
|
5916
|
-
|
|
5917
|
-
|
|
5918
|
-
|
|
7189
|
+
function isExpiredBootstrapGrant(expiresAtMs, nowMs) {
|
|
7190
|
+
if (expiresAtMs === void 0 || expiresAtMs === null || expiresAtMs === "" || expiresAtMs === "0" || expiresAtMs === 0) {
|
|
7191
|
+
return false;
|
|
7192
|
+
}
|
|
7193
|
+
const exp = typeof expiresAtMs === "number" ? expiresAtMs : typeof expiresAtMs === "string" && /^\d+$/.test(expiresAtMs) ? Number(expiresAtMs) : NaN;
|
|
7194
|
+
if (!Number.isFinite(exp) || exp <= 0) return false;
|
|
7195
|
+
return exp <= nowMs;
|
|
7196
|
+
}
|
|
7197
|
+
function grantObjectIdFromBootstrap(grants, nowMs) {
|
|
7198
|
+
let named = 0;
|
|
7199
|
+
const live = [];
|
|
7200
|
+
for (const grant of grants) {
|
|
7201
|
+
if (!grant || typeof grant !== "object") continue;
|
|
7202
|
+
const g = grant;
|
|
7203
|
+
const id = g.grantObjectId;
|
|
7204
|
+
if (typeof id !== "string" || !id.trim()) continue;
|
|
7205
|
+
named += 1;
|
|
7206
|
+
if (isExpiredBootstrapGrant(g.expiresAtMs, nowMs)) continue;
|
|
7207
|
+
live.push(id.trim());
|
|
7208
|
+
}
|
|
7209
|
+
if (live.length === 1) return { kind: "live", grantObjectId: live[0] };
|
|
7210
|
+
if (live.length > 1) return { kind: "ambiguous" };
|
|
7211
|
+
if (named > 0) return { kind: "absent" };
|
|
7212
|
+
if (grants.length > 0) return { kind: "unknown", detail: "delegate grants list was not usable" };
|
|
7213
|
+
return { kind: "absent" };
|
|
7214
|
+
}
|
|
7215
|
+
async function checkLiveDelegateGrant(args) {
|
|
7216
|
+
const deployment = await fetchGatewayDeployment({
|
|
7217
|
+
gatewayUrl: args.gatewayUrl,
|
|
7218
|
+
...args.fetchImpl ? { fetchImpl: args.fetchImpl } : {}
|
|
5919
7219
|
});
|
|
5920
|
-
if (!
|
|
5921
|
-
return
|
|
7220
|
+
if (!deployment.ok) {
|
|
7221
|
+
return {
|
|
7222
|
+
kind: "unknown",
|
|
7223
|
+
detail: "gateway deployment identity could not be verified"
|
|
7224
|
+
};
|
|
5922
7225
|
}
|
|
5923
|
-
const
|
|
5924
|
-
|
|
5925
|
-
|
|
5926
|
-
|
|
5927
|
-
|
|
5928
|
-
|
|
5929
|
-
snapshot,
|
|
5930
|
-
packageId,
|
|
5931
|
-
...resolved.value.session.assertLive ? { assertLive: resolved.value.session.assertLive } : {}
|
|
7226
|
+
const listed = await bootstrapPayerGrants({
|
|
7227
|
+
gatewayUrl: args.gatewayUrl,
|
|
7228
|
+
context: {
|
|
7229
|
+
network: deployment.value.network,
|
|
7230
|
+
packageId: deployment.value.packageId,
|
|
7231
|
+
delegateAddress: args.delegateAddress
|
|
5932
7232
|
},
|
|
5933
|
-
|
|
5934
|
-
|
|
5935
|
-
...deps.fetchImpl ? { fetchImpl: deps.fetchImpl } : {},
|
|
5936
|
-
...deps.buildPayKind ? { buildPayKind: deps.buildPayKind } : {}
|
|
5937
|
-
}
|
|
7233
|
+
signPersonalMessage: args.signPersonalMessage,
|
|
7234
|
+
...args.fetchImpl ? { fetchImpl: args.fetchImpl } : {}
|
|
5938
7235
|
});
|
|
5939
|
-
if (
|
|
5940
|
-
|
|
5941
|
-
|
|
5942
|
-
|
|
5943
|
-
|
|
5944
|
-
|
|
5945
|
-
|
|
5946
|
-
|
|
5947
|
-
|
|
5948
|
-
|
|
5949
|
-
|
|
5950
|
-
|
|
5951
|
-
|
|
5952
|
-
|
|
5953
|
-
|
|
5954
|
-
|
|
5955
|
-
|
|
5956
|
-
|
|
5957
|
-
|
|
5958
|
-
|
|
5959
|
-
|
|
5960
|
-
|
|
7236
|
+
if (!listed.ok) {
|
|
7237
|
+
return {
|
|
7238
|
+
kind: "unknown",
|
|
7239
|
+
detail: `delegate grants read ${listed.error} (HTTP ${listed.status})`
|
|
7240
|
+
};
|
|
7241
|
+
}
|
|
7242
|
+
if (!Array.isArray(listed.data.grants) || listed.data.grants.length === 0) {
|
|
7243
|
+
return { kind: "absent" };
|
|
7244
|
+
}
|
|
7245
|
+
return grantObjectIdFromBootstrap(listed.data.grants, Date.now());
|
|
7246
|
+
}
|
|
7247
|
+
async function payStdioWithLocalKey(args) {
|
|
7248
|
+
const { request, intent, cfg, deps } = args;
|
|
7249
|
+
const built = await stdioPayerAndReader(cfg, deps);
|
|
7250
|
+
if (!built.ok) return built.result;
|
|
7251
|
+
const payer = built.payer;
|
|
7252
|
+
const grantCeiling = payer.grant.maxPerPayment;
|
|
7253
|
+
const maxAmount = intent.maxAmount !== void 0 ? intent.maxAmount.toString() : grantCeiling != null && grantCeiling !== "" ? grantCeiling : null;
|
|
7254
|
+
if (maxAmount === null) {
|
|
7255
|
+
return error({
|
|
7256
|
+
status: "failed",
|
|
7257
|
+
paid: false,
|
|
7258
|
+
code: "POLICY_REJECTED",
|
|
7259
|
+
detail: "stdio pay requires maxAmount or a grant maxPerPayment; refusing to default the ceiling to the offer"
|
|
7260
|
+
});
|
|
7261
|
+
}
|
|
7262
|
+
const settled = await payer.pay(request.url, {
|
|
7263
|
+
method: request.method,
|
|
7264
|
+
...request.method === "POST" ? { body: request.body } : {},
|
|
7265
|
+
...request.contentType ? { contentType: request.contentType } : {},
|
|
7266
|
+
intent: {
|
|
5961
7267
|
maxAmount,
|
|
5962
|
-
asset:
|
|
7268
|
+
asset: payer.grant.coinType,
|
|
5963
7269
|
...intent.recipient ? { recipient: intent.recipient } : {}
|
|
5964
|
-
}
|
|
5965
|
-
|
|
5966
|
-
|
|
5967
|
-
|
|
5968
|
-
|
|
5969
|
-
|
|
5970
|
-
|
|
5971
|
-
|
|
5972
|
-
|
|
5973
|
-
|
|
5974
|
-
|
|
5975
|
-
|
|
5976
|
-
|
|
5977
|
-
|
|
5978
|
-
|
|
5979
|
-
|
|
5980
|
-
deps: {
|
|
5981
|
-
...agentHeld.payerDeps,
|
|
5982
|
-
...deps.fetchImpl && !agentHeld.payerDeps.fetchImpl ? { fetchImpl: deps.fetchImpl } : {}
|
|
5983
|
-
},
|
|
5984
|
-
intent: spendIntent
|
|
7270
|
+
}
|
|
7271
|
+
});
|
|
7272
|
+
return settlePayResult(settled);
|
|
7273
|
+
}
|
|
7274
|
+
async function stdioPayerAndReader(cfg, deps) {
|
|
7275
|
+
const keypair = loadLocalKeypair();
|
|
7276
|
+
if (!keypair) return { ok: false, result: error(AUTH_REQUIRED) };
|
|
7277
|
+
const gatewayUrl = stdioGatewayUrl(cfg);
|
|
7278
|
+
const local = loadLocalSigner();
|
|
7279
|
+
let select;
|
|
7280
|
+
const shouldSelect = Boolean(local) && (deps.fetchImpl !== void 0 || deps.createPayer === void 0);
|
|
7281
|
+
if (local && shouldSelect) {
|
|
7282
|
+
const check = await checkLiveDelegateGrant({
|
|
7283
|
+
gatewayUrl,
|
|
7284
|
+
delegateAddress: local.address,
|
|
7285
|
+
signPersonalMessage: local.signPersonalMessage,
|
|
7286
|
+
...deps.fetchImpl ? { fetchImpl: deps.fetchImpl } : {}
|
|
5985
7287
|
});
|
|
5986
|
-
|
|
7288
|
+
if (check.kind === "ambiguous") {
|
|
7289
|
+
return {
|
|
7290
|
+
ok: false,
|
|
7291
|
+
result: closedError(
|
|
7292
|
+
"AMBIGUOUS_GRANT",
|
|
7293
|
+
"multiple live grants name this key; pass a selector (grantObjectId / asset / coinType)"
|
|
7294
|
+
)
|
|
7295
|
+
};
|
|
7296
|
+
}
|
|
7297
|
+
if (check.kind === "live") select = { grantObjectId: check.grantObjectId };
|
|
7298
|
+
}
|
|
7299
|
+
const built = await (deps.createPayer ?? createPayer)({
|
|
7300
|
+
gatewayUrl,
|
|
7301
|
+
keypair,
|
|
7302
|
+
...select ? { select } : {},
|
|
7303
|
+
...deps.fetchImpl ? { fetchImpl: deps.fetchImpl } : {},
|
|
7304
|
+
...cfg.rpcUrl ? { fullnodeUrl: cfg.rpcUrl } : {}
|
|
7305
|
+
});
|
|
7306
|
+
if (!built.ok) {
|
|
7307
|
+
return {
|
|
7308
|
+
ok: false,
|
|
7309
|
+
result: closedError(built.error.code, built.error.detail)
|
|
7310
|
+
};
|
|
5987
7311
|
}
|
|
5988
|
-
return
|
|
7312
|
+
return { ok: true, payer: built.value };
|
|
7313
|
+
}
|
|
7314
|
+
function stdioReaderForPayer(cfg, deps, payer) {
|
|
7315
|
+
return (deps.createReader ?? createReader)({
|
|
7316
|
+
gatewayUrl: stdioGatewayUrl(cfg),
|
|
7317
|
+
context: {
|
|
7318
|
+
network: payer.deployment.network,
|
|
7319
|
+
packageId: payer.deployment.packageId,
|
|
7320
|
+
grantObjectId: payer.grant.grantObjectId,
|
|
7321
|
+
delegateAddress: payer.address
|
|
7322
|
+
},
|
|
7323
|
+
signPersonalMessage: async (message) => {
|
|
7324
|
+
const { signature } = await loadLocalKeypair().signPersonalMessage(message);
|
|
7325
|
+
return signature;
|
|
7326
|
+
},
|
|
7327
|
+
...deps.fetchImpl ? { fetchImpl: deps.fetchImpl } : {}
|
|
7328
|
+
});
|
|
7329
|
+
}
|
|
7330
|
+
function budgetFromBalance(balance, grant) {
|
|
7331
|
+
if (!balance.ok || !balance.data) return void 0;
|
|
7332
|
+
return {
|
|
7333
|
+
sessionRemaining: balance.data.sessionRemaining ?? null,
|
|
7334
|
+
maxPerPayment: balance.data.maxPerPayment ?? grant.maxPerPayment ?? null,
|
|
7335
|
+
coinType: balance.data.coinType ?? grant.coinType ?? null
|
|
7336
|
+
};
|
|
7337
|
+
}
|
|
7338
|
+
async function fetchPublicCatalog(gatewayUrl, fetchImpl) {
|
|
7339
|
+
const catalogUrl = new URL("/v1/services", gatewayUrl);
|
|
7340
|
+
const response = await (fetchImpl ?? fetch)(catalogUrl);
|
|
7341
|
+
if (!response.ok) return { ok: false, result: error(`gateway unavailable: HTTP ${response.status}`) };
|
|
7342
|
+
return { ok: true, catalog: await response.json() };
|
|
7343
|
+
}
|
|
7344
|
+
async function publicCatalog(gatewayUrl, query, fetchImpl) {
|
|
7345
|
+
const loaded = await fetchPublicCatalog(gatewayUrl, fetchImpl);
|
|
7346
|
+
if (!loaded.ok) return loaded.result;
|
|
7347
|
+
return text(
|
|
7348
|
+
joinDiscoverCatalog({
|
|
7349
|
+
catalog: loaded.catalog,
|
|
7350
|
+
gatewayUrl,
|
|
7351
|
+
query
|
|
7352
|
+
})
|
|
7353
|
+
);
|
|
5989
7354
|
}
|
|
5990
7355
|
async function discover(args, cfg, deps = {}) {
|
|
5991
|
-
|
|
5992
|
-
|
|
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
|
-
);
|
|
7356
|
+
if (deps.auth || !loadLocalKeypair()) {
|
|
7357
|
+
return publicCatalog(cfg.gatewayUrl, args.query, deps.fetchImpl);
|
|
6003
7358
|
}
|
|
6004
|
-
|
|
7359
|
+
const local = loadLocalSigner();
|
|
7360
|
+
if (local) {
|
|
7361
|
+
const check = await checkLiveDelegateGrant({
|
|
7362
|
+
gatewayUrl: stdioGatewayUrl(cfg),
|
|
7363
|
+
delegateAddress: local.address,
|
|
7364
|
+
signPersonalMessage: local.signPersonalMessage,
|
|
7365
|
+
...deps.fetchImpl ? { fetchImpl: deps.fetchImpl } : {}
|
|
7366
|
+
});
|
|
7367
|
+
if (check.kind !== "live") {
|
|
7368
|
+
return publicCatalog(stdioGatewayUrl(cfg), args.query, deps.fetchImpl);
|
|
7369
|
+
}
|
|
7370
|
+
}
|
|
7371
|
+
const resolved = await stdioPayerAndReader(cfg, deps);
|
|
7372
|
+
if (!resolved.ok) return resolved.result;
|
|
7373
|
+
const gatewayUrl = stdioGatewayUrl(cfg);
|
|
7374
|
+
const loaded = await fetchPublicCatalog(gatewayUrl, deps.fetchImpl);
|
|
7375
|
+
if (!loaded.ok) return loaded.result;
|
|
7376
|
+
const scoped = resolved.payer.grant.accessMode === "scoped";
|
|
7377
|
+
const reader = stdioReaderForPayer(cfg, deps, resolved.payer);
|
|
7378
|
+
const [policies, balance] = await Promise.all([
|
|
7379
|
+
scoped ? reader.policies() : Promise.resolve({ ok: true, data: { policies: void 0 } }),
|
|
7380
|
+
reader.balance()
|
|
7381
|
+
]);
|
|
7382
|
+
if (scoped && !policies.ok) {
|
|
7383
|
+
return closedError("POLICIES_UNAVAILABLE", "grant policies could not be loaded");
|
|
7384
|
+
}
|
|
7385
|
+
return text(
|
|
7386
|
+
joinDiscoverCatalog({
|
|
7387
|
+
catalog: loaded.catalog,
|
|
7388
|
+
gatewayUrl,
|
|
7389
|
+
query: args.query,
|
|
7390
|
+
...scoped && policies.ok ? { policies: policies.data.policies } : {},
|
|
7391
|
+
budget: budgetFromBalance(balance, resolved.payer.grant)
|
|
7392
|
+
})
|
|
7393
|
+
);
|
|
7394
|
+
}
|
|
7395
|
+
async function accessContext(deps = {}, cfg) {
|
|
7396
|
+
if (deps.auth) {
|
|
7397
|
+
if (!deps.auth.access) {
|
|
7398
|
+
return error(
|
|
7399
|
+
"SuiPay access context unavailable. Reconnect this MCP session or inspect the connection in buyer console."
|
|
7400
|
+
);
|
|
7401
|
+
}
|
|
7402
|
+
return text(deps.auth.access);
|
|
7403
|
+
}
|
|
7404
|
+
if (!cfg) return error(AUTH_REQUIRED);
|
|
7405
|
+
const resolved = await stdioPayerAndReader(cfg, deps);
|
|
7406
|
+
if (!resolved.ok) return resolved.result;
|
|
7407
|
+
const gatewayUrl = stdioGatewayUrl(cfg);
|
|
7408
|
+
const reader = stdioReaderForPayer(cfg, deps, resolved.payer);
|
|
7409
|
+
const scoped = resolved.payer.grant.accessMode === "scoped";
|
|
7410
|
+
const [policies, balance, loaded] = await Promise.all([
|
|
7411
|
+
reader.policies(),
|
|
7412
|
+
reader.balance(),
|
|
7413
|
+
fetchPublicCatalog(gatewayUrl, deps.fetchImpl)
|
|
7414
|
+
]);
|
|
7415
|
+
const budget = budgetFromBalance(balance, resolved.payer.grant);
|
|
7416
|
+
const joined = loaded.ok && (!scoped || policies.ok) ? joinDiscoverCatalog({
|
|
7417
|
+
catalog: loaded.catalog,
|
|
7418
|
+
gatewayUrl,
|
|
7419
|
+
...scoped && policies.ok ? { policies: policies.data.policies } : {},
|
|
7420
|
+
budget
|
|
7421
|
+
}) : null;
|
|
7422
|
+
return text({
|
|
7423
|
+
accessMode: resolved.payer.grant.accessMode,
|
|
7424
|
+
grant: {
|
|
7425
|
+
id: resolved.payer.grant.grantObjectId,
|
|
7426
|
+
poolObjectId: resolved.payer.grant.poolObjectId,
|
|
7427
|
+
coinType: resolved.payer.grant.coinType,
|
|
7428
|
+
maxPerPayment: resolved.payer.grant.maxPerPayment ?? null,
|
|
7429
|
+
sessionCap: resolved.payer.grant.sessionCap ?? null
|
|
7430
|
+
},
|
|
7431
|
+
policies: policies.ok ? policies.data : { error: policies.error },
|
|
7432
|
+
balance: balance.ok ? balance.data : { error: balance.error },
|
|
7433
|
+
...joined ? { services: joined.services, count: joined.count } : {}
|
|
7434
|
+
});
|
|
6005
7435
|
}
|
|
6006
|
-
async function receipts(args,
|
|
7436
|
+
async function receipts(args, cfg, deps = {}) {
|
|
6007
7437
|
if (deps.auth) {
|
|
6008
7438
|
if (!deps.auth.listReceipts) {
|
|
6009
7439
|
return error(
|
|
@@ -6017,28 +7447,144 @@ async function receipts(args, _cfg, deps = {}) {
|
|
|
6017
7447
|
return closedError("RECEIPTS_UNAVAILABLE", "receipts could not be loaded");
|
|
6018
7448
|
}
|
|
6019
7449
|
}
|
|
6020
|
-
if (
|
|
6021
|
-
|
|
6022
|
-
|
|
6023
|
-
|
|
7450
|
+
if (profile(deps)) {
|
|
7451
|
+
return error(
|
|
7452
|
+
"receipts is available after suipay_login (version-2 key) or on remote OAuth (/mcp) sessions; the local V1 allowance profile has no settlement feed access"
|
|
7453
|
+
);
|
|
7454
|
+
}
|
|
7455
|
+
const resolved = await stdioPayerAndReader(cfg, deps);
|
|
7456
|
+
if (!resolved.ok) return resolved.result;
|
|
7457
|
+
const reader = (deps.createReader ?? createReader)({
|
|
7458
|
+
gatewayUrl: stdioGatewayUrl(cfg),
|
|
7459
|
+
context: {
|
|
7460
|
+
network: resolved.payer.deployment.network,
|
|
7461
|
+
packageId: resolved.payer.deployment.packageId,
|
|
7462
|
+
grantObjectId: resolved.payer.grant.grantObjectId,
|
|
7463
|
+
delegateAddress: resolved.payer.address
|
|
7464
|
+
},
|
|
7465
|
+
signPersonalMessage: async (message) => {
|
|
7466
|
+
const { signature } = await loadLocalKeypair().signPersonalMessage(message);
|
|
7467
|
+
return signature;
|
|
7468
|
+
},
|
|
7469
|
+
...deps.fetchImpl ? { fetchImpl: deps.fetchImpl } : {}
|
|
7470
|
+
});
|
|
7471
|
+
const listed = await reader.receipts();
|
|
7472
|
+
if (!listed.ok) {
|
|
7473
|
+
return closedError("RECEIPTS_UNAVAILABLE", "receipts could not be loaded");
|
|
7474
|
+
}
|
|
7475
|
+
const receiptsOut = listed.data.receipts;
|
|
7476
|
+
const filtered = args.challengeId && Array.isArray(receiptsOut) ? receiptsOut.filter((row) => {
|
|
7477
|
+
if (!row || typeof row !== "object") return false;
|
|
7478
|
+
const rec = row;
|
|
7479
|
+
return rec.challengeId === args.challengeId || rec.receipt?.challengeId === args.challengeId;
|
|
7480
|
+
}) : receiptsOut;
|
|
7481
|
+
return text({ count: Array.isArray(filtered) ? filtered.length : 0, receipts: filtered });
|
|
7482
|
+
}
|
|
7483
|
+
function parseLoginTarget(value) {
|
|
7484
|
+
if (value === void 0 || value === null || value === "") return void 0;
|
|
7485
|
+
if (typeof value !== "string") return { error: 'target must be "dev" or "local"' };
|
|
7486
|
+
const target = value.trim().toLowerCase();
|
|
7487
|
+
if (target === "dev" || target === "local") return target;
|
|
7488
|
+
return { error: 'target must be "dev" or "local"' };
|
|
6024
7489
|
}
|
|
6025
7490
|
async function suipayLogin(args, cfg, deps = {}) {
|
|
7491
|
+
const parsedTarget = parseLoginTarget(args.target);
|
|
7492
|
+
if (parsedTarget && typeof parsedTarget === "object" && "error" in parsedTarget) {
|
|
7493
|
+
return error({
|
|
7494
|
+
status: "failed",
|
|
7495
|
+
code: "INVALID_TARGET",
|
|
7496
|
+
detail: parsedTarget.error
|
|
7497
|
+
});
|
|
7498
|
+
}
|
|
7499
|
+
const target = parsedTarget;
|
|
6026
7500
|
const label = args.label?.trim() || cfg.label.trim();
|
|
6027
7501
|
const identity = await ensureLocalSigningKey(label ? { label } : {});
|
|
6028
|
-
const
|
|
6029
|
-
|
|
6030
|
-
const
|
|
6031
|
-
|
|
6032
|
-
|
|
6033
|
-
|
|
6034
|
-
|
|
7502
|
+
const local = loadLocalSigner();
|
|
7503
|
+
const stored = loadLocalSigningKeyRecord();
|
|
7504
|
+
const endpoints = target ? MCP_TARGET_PRESETS[target] : {
|
|
7505
|
+
consoleUrl: cfg.consoleUrl,
|
|
7506
|
+
gatewayUrl: stored?.gatewayUrl ?? cfg.gatewayUrl
|
|
7507
|
+
};
|
|
7508
|
+
if (local) {
|
|
7509
|
+
const live = await checkLiveDelegateGrant({
|
|
7510
|
+
gatewayUrl: endpoints.gatewayUrl,
|
|
7511
|
+
delegateAddress: local.address,
|
|
7512
|
+
signPersonalMessage: local.signPersonalMessage,
|
|
7513
|
+
...deps.fetchImpl ? { fetchImpl: deps.fetchImpl } : {}
|
|
7514
|
+
});
|
|
7515
|
+
if (live.kind === "ambiguous") {
|
|
7516
|
+
return error({
|
|
7517
|
+
status: "failed",
|
|
7518
|
+
code: "AMBIGUOUS_GRANT",
|
|
7519
|
+
detail: "multiple live grants name this key; revoke extras or pass a selector before paying"
|
|
7520
|
+
});
|
|
7521
|
+
}
|
|
7522
|
+
if (live.kind === "live") {
|
|
7523
|
+
const storedUrl = stored?.gatewayUrl;
|
|
7524
|
+
if (!storedUrl || storedUrl !== endpoints.gatewayUrl && (sameMcpGateway(storedUrl, endpoints.gatewayUrl) || target !== void 0)) {
|
|
7525
|
+
persistGatewayUrl(endpoints.gatewayUrl);
|
|
7526
|
+
}
|
|
7527
|
+
return text({
|
|
7528
|
+
status: "ok",
|
|
7529
|
+
reused: true,
|
|
7530
|
+
target: target ?? cfg.target,
|
|
7531
|
+
delegateAddress: identity.delegateAddress,
|
|
7532
|
+
gatewayUrl: loadLocalSigningKeyRecord()?.gatewayUrl ?? endpoints.gatewayUrl,
|
|
7533
|
+
grantObjectId: live.grantObjectId,
|
|
7534
|
+
message: "Existing spend-account grant is live for this key. No browser round-trip."
|
|
7535
|
+
});
|
|
7536
|
+
}
|
|
7537
|
+
if (live.kind === "unknown") {
|
|
7538
|
+
return error({
|
|
7539
|
+
status: "failed",
|
|
7540
|
+
code: "GRANT_STATUS_UNKNOWN",
|
|
7541
|
+
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)."
|
|
7542
|
+
});
|
|
7543
|
+
}
|
|
6035
7544
|
}
|
|
6036
|
-
|
|
6037
|
-
|
|
6038
|
-
|
|
6039
|
-
|
|
6040
|
-
|
|
7545
|
+
const started = await (deps.startStdioLogin ?? startStdioLogin)({
|
|
7546
|
+
consoleUrl: endpoints.consoleUrl,
|
|
7547
|
+
gatewayUrl: endpoints.gatewayUrl,
|
|
7548
|
+
identity,
|
|
7549
|
+
label,
|
|
7550
|
+
timeoutMs: LOGIN_BG_TIMEOUT_MS,
|
|
7551
|
+
openBrowser: false,
|
|
7552
|
+
onUrl: deps.onUrl
|
|
6041
7553
|
});
|
|
7554
|
+
const url = started.connectUrl;
|
|
7555
|
+
return text(
|
|
7556
|
+
[
|
|
7557
|
+
`## ACTION REQUIRED: User must click this URL to sign in`,
|
|
7558
|
+
``,
|
|
7559
|
+
`**Target:** ${target ?? cfg.target ?? "custom"} \xB7 ${endpoints.gatewayUrl}`,
|
|
7560
|
+
``,
|
|
7561
|
+
`**URL:** ${url}`,
|
|
7562
|
+
``,
|
|
7563
|
+
"```",
|
|
7564
|
+
url,
|
|
7565
|
+
"```",
|
|
7566
|
+
``,
|
|
7567
|
+
`[Click here to open SuiPay sign-in](${url})`,
|
|
7568
|
+
``,
|
|
7569
|
+
`**IMPORTANT for the assistant**: do NOT summarize or omit the URL above.`,
|
|
7570
|
+
`The user CANNOT proceed without seeing the exact URL. Surface it verbatim.`,
|
|
7571
|
+
``,
|
|
7572
|
+
`1. Open the URL in any browser`,
|
|
7573
|
+
`2. Connect the owner wallet and approve the spend-account grant (Access keys / policies)`,
|
|
7574
|
+
`3. Once Connected appears, retry the original request \u2014 credentials at \`~/.suipay/credentials.json\` are picked up on the next tool call`,
|
|
7575
|
+
``,
|
|
7576
|
+
`_The login link stays valid for 5 minutes. If it expires, call \`suipay_login\` again._`,
|
|
7577
|
+
``,
|
|
7578
|
+
JSON.stringify({
|
|
7579
|
+
status: "ok",
|
|
7580
|
+
target: target ?? cfg.target,
|
|
7581
|
+
delegateAddress: identity.delegateAddress,
|
|
7582
|
+
connectUrl: url,
|
|
7583
|
+
consoleUrl: endpoints.consoleUrl,
|
|
7584
|
+
gatewayUrl: endpoints.gatewayUrl
|
|
7585
|
+
})
|
|
7586
|
+
].join("\n")
|
|
7587
|
+
);
|
|
6042
7588
|
}
|
|
6043
7589
|
async function logout(args, _cfg, deps = {}) {
|
|
6044
7590
|
if (args.force) {
|
|
@@ -6064,7 +7610,7 @@ var TOOLS = [
|
|
|
6064
7610
|
},
|
|
6065
7611
|
{
|
|
6066
7612
|
name: "pay",
|
|
6067
|
-
description: "Pay a HTTP resource via delegate. On hosted /mcp the gateway PREPARES an unsigned transaction and returns it (prepare-only \u2014 it never holds the delegate key and cannot settle). Only a process holding the delegate key the grant names can complete settlement (e.g. local stdio MCP, or any self-hosted process with the matching key). Pass recipient and/or maxAmount to state who you intend to pay and what you will spend; both are checked before anything is signed or handed back. Local V1 allowance pay is retired.",
|
|
7613
|
+
description: "Pay a HTTP resource via delegate. On hosted /mcp the gateway PREPARES an unsigned transaction and returns it (prepare-only \u2014 it never holds the delegate key and cannot settle). Only a process holding the delegate key the grant names can complete settlement (e.g. local stdio MCP, or any self-hosted process with the matching key). Pass recipient and/or maxAmount to state who you intend to pay and what you will spend; both are checked before anything is signed or handed back. Local V1 allowance pay is retired. When this process settles a paid binary body, it writes the bytes under ~/.suipay/deliveries/ and names savedTo (absolute path) and savedToUrl (file://) first in the JSON. Hosted /mcp is prepare-only and does not write a buyer file. Always quote savedTo when present. If savedTo is null, quote saveError and do not call pay again. Do not assume the host rendered an image block. Never call pay again to display or save a delivery that already settled.",
|
|
6068
7614
|
inputSchema: {
|
|
6069
7615
|
type: "object",
|
|
6070
7616
|
properties: {
|
|
@@ -6092,7 +7638,7 @@ var TOOLS = [
|
|
|
6092
7638
|
},
|
|
6093
7639
|
{
|
|
6094
7640
|
name: "discover",
|
|
6095
|
-
description: "Search SuiPay gateway resources.",
|
|
7641
|
+
description: "Search SuiPay gateway resources. After login, each row includes name, path, url, catalog price, and whether the current session can cover one call at that price (descriptive only \u2014 pay still enforces the grant).",
|
|
6096
7642
|
inputSchema: {
|
|
6097
7643
|
type: "object",
|
|
6098
7644
|
properties: {
|
|
@@ -6118,11 +7664,16 @@ var TOOLS = [
|
|
|
6118
7664
|
},
|
|
6119
7665
|
{
|
|
6120
7666
|
name: "suipay_login",
|
|
6121
|
-
description:
|
|
7667
|
+
description: 'Mint or reuse the local delegate key (version-2, mode 0600) and return a clickable console URL immediately so the owner can bind a spend-account grant. Does not wait for the wallet. Default target is Railway suipay-dev; pass target="local" (or start the binary with --local) for the laptop stack. Stdio only; hosted /mcp never holds a spendable key.',
|
|
6122
7668
|
inputSchema: {
|
|
6123
7669
|
type: "object",
|
|
6124
7670
|
properties: {
|
|
6125
7671
|
label: { type: "string", description: "Label shown in SuiPay console." },
|
|
7672
|
+
target: {
|
|
7673
|
+
type: "string",
|
|
7674
|
+
enum: ["dev", "local"],
|
|
7675
|
+
description: 'Where to bind the grant. Default is Railway suipay-dev. Pass "local" for localhost:3000 / 127.0.0.1:4340.'
|
|
7676
|
+
},
|
|
6126
7677
|
gatewayResourceUrl: {
|
|
6127
7678
|
type: "string",
|
|
6128
7679
|
description: "Gateway resource URL whose 402 defines recipient, asset, and package."
|
|
@@ -6178,7 +7729,7 @@ function toolError(value) {
|
|
|
6178
7729
|
isError: true
|
|
6179
7730
|
};
|
|
6180
7731
|
}
|
|
6181
|
-
function
|
|
7732
|
+
function createServer2(cfg, auth, trace, options = {}) {
|
|
6182
7733
|
const resolveAgentHeld = options.resolveAgentHeld ?? resolveAgentHeldSettlement;
|
|
6183
7734
|
const server = new Server(
|
|
6184
7735
|
{ name: "suipay", version: "1.0.0" },
|
|
@@ -6203,7 +7754,7 @@ function createServer(cfg, auth, trace, options = {}) {
|
|
|
6203
7754
|
}
|
|
6204
7755
|
switch (name) {
|
|
6205
7756
|
case "access_context":
|
|
6206
|
-
return await accessContext({ auth });
|
|
7757
|
+
return await accessContext({ auth }, cfg);
|
|
6207
7758
|
case "pay": {
|
|
6208
7759
|
const agentHeld = auth?.spendAccountPay ? resolveAgentHeld(cfg, auth.spendAccountPay) : null;
|
|
6209
7760
|
return await pay(
|
|
@@ -6247,6 +7798,7 @@ function createServer(cfg, auth, trace, options = {}) {
|
|
|
6247
7798
|
return await suipayLogin(
|
|
6248
7799
|
{
|
|
6249
7800
|
label: typeof args.label === "string" ? args.label : void 0,
|
|
7801
|
+
target: typeof args.target === "string" ? args.target : void 0,
|
|
6250
7802
|
gatewayResourceUrl: typeof args.gatewayResourceUrl === "string" ? args.gatewayResourceUrl : void 0
|
|
6251
7803
|
},
|
|
6252
7804
|
cfg,
|
|
@@ -6745,7 +8297,7 @@ async function handleSuipayMcpHttpRequest(req, cfg, auth, trace, options) {
|
|
|
6745
8297
|
sessionIdGenerator: void 0,
|
|
6746
8298
|
enableJsonResponse: true
|
|
6747
8299
|
});
|
|
6748
|
-
const server =
|
|
8300
|
+
const server = createServer2(cfg, auth, hooks, options);
|
|
6749
8301
|
await server.connect(transport);
|
|
6750
8302
|
return transport.handleRequest(request);
|
|
6751
8303
|
}
|
|
@@ -6754,10 +8306,15 @@ async function handleSuipayMcpHttpRequest(req, cfg, auth, trace, options) {
|
|
|
6754
8306
|
|
|
6755
8307
|
export {
|
|
6756
8308
|
resolveGrantTarget,
|
|
8309
|
+
MCP_TARGET_PRESETS,
|
|
8310
|
+
parseMcpTargetFlag,
|
|
8311
|
+
resolveMcpTarget,
|
|
8312
|
+
sameMcpGateway,
|
|
6757
8313
|
loadMcpConfig,
|
|
6758
8314
|
loadPaymentProfile,
|
|
6759
8315
|
ensureLocalSigningKey,
|
|
6760
8316
|
loadLocalSigner,
|
|
8317
|
+
loadLocalSigningKeyRecord,
|
|
6761
8318
|
resetAgentHeldProcessLedger,
|
|
6762
8319
|
resolveAgentHeldSettlement,
|
|
6763
8320
|
settlePreparedMcpPayment,
|
|
@@ -6768,7 +8325,7 @@ export {
|
|
|
6768
8325
|
receipts,
|
|
6769
8326
|
PACKAGE_NAME,
|
|
6770
8327
|
TOOLS,
|
|
6771
|
-
createServer,
|
|
8328
|
+
createServer2 as createServer,
|
|
6772
8329
|
canonicalMcpRequestId,
|
|
6773
8330
|
inspectMcpEnvelope,
|
|
6774
8331
|
traceMcpHttpRequest,
|