@cmdoss/suipay-mcp 0.2.2-dev.2 → 0.2.2-dev.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -151,46 +151,6 @@ import { normalizeStructTag as normalizeStructTag2 } from "@mysten/sui/utils";
151
151
 
152
152
  // ../core/dist/assets/testnet.js
153
153
  import { normalizeStructTag } from "@mysten/sui/utils";
154
- var TESTNET_SUI_COIN_TYPE = "0x2::sui::SUI";
155
- var TESTNET_USDC_COIN_TYPE = "0xa1ec7fc00a6f40db9693ad1415d0c193ad3906494428cf252621037bd7117e29::usdc::USDC";
156
- var TESTNET_SUI_DECIMALS = 9;
157
- var TESTNET_USDC_DECIMALS = 6;
158
- var TESTNET_ASSETS = [
159
- {
160
- id: "sui",
161
- symbol: "SUI",
162
- coinType: TESTNET_SUI_COIN_TYPE,
163
- decimals: TESTNET_SUI_DECIMALS,
164
- network: "sui:testnet"
165
- },
166
- {
167
- id: "usdc",
168
- symbol: "USDC",
169
- coinType: TESTNET_USDC_COIN_TYPE,
170
- decimals: TESTNET_USDC_DECIMALS,
171
- network: "sui:testnet"
172
- }
173
- ];
174
- function supportedAssetByCoinType(coinType) {
175
- const normalized = tryNormalizeStructTag(coinType);
176
- if (!normalized)
177
- return null;
178
- for (const asset of TESTNET_ASSETS) {
179
- if (tryNormalizeStructTag(asset.coinType) === normalized)
180
- return asset;
181
- }
182
- return null;
183
- }
184
- function tryNormalizeStructTag(coinType) {
185
- const trimmed = coinType.trim();
186
- if (!trimmed)
187
- return null;
188
- try {
189
- return normalizeStructTag(trimmed);
190
- } catch {
191
- return null;
192
- }
193
- }
194
154
 
195
155
  // ../core/dist/service-state/index.js
196
156
  var STATE_DIMENSIONS = Object.freeze([
@@ -411,101 +371,6 @@ var SERVICE_STATE_TRANSITIONS = Object.freeze([
411
371
  ]);
412
372
 
413
373
  // ../core/dist/policy/target.js
414
- var AUTHORIZATION_PROTOCOL_VERSION = "suipay-target-v1";
415
- function normalizeHexAddress(address) {
416
- const raw = address.trim().toLowerCase();
417
- if (!raw.startsWith("0x")) {
418
- throw new Error("Address must be a 0x-prefixed Sui address.");
419
- }
420
- const hex = raw.slice(2);
421
- if (!/^[0-9a-f]+$/.test(hex) || hex.length === 0 || hex.length > 64) {
422
- throw new Error("Invalid Sui address.");
423
- }
424
- if (/^0+$/.test(hex)) {
425
- throw new Error("Recipient must be a non-zero address.");
426
- }
427
- return `0x${hex.padStart(64, "0")}`;
428
- }
429
- function normalizePath(path) {
430
- const p = path.trim();
431
- if (!p.startsWith("/")) {
432
- throw new Error("Path must be an absolute path starting with /.");
433
- }
434
- if (p.length > 1 && p.endsWith("/"))
435
- return p.replace(/\/+$/, "");
436
- return p;
437
- }
438
- function normalizeMethod(method) {
439
- const m = method.trim().toUpperCase();
440
- if (!m)
441
- throw new Error("HTTP method is required.");
442
- return m;
443
- }
444
- function normalizeNetwork(network) {
445
- const n = network.trim().toLowerCase();
446
- if (n !== "sui:testnet") {
447
- throw new Error(`Unsupported network: ${network}`);
448
- }
449
- return n;
450
- }
451
- function normalizeAsset(asset) {
452
- const trimmed = asset.trim();
453
- let normalized;
454
- try {
455
- normalized = normalizeStructTag2(trimmed);
456
- } catch {
457
- throw new Error(`Invalid coin type: ${asset}`);
458
- }
459
- if (!supportedAssetByCoinType(normalized)) {
460
- throw new Error(`Unsupported asset: ${asset}`);
461
- }
462
- return normalized;
463
- }
464
- function normalizeId(id, label) {
465
- const v = id.trim();
466
- if (!v)
467
- throw new Error(`${label} is required.`);
468
- if (/[\n\r\0]/.test(v))
469
- throw new Error(`${label} contains control characters.`);
470
- return v;
471
- }
472
- function normalizeEpoch(epoch) {
473
- if (!Number.isInteger(epoch) || epoch < 1) {
474
- throw new Error("authorizationEpoch must be a positive integer.");
475
- }
476
- return epoch;
477
- }
478
- function canonicalizeTargetFields(input) {
479
- return {
480
- network: normalizeNetwork(input.network),
481
- providerId: normalizeHexAddress(input.providerId),
482
- serviceId: normalizeId(input.serviceId, "serviceId"),
483
- endpointId: normalizeId(input.endpointId, "endpointId"),
484
- method: normalizeMethod(input.method),
485
- path: normalizePath(input.path),
486
- asset: normalizeAsset(input.asset),
487
- recipient: normalizeHexAddress(input.recipient),
488
- authorizationEpoch: normalizeEpoch(input.authorizationEpoch)
489
- };
490
- }
491
- function authorizationFingerprint(input) {
492
- const c = canonicalizeTargetFields(input);
493
- return [
494
- AUTHORIZATION_PROTOCOL_VERSION,
495
- c.network,
496
- c.providerId,
497
- c.serviceId,
498
- c.endpointId,
499
- c.method,
500
- c.path,
501
- c.asset,
502
- c.recipient,
503
- String(c.authorizationEpoch)
504
- ].join("\n");
505
- }
506
- function targetHashFromFingerprint(fingerprint) {
507
- return createHash("sha256").update(fingerprint, "utf8").digest("hex");
508
- }
509
374
  function targetHashBytes(hexHash) {
510
375
  if (!/^[0-9a-f]{64}$/i.test(hexHash)) {
511
376
  throw new Error("targetHash must be 32-byte hex.");
@@ -4467,10 +4332,8 @@ import {
4467
4332
  fsyncSync,
4468
4333
  openSync,
4469
4334
  closeSync,
4470
- writeSync,
4471
- linkSync
4335
+ writeSync
4472
4336
  } from "fs";
4473
- import { decodeSuiPrivateKey } from "@mysten/sui/cryptography";
4474
4337
  import { Ed25519Keypair as Ed25519Keypair2 } from "@mysten/sui/keypairs/ed25519";
4475
4338
  import { fromBase64 as fromBase644 } from "@mysten/sui/utils";
4476
4339
  import { bytesToHex as bytesToHex4 } from "@noble/hashes/utils.js";
@@ -4484,16 +4347,6 @@ function mcpCredentialsPath() {
4484
4347
  const home = process.env.HOME ?? process.env.USERPROFILE ?? homedir();
4485
4348
  return join(home, ".suipay", "credentials.json");
4486
4349
  }
4487
- var MCP_TARGET_PRESETS = {
4488
- dev: {
4489
- consoleUrl: "https://suipay-dev.up.railway.app",
4490
- gatewayUrl: "https://gateway-dev-ba84.up.railway.app"
4491
- },
4492
- local: {
4493
- consoleUrl: "http://localhost:3000",
4494
- gatewayUrl: "http://127.0.0.1:4340"
4495
- }
4496
- };
4497
4350
  var GRPC_URLS = {
4498
4351
  mainnet: "https://fullnode.mainnet.sui.io:443",
4499
4352
  testnet: "https://fullnode.testnet.sui.io:443",
@@ -4537,56 +4390,11 @@ function resolveActiveMovePackageId(env = process.env) {
4537
4390
  }
4538
4391
  return configured[0]?.value;
4539
4392
  }
4540
- function 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 = []) {
4393
+ function loadMcpConfig(env = process.env) {
4582
4394
  const network = env.SUIPAY_NETWORK ?? "testnet";
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;
4395
+ const gatewayUrl = env.SUIPAY_GATEWAY_URL ?? "http://localhost:4340";
4587
4396
  return {
4588
- target,
4589
- consoleUrl,
4397
+ consoleUrl: env.SUIPAY_CONSOLE_URL ?? "http://localhost:3000",
4590
4398
  gatewayUrl,
4591
4399
  rpcUrl: env.SUIPAY_RPC_URL ?? GRPC_URLS[network],
4592
4400
  network,
@@ -4635,23 +4443,10 @@ function isValid(obj) {
4635
4443
  const c = obj;
4636
4444
  return typeof c.delegatePrivateKey === "string" && HEX64.test(c.delegatePrivateKey) && typeof c.delegatePublicKeyHex === "string" && HEX64.test(c.delegatePublicKeyHex) && typeof c.delegateAddress === "string" && ADDR.test(c.delegateAddress) && typeof c.ownerAddress === "string" && ADDR.test(c.ownerAddress) && typeof c.allowanceId === "string" && ADDR.test(c.allowanceId) && typeof c.packageId === "string" && ADDR.test(c.packageId) && typeof c.recipient === "string" && ADDR.test(c.recipient) && typeof c.coinType === "string" && c.coinType.length > 0 && typeof c.gatewayUrl === "string" && typeof c.network === "string" && typeof c.maxPerPayment === "string" && /^\d+$/.test(c.maxPerPayment) && typeof c.createdAt === "string" && c.version === 1;
4637
4445
  }
4638
- function isHttpUrl(value) {
4639
- if (typeof value !== "string" || !value.trim()) return false;
4640
- try {
4641
- const parsed = new URL(value);
4642
- return parsed.protocol === "http:" || parsed.protocol === "https:";
4643
- } catch {
4644
- return false;
4645
- }
4646
- }
4647
4446
  function isLocalSigningKeyRecord(obj) {
4648
4447
  if (!obj || typeof obj !== "object") return false;
4649
4448
  const c = obj;
4650
- 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;
4449
+ return c.version === 2 && typeof c.delegatePrivateKey === "string" && HEX64.test(c.delegatePrivateKey) && typeof c.delegatePublicKeyHex === "string" && HEX64.test(c.delegatePublicKeyHex) && typeof c.delegateAddress === "string" && ADDR.test(c.delegateAddress) && typeof c.createdAt === "string";
4655
4450
  }
4656
4451
  function readCredentialsJson() {
4657
4452
  const path = credsPath();
@@ -4687,19 +4482,15 @@ function loadCreds() {
4687
4482
  }
4688
4483
  return parsed;
4689
4484
  }
4690
- function fsyncDir(dir) {
4485
+ function writeCredentialsFile(payload) {
4486
+ const dir = credsDir();
4487
+ mkdirSync(dir, { recursive: true, mode: 448 });
4691
4488
  try {
4692
- const fd = openSync(dir, "r");
4693
- try {
4694
- fsyncSync(fd);
4695
- } finally {
4696
- closeSync(fd);
4697
- }
4489
+ chmodSync(dir, 448);
4698
4490
  } catch {
4699
4491
  }
4700
- }
4701
- function writeTmpCredentials(dir, payload) {
4702
- const tmp = join2(dir, `.credentials.${process.pid}.${randomSuffix()}.tmp`);
4492
+ const path = credsPath();
4493
+ const tmp = join2(dir, `.credentials.${process.pid}.tmp`);
4703
4494
  const fd = openSync(tmp, "w", 384);
4704
4495
  try {
4705
4496
  writeSync(fd, JSON.stringify(payload, null, 2));
@@ -4707,23 +4498,7 @@ function writeTmpCredentials(dir, payload) {
4707
4498
  } finally {
4708
4499
  closeSync(fd);
4709
4500
  }
4710
- return tmp;
4711
- }
4712
- function randomSuffix() {
4713
- return Math.random().toString(16).slice(2, 10);
4714
- }
4715
- function writeCredentialsFile(payload) {
4716
- const dir = credsDir();
4717
- mkdirSync(dir, { recursive: true, mode: 448 });
4718
- try {
4719
- chmodSync(dir, 448);
4720
- } catch {
4721
- }
4722
- const path = credsPath();
4723
- const tmp = writeTmpCredentials(dir, payload);
4724
- fsyncDir(dir);
4725
4501
  renameSync(tmp, path);
4726
- fsyncDir(dir);
4727
4502
  try {
4728
4503
  chmodSync(path, 384);
4729
4504
  } catch {
@@ -4737,23 +4512,19 @@ function createCredentialsFileExclusive(payload) {
4737
4512
  } catch {
4738
4513
  }
4739
4514
  const path = credsPath();
4740
- const tmp = writeTmpCredentials(dir, payload);
4741
- fsyncDir(dir);
4515
+ let fd;
4742
4516
  try {
4743
- linkSync(tmp, path);
4517
+ fd = openSync(path, "wx", 384);
4744
4518
  } catch (error2) {
4745
- try {
4746
- unlinkSync(tmp);
4747
- } catch {
4748
- }
4749
4519
  if (error2.code === "EEXIST") return false;
4750
4520
  throw error2;
4751
4521
  }
4752
4522
  try {
4753
- unlinkSync(tmp);
4754
- } catch {
4523
+ writeSync(fd, JSON.stringify(payload, null, 2));
4524
+ fsyncSync(fd);
4525
+ } finally {
4526
+ closeSync(fd);
4755
4527
  }
4756
- fsyncDir(dir);
4757
4528
  try {
4758
4529
  chmodSync(path, 384);
4759
4530
  } catch {
@@ -4842,90 +4613,12 @@ function rewriteV1ToLocalSigningKey(creds, opts = {}) {
4842
4613
  delegatePublicKeyHex: creds.delegatePublicKeyHex,
4843
4614
  delegateAddress: creds.delegateAddress,
4844
4615
  createdAt: creds.createdAt,
4845
- ...isHttpUrl(creds.gatewayUrl) ? { gatewayUrl: creds.gatewayUrl } : {},
4846
4616
  ...opts.label?.trim() ? { label: opts.label.trim() } : creds.label?.trim() ? { label: creds.label.trim() } : {}
4847
4617
  };
4848
4618
  writeCredentialsFile(record);
4849
4619
  return identity;
4850
4620
  }
4851
- function keypairFromOperatorSecret(raw) {
4852
- const key = raw.trim();
4853
- if (!key) throw new Error("SUIPAY_MCP_KEY is set but empty");
4854
- try {
4855
- return /^(0x)?[0-9a-fA-F]{64}$/.test(key) ? Ed25519Keypair2.fromSecretKey(
4856
- Uint8Array.from(Buffer.from(key.replace(/^0x/i, ""), "hex"))
4857
- ) : Ed25519Keypair2.fromSecretKey(key);
4858
- } catch {
4859
- throw new Error(
4860
- "SUIPAY_MCP_KEY is malformed (expected 64-hex or suiprivkey1\u2026 Ed25519 secret)"
4861
- );
4862
- }
4863
- }
4864
- function seedHexFromKeypair(kp, operatorSecret) {
4865
- const hex = operatorSecret.trim().replace(/^0x/i, "");
4866
- if (/^[0-9a-fA-F]{64}$/.test(hex)) return hex.toLowerCase();
4867
- try {
4868
- return bytesToHex4(decodeSuiPrivateKey(kp.getSecretKey()).secretKey);
4869
- } catch {
4870
- throw new Error(
4871
- "SUIPAY_MCP_KEY is malformed (expected 64-hex or suiprivkey1\u2026 Ed25519 secret)"
4872
- );
4873
- }
4874
- }
4875
- function recordFromKeypair(kp, opts = {}, existing, operatorSecret) {
4876
- const seed = seedHexFromKeypair(kp, operatorSecret);
4877
- const publicKeyHex = bytesToHex4(kp.getPublicKey().toRawBytes());
4878
- const delegateAddress = kp.getPublicKey().toSuiAddress();
4879
- const label = opts.label?.trim() || existing?.label?.trim();
4880
- return {
4881
- version: 2,
4882
- delegatePrivateKey: seed,
4883
- delegatePublicKeyHex: publicKeyHex,
4884
- delegateAddress,
4885
- createdAt: existing?.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
4886
- ...existing && isHttpUrl(existing.gatewayUrl ?? "") ? { gatewayUrl: existing.gatewayUrl } : {},
4887
- ...label ? { label } : {}
4888
- };
4889
- }
4890
- var ENV_KEY_MISMATCH = "SUIPAY_MCP_KEY does not match the stored delegate key - refusing to overwrite";
4891
- function existingRecordForEnv(parsed) {
4892
- if (parsed === null) return null;
4893
- if (isLocalSigningKeyRecord(parsed) || isValid(parsed)) return parsed;
4894
- throw new Error(
4895
- `credentials file at ${credsPath()} failed schema validation - refusing to continue`
4896
- );
4897
- }
4898
4621
  async function ensureLocalSigningKey(opts = {}) {
4899
- const envKey = process.env.SUIPAY_MCP_KEY?.trim();
4900
- if (envKey) {
4901
- const kp2 = keypairFromOperatorSecret(envKey);
4902
- const parsed2 = readCredentialsJson();
4903
- const existing = existingRecordForEnv(parsed2);
4904
- const asV2 = existing ? isLocalSigningKeyRecord(existing) ? existing : {
4905
- version: 2,
4906
- delegatePrivateKey: existing.delegatePrivateKey,
4907
- delegatePublicKeyHex: existing.delegatePublicKeyHex,
4908
- delegateAddress: existing.delegateAddress,
4909
- createdAt: existing.createdAt,
4910
- ...isHttpUrl(existing.gatewayUrl) ? { gatewayUrl: existing.gatewayUrl } : {},
4911
- ...existing.label?.trim() ? { label: existing.label.trim() } : {}
4912
- } : null;
4913
- const record2 = recordFromKeypair(kp2, opts, asV2, envKey);
4914
- if (existing) {
4915
- if (existing.delegateAddress !== record2.delegateAddress || existing.delegatePrivateKey !== record2.delegatePrivateKey) {
4916
- throw new Error(ENV_KEY_MISMATCH);
4917
- }
4918
- if (!isLocalSigningKeyRecord(existing)) {
4919
- writeCredentialsFile(record2);
4920
- }
4921
- } else {
4922
- writeCredentialsFile(record2);
4923
- }
4924
- return {
4925
- delegateAddress: record2.delegateAddress,
4926
- publicKeyHex: record2.delegatePublicKeyHex
4927
- };
4928
- }
4929
4622
  const parsed = readCredentialsJson();
4930
4623
  if (parsed !== null) {
4931
4624
  if (isLocalSigningKeyRecord(parsed)) {
@@ -4995,67 +4688,6 @@ function loadLocalSigner() {
4995
4688
  }
4996
4689
  };
4997
4690
  }
4998
- function loadLocalSigningKeyRecord() {
4999
- const parsed = readCredentialsJson();
5000
- if (parsed === null) return null;
5001
- if (isLocalSigningKeyRecord(parsed)) {
5002
- identityFromStored(
5003
- parsed.delegatePrivateKey,
5004
- parsed.delegatePublicKeyHex,
5005
- parsed.delegateAddress
5006
- );
5007
- return parsed;
5008
- }
5009
- if (isValid(parsed)) return null;
5010
- throw new Error(
5011
- `credentials file at ${credsPath()} failed schema validation - refusing to continue`
5012
- );
5013
- }
5014
- function loadLocalKeypair() {
5015
- const parsed = readCredentialsJson();
5016
- if (parsed === null) return null;
5017
- if (!isLocalSigningKeyRecord(parsed) && !isValid(parsed)) {
5018
- throw new Error(
5019
- `credentials file at ${credsPath()} failed schema validation - refusing to continue`
5020
- );
5021
- }
5022
- identityFromStored(
5023
- parsed.delegatePrivateKey,
5024
- parsed.delegatePublicKeyHex,
5025
- parsed.delegateAddress
5026
- );
5027
- return Ed25519Keypair2.fromSecretKey(
5028
- Uint8Array.from(Buffer.from(parsed.delegatePrivateKey, "hex"))
5029
- );
5030
- }
5031
- function persistGatewayUrl(gatewayUrl) {
5032
- if (!isHttpUrl(gatewayUrl)) {
5033
- throw new Error("gatewayUrl must be an http(s) origin");
5034
- }
5035
- const parsed = readCredentialsJson();
5036
- if (parsed === null) {
5037
- throw new Error(
5038
- `credentials file at ${credsPath()} is missing - refusing to persist gatewayUrl`
5039
- );
5040
- }
5041
- if (isLocalSigningKeyRecord(parsed)) {
5042
- identityFromStored(
5043
- parsed.delegatePrivateKey,
5044
- parsed.delegatePublicKeyHex,
5045
- parsed.delegateAddress
5046
- );
5047
- writeCredentialsFile({ ...parsed, gatewayUrl });
5048
- return;
5049
- }
5050
- if (isValid(parsed)) {
5051
- rewriteV1ToLocalSigningKey(parsed);
5052
- persistGatewayUrl(gatewayUrl);
5053
- return;
5054
- }
5055
- throw new Error(
5056
- `credentials file at ${credsPath()} failed schema validation - refusing to continue`
5057
- );
5058
- }
5059
4691
 
5060
4692
  // src/agent-held.ts
5061
4693
  var processLedger = null;
@@ -5083,6 +4715,38 @@ function offerSecret(env) {
5083
4715
  const secret = env.SUIPAY_SECRET?.trim();
5084
4716
  return secret ? secret : null;
5085
4717
  }
4718
+ function settlementFromLocalSigner(cfg, local, packageId, env = process.env) {
4719
+ const chain = createSpendAccountChain({
4720
+ packageId,
4721
+ network: cfg.network,
4722
+ url: cfg.rpcUrl
4723
+ });
4724
+ const finality = createSpendAccountFinalityReader({ client: chain.client });
4725
+ const getObjectJson = async (objectId) => {
4726
+ const { object } = await chain.client.core.getObject({
4727
+ objectId,
4728
+ include: { json: true }
4729
+ });
4730
+ return objectWithOwner(object);
4731
+ };
4732
+ const secret = offerSecret(env);
4733
+ return {
4734
+ signer: {
4735
+ address: local.address,
4736
+ signTransaction: local.signTransaction,
4737
+ signPersonalMessage: local.signPersonalMessage
4738
+ },
4739
+ getObjectJson,
4740
+ payerDeps: {
4741
+ ...secret ? { offerSecret: secret } : {},
4742
+ paymentLedger: sharedProcessLedger(),
4743
+ sponsor: { sponsorUrl: cfg.gatewayUrl },
4744
+ gasMode: "sponsored",
4745
+ resolveSharedObjectVersions: (ids) => resolveSharedObjectVersionsFromGetObject(getObjectJson, ids),
4746
+ getFinalizedTx: (digest) => finality.getFinalizedTx(digest)
4747
+ }
4748
+ };
4749
+ }
5086
4750
  function resolveAgentHeldSettlement(cfg, spendAccountPay, env = process.env) {
5087
4751
  const secret = offerSecret(env);
5088
4752
  if (!secret) return null;
@@ -5176,311 +4840,52 @@ function toolAllowedByScope(toolName, grantedScope) {
5176
4840
  }
5177
4841
 
5178
4842
  // src/tools.ts
5179
- import { isValidSuiAddress, normalizeSuiAddress as normalizeSuiAddress12 } from "@mysten/sui/utils";
4843
+ import { isValidSuiAddress, normalizeSuiAddress as normalizeSuiAddress10 } from "@mysten/sui/utils";
5180
4844
 
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
- };
5205
- }
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);
4845
+ // ../sdk/dist/agent-sdk/manifest.js
4846
+ var MANIFEST_ERROR_CODES = {
4847
+ MALFORMED_MANIFEST: "MALFORMED_MANIFEST",
4848
+ MISSING_NETWORK: "MISSING_NETWORK",
4849
+ MISSING_PACKAGE: "MISSING_PACKAGE",
4850
+ MISSING_DELEGATE: "MISSING_DELEGATE",
4851
+ NO_GRANTS: "NO_GRANTS",
4852
+ MISSING_COIN_TYPE: "MISSING_COIN_TYPE",
4853
+ MISSING_POOL_OBJECT: "MISSING_POOL_OBJECT",
4854
+ MISSING_GRANT_OBJECT: "MISSING_GRANT_OBJECT",
4855
+ MISSING_POLICY_BINDING: "MISSING_POLICY_BINDING",
4856
+ INVALID_ACCESS_MODE: "INVALID_ACCESS_MODE",
4857
+ GRANT_NOT_FOUND: "GRANT_NOT_FOUND",
4858
+ AMBIGUOUS_GRANT: "AMBIGUOUS_GRANT",
4859
+ /**
4860
+ * The chain does not agree that this grant belongs to this delegate. The
4861
+ * gateway said it did; an unsigned `getObject` says otherwise, and the chain
4862
+ * wins (ADR-0016 §3).
4863
+ */
4864
+ GRANT_NOT_VERIFIED: "GRANT_NOT_VERIFIED",
4865
+ /**
4866
+ * The on-chain check could not run to a verdict — the fullnode read was
4867
+ * unreachable or returned nothing. "Could not verify" is not "verified and it
4868
+ * failed": both refuse, but only one is a security event.
4869
+ */
4870
+ GRANT_VERIFY_UNAVAILABLE: "GRANT_VERIFY_UNAVAILABLE"
4871
+ };
4872
+ function fail(code, detail) {
4873
+ return { ok: false, error: { code, detail } };
4874
+ }
4875
+ var OBJECT_ID_RE = /^0x[0-9a-fA-F]{1,64}$/;
4876
+ var HEX32_RE = /^(0x)?[0-9a-fA-F]{64}$/;
4877
+ var U64_MAX = (1n << 64n) - 1n;
4878
+ function isNonEmptyString(v) {
4879
+ return typeof v === "string" && v.trim().length > 0;
4880
+ }
4881
+ function isObjectId(v) {
4882
+ return typeof v === "string" && OBJECT_ID_RE.test(v.trim());
4883
+ }
4884
+ function parseU64PolicyId(onChain) {
4885
+ if (typeof onChain === "number") {
4886
+ if (!Number.isSafeInteger(onChain) || onChain < 0)
4887
+ return null;
4888
+ return String(onChain);
5484
4889
  }
5485
4890
  if (typeof onChain === "string" && /^\d+$/.test(onChain.trim())) {
5486
4891
  const s = onChain.trim();
@@ -5867,489 +5272,71 @@ async function resolvePayerSession(options) {
5867
5272
  });
5868
5273
  }
5869
5274
 
5870
- // ../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()));
5886
-
5887
- // ../sdk/dist/agent-sdk/smoke.js
5888
- function mergeObjectEnvelope(object) {
5889
- if (!object || typeof object.json !== "object" || object.json === null) {
5890
- return null;
5891
- }
5892
- return { ...object.json, owner: object.owner };
5893
- }
5894
- var SECRET_KEY_SET = new Set(FORBIDDEN_SECRET_KEYS.map((k) => k.toLowerCase()));
5275
+ // src/tools.ts
5276
+ import open from "open";
5895
5277
 
5896
- // ../sdk/dist/agent-sdk/delegate-agent.js
5897
- async function verifyGrantOnChain(input) {
5898
- let state;
5899
- try {
5900
- state = await readPublicGrantState(input.getObjectJson, input.grantObjectId);
5901
- } catch {
5902
- return {
5903
- ok: false,
5904
- error: {
5905
- code: MANIFEST_ERROR_CODES.GRANT_VERIFY_UNAVAILABLE,
5906
- detail: `the on-chain read of grant ${input.grantObjectId} did not complete, so this session was not verified and no payment was prepared`
5907
- }
5908
- };
5909
- }
5910
- if (!state) {
5911
- return {
5912
- ok: false,
5913
- error: {
5914
- code: MANIFEST_ERROR_CODES.GRANT_VERIFY_UNAVAILABLE,
5915
- detail: `grant ${input.grantObjectId} returned no readable on-chain state`
5916
- }
5917
- };
5918
- }
5919
- const notVerified = (detail) => ({
5920
- ok: false,
5921
- error: { code: MANIFEST_ERROR_CODES.GRANT_NOT_VERIFIED, detail }
5922
- });
5923
- if (!state.delegateAddress || normalizeSuiAddress10(state.delegateAddress) !== normalizeSuiAddress10(input.delegateAddress)) {
5924
- return notVerified(`grant ${input.grantObjectId} does not name this delegate on chain`);
5925
- }
5926
- if (!state.poolObjectId || normalizeSuiAddress10(state.poolObjectId) !== normalizeSuiAddress10(input.poolObjectId)) {
5927
- return notVerified(`grant ${input.grantObjectId} draws on a different pool than the one resolved`);
5928
- }
5929
- if (state.revoked === true) {
5930
- return notVerified(`grant ${input.grantObjectId} is revoked on chain`);
5931
- }
5932
- if (state.paused === true) {
5933
- return notVerified(`grant ${input.grantObjectId} is paused on chain`);
5934
- }
5935
- return { ok: true, value: null };
5936
- }
5937
- async function createPayer(options) {
5938
- const keypair = options.keypair;
5939
- const address = keypair.getPublicKey().toSuiAddress();
5940
- const resolved = await resolvePayerSession({
5941
- gatewayUrl: options.gatewayUrl,
5942
- delegateAddress: address,
5943
- // The bootstrap signature and the payment signature come from the same key.
5944
- // A caller never wires a second signer for either this or the sponsor personal-message.
5945
- signPersonalMessage: async (message) => {
5946
- const { signature } = await keypair.signPersonalMessage(message);
5947
- return signature;
5948
- },
5949
- signer: keypair,
5950
- ...options.select ? { select: options.select } : {},
5951
- ...options.deployment ? { deployment: options.deployment } : {},
5952
- ...options.fetchImpl ? { fetchImpl: options.fetchImpl } : {}
5953
- });
5954
- if (!resolved.ok)
5955
- return resolved;
5956
- const { session, binding } = resolved.value;
5957
- const deployment = {
5958
- network: session.network,
5959
- packageId: session.packageId
5960
- };
5961
- let network;
5962
- try {
5963
- network = toGrpcNetwork(deployment.network);
5964
- } catch {
5965
- return {
5966
- ok: false,
5967
- error: {
5968
- code: MANIFEST_ERROR_CODES.MISSING_NETWORK,
5969
- detail: `the gateway reports network "${deployment.network}", which has no Sui fullnode this SDK can read; pass fullnodeUrl to name one`
5970
- }
5971
- };
5972
- }
5973
- let chain;
5278
+ // src/settle.ts
5279
+ async function settlePreparedMcpPayment(args) {
5974
5280
  try {
5975
- chain = createSpendAccountChain({
5976
- packageId: deployment.packageId,
5977
- network,
5978
- ...options.fullnodeUrl ? { url: options.fullnodeUrl } : {},
5979
- ...options.client ? { client: options.client } : {}
5980
- });
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 }
5281
+ await verifyPreparedSettlementKind({
5282
+ kindBytes: args.signingRequest.unsignedKindBytes,
5283
+ expected: {
5284
+ packageId: args.signingRequest.packageId,
5285
+ delegateAddress: args.expected.delegateAddress,
5286
+ amount: args.expected.amount,
5287
+ recipient: args.expected.recipient,
5288
+ accessMode: args.signingRequest.submit.moveCallTarget.endsWith(
5289
+ "settle_open_recipient_payment"
5290
+ ) ? "open" : "scoped"
5291
+ },
5292
+ getObjectJson: args.getObjectJson
5995
5293
  });
5996
- 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);
5294
+ } catch (err) {
5295
+ if (err instanceof PreparedVerificationUnavailableError) {
6029
5296
  return {
6030
5297
  status: "failed",
6031
5298
  paid: false,
6032
- code: DELEGATED_PAY_ERROR_CODES.UNSUPPORTED_REQUEST,
6033
- detail: "request cannot be paid for"
5299
+ code: DELEGATED_PAY_ERROR_CODES.PREPARED_VERIFY_UNAVAILABLE,
5300
+ detail: err.message
6034
5301
  };
6035
5302
  }
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
- });
6045
- }
6046
- async function verifySettlement(prepared, offer) {
6047
- return verifyPreparedPayment({
6048
- prepared,
6049
- offer,
6050
- delegateAddress: address,
6051
- expectedGrantObjectId: binding.grantObjectId,
6052
- expectedPoolObjectId: binding.poolObjectId
6053
- });
6054
- }
6055
- return {
6056
- ok: true,
6057
- value: { address, deployment, grant: binding, session, pay: pay2, verifySettlement }
6058
- };
6059
- }
6060
-
6061
- // src/stdio-login.ts
6062
- import { createServer } from "http";
6063
- import { randomBytes as randomBytes3, timingSafeEqual as timingSafeEqual2 } from "crypto";
6064
- import open from "open";
6065
- var HEX642 = /^[0-9a-f]{64}$/;
6066
- var PUBHEX = /^[0-9a-fA-F]{64}$/;
6067
- function isPreflight(obj) {
6068
- if (!obj || typeof obj !== "object") return false;
6069
- const o = obj;
6070
- return typeof o.state === "string" && HEX642.test(o.state) && typeof o.publicKey === "string" && PUBHEX.test(o.publicKey) && typeof o.delegateAddress === "string" && /^0x[0-9a-fA-F]{64}$/.test(o.delegateAddress) && typeof o.gateway === "string" && o.gateway.length > 0;
6071
- }
6072
- function isCallback(obj) {
6073
- if (!obj || typeof obj !== "object") return false;
6074
- const o = obj;
6075
- return typeof o.state === "string" && HEX642.test(o.state) && typeof o.gatewayUrl === "string" && o.gatewayUrl.length > 0;
6076
- }
6077
- function stateEquals(a, b) {
6078
- if (a.length !== b.length) return false;
6079
- try {
6080
- return timingSafeEqual2(Buffer.from(a, "hex"), Buffer.from(b, "hex"));
6081
- } catch {
6082
- return false;
6083
- }
6084
- }
6085
- function normalizeOrigin(url) {
6086
- try {
6087
- return new URL(url).origin;
6088
- } catch {
6089
- return url.replace(/\/+$/, "");
6090
- }
6091
- }
6092
- function loopbackAlternateOrigin(origin) {
6093
- try {
6094
- const url = new URL(origin);
6095
- if (url.protocol !== "http:" && url.protocol !== "https:") return null;
6096
- if (url.hostname === "localhost") {
6097
- url.hostname = "127.0.0.1";
6098
- return url.origin;
6099
- }
6100
- if (url.hostname === "127.0.0.1") {
6101
- url.hostname = "localhost";
6102
- return url.origin;
6103
- }
6104
- return null;
6105
- } catch {
6106
- return null;
6107
- }
6108
- }
6109
- function normalizeUrl(url) {
6110
- return url.replace(/\/+$/, "");
6111
- }
6112
- function readBody2(req, maxBytes = 16 * 1024) {
6113
- return new Promise((resolve, reject2) => {
6114
- const chunks = [];
6115
- let len = 0;
6116
- req.on("data", (c) => {
6117
- len += c.length;
6118
- if (len > maxBytes) {
6119
- reject2(new Error("body too large"));
6120
- req.destroy();
6121
- return;
6122
- }
6123
- chunks.push(c);
6124
- });
6125
- req.on("end", () => resolve(Buffer.concat(chunks).toString("utf8")));
6126
- req.on("error", reject2);
6127
- });
6128
- }
6129
- var SECURITY_HEADERS = {
6130
- "cache-control": "no-store",
6131
- "x-content-type-options": "nosniff",
6132
- "referrer-policy": "no-referrer",
6133
- "content-security-policy": "default-src 'none'"
6134
- };
6135
- var SUCCESS_HTML = `<!doctype html><meta charset=utf-8><title>SuiPay MCP - Connected</title><body style="font:16px system-ui;max-width:480px;margin:80px auto;padding:0 24px"><h1>\u2713 SuiPay MCP connected</h1><p>Credentials saved to <code>~/.suipay/credentials.json</code>. You can close this tab \u2014 your MCP client will pick up the new credentials automatically.</p>`;
6136
- var failHtml = (m) => `<!doctype html><meta charset=utf-8><title>SuiPay MCP - Failed</title><body style="font:16px system-ui;max-width:480px;margin:80px auto;padding:0 24px"><h1>\xD7 Login failed</h1><p>${m}</p>`;
6137
- async function startStdioLogin(opts) {
6138
- const timeoutMs = opts.timeoutMs ?? 5 * 6e4;
6139
- const openBrowser = opts.openBrowser ?? false;
6140
- const label = opts.label ?? "SuiPay MCP";
6141
- const expectedOrigin = normalizeOrigin(opts.consoleUrl);
6142
- const allowedCorsOrigins = /* @__PURE__ */ new Set([expectedOrigin]);
6143
- const loopbackAlt = loopbackAlternateOrigin(expectedOrigin);
6144
- if (loopbackAlt) allowedCorsOrigins.add(loopbackAlt);
6145
- const expectedGateway = normalizeUrl(opts.gatewayUrl);
6146
- const connectState = randomBytes3(32).toString("hex");
6147
- const server = createServer();
6148
- server.requestTimeout = 1e4;
6149
- server.headersTimeout = 1e4;
6150
- server.keepAliveTimeout = 5e3;
6151
- await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve));
6152
- const port = server.address().port;
6153
- const allowedHosts = /* @__PURE__ */ new Set([`127.0.0.1:${port}`, `localhost:${port}`]);
6154
- const base = opts.consoleUrl.replace(/\/+$/, "");
6155
- const connectUrl = `${base}/connect/agent?port=${port}&publicKey=${encodeURIComponent(opts.identity.publicKeyHex)}&delegateAddress=${encodeURIComponent(opts.identity.delegateAddress)}&label=${encodeURIComponent(label)}&gateway=${encodeURIComponent(opts.gatewayUrl)}&connectState=${connectState}`;
6156
- try {
6157
- opts.onUrl?.(connectUrl);
6158
- } catch {
5303
+ return {
5304
+ status: "failed",
5305
+ paid: false,
5306
+ code: DELEGATED_PAY_ERROR_CODES.TX_MISMATCH,
5307
+ detail: err instanceof Error ? err.message : "prepared transaction mismatch"
5308
+ };
6159
5309
  }
6160
- 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
- });
5310
+ const prepared = args.signingRequest.resource;
5311
+ if (args.request.url !== prepared.url || args.request.method !== prepared.method) {
5312
+ return {
5313
+ status: "failed",
5314
+ paid: false,
5315
+ code: DELEGATED_PAY_ERROR_CODES.UNSUPPORTED_REQUEST,
5316
+ detail: "prepared resource request cannot be replayed"
5317
+ };
6332
5318
  }
6333
- void done.then(() => {
6334
- if (error2) {
6335
- }
5319
+ return payDelegatedSpendAccount({
5320
+ url: args.signingRequest.resource.url,
5321
+ request: args.request,
5322
+ session: args.session,
5323
+ cfg: args.cfg,
5324
+ deps: args.deps,
5325
+ preparedTerms: {
5326
+ recipient: args.expected.recipient,
5327
+ amount: args.expected.amount,
5328
+ asset: args.signingRequest.coinType,
5329
+ targetHash: args.signingRequest.targetHash
5330
+ },
5331
+ ...args.intent ? { intent: args.intent } : {}
6336
5332
  });
6337
- return {
6338
- connectUrl,
6339
- port,
6340
- connectState,
6341
- done,
6342
- close: () => {
6343
- server.close();
6344
- }
6345
- };
6346
5333
  }
6347
5334
 
6348
5335
  // src/spend-account-pay.ts
6349
- import { Transaction as Transaction6 } from "@mysten/sui/transactions";
5336
+ import { Transaction as Transaction5 } from "@mysten/sui/transactions";
6350
5337
  import {
6351
- fromBase64 as fromBase646,
6352
- normalizeSuiAddress as normalizeSuiAddress11,
5338
+ fromBase64 as fromBase645,
5339
+ normalizeSuiAddress as normalizeSuiAddress9,
6353
5340
  toBase64 as toBase644
6354
5341
  } from "@mysten/sui/utils";
6355
5342
  var SUI_CLOCK3 = "0x6";
@@ -6387,11 +5374,11 @@ async function buildSpendAccountPayKind2(input) {
6387
5374
  if (!input.termsHash.length) throw new Error("termsHash must be non-empty");
6388
5375
  if (!input.paymentIdHash.length) throw new Error("paymentIdHash must be non-empty");
6389
5376
  const auth = defaultAuthorityForRail("delegate", input.packageId);
6390
- const target = `${normalizeSuiAddress11(auth.packageId)}::${auth.module}::${auth.payFunction}`;
5377
+ const target = `${normalizeSuiAddress9(auth.packageId)}::${auth.module}::${auth.payFunction}`;
6391
5378
  const hashHex = input.targetHash.replace(/^0x/i, "").toLowerCase();
6392
5379
  if (!/^[0-9a-f]{64}$/.test(hashHex)) throw new Error("targetHash must be 32-byte hex");
6393
5380
  const hash = Uint8Array.from(Buffer.from(hashHex, "hex"));
6394
- const tx = new Transaction6();
5381
+ const tx = new Transaction5();
6395
5382
  tx.setSender(input.delegate);
6396
5383
  tx.moveCall({
6397
5384
  target,
@@ -6425,29 +5412,29 @@ async function buildSpendAccountPayKind2(input) {
6425
5412
  function verifySpendAccountPayReconstructs(bytes, expected) {
6426
5413
  let data;
6427
5414
  try {
6428
- data = Transaction6.from(fromBase646(bytes)).getData();
5415
+ data = Transaction5.from(fromBase645(bytes)).getData();
6429
5416
  } catch {
6430
5417
  throw new Error("sponsored transaction is not valid Sui transaction bytes");
6431
5418
  }
6432
- if (!data.sender || normalizeSuiAddress11(data.sender) !== normalizeSuiAddress11(expected.delegate)) {
5419
+ if (!data.sender || normalizeSuiAddress9(data.sender) !== normalizeSuiAddress9(expected.delegate)) {
6433
5420
  throw new Error("sponsored transaction sender does not match delegate");
6434
5421
  }
6435
5422
  if (!data.commands || data.commands.length !== 1) {
6436
5423
  throw new Error("sponsored transaction must contain exactly one command");
6437
5424
  }
6438
5425
  const auth = defaultAuthorityForRail("delegate", expected.packageId);
6439
- const wanted = `${normalizeSuiAddress11(auth.packageId)}::${auth.module}::${auth.payFunction}`;
5426
+ const wanted = `${normalizeSuiAddress9(auth.packageId)}::${auth.module}::${auth.payFunction}`;
6440
5427
  const cmd = data.commands[0];
6441
5428
  if (cmd.$kind !== "MoveCall" || !cmd.MoveCall) {
6442
5429
  throw new Error("sponsored transaction is not a MoveCall");
6443
5430
  }
6444
- const got = `${normalizeSuiAddress11(String(cmd.MoveCall.package))}::${cmd.MoveCall.module}::${cmd.MoveCall.function}`;
5431
+ const got = `${normalizeSuiAddress9(String(cmd.MoveCall.package))}::${cmd.MoveCall.module}::${cmd.MoveCall.function}`;
6445
5432
  if (got !== wanted) {
6446
5433
  throw new Error(`sponsored transaction does not reconstruct ${wanted}`);
6447
5434
  }
6448
5435
  let expectedKind;
6449
5436
  try {
6450
- expectedKind = Transaction6.fromKind(fromBase646(expected.kindBytes)).getData();
5437
+ expectedKind = Transaction5.fromKind(fromBase645(expected.kindBytes)).getData();
6451
5438
  } catch {
6452
5439
  throw new Error("sponsored transaction kind baseline invalid");
6453
5440
  }
@@ -6604,7 +5591,7 @@ async function prepareSpendAccountPayment(args) {
6604
5591
  const signingRequest = {
6605
5592
  unsignedKindBytes: kindBytes,
6606
5593
  delegateAddress: resolved.delegateAddress,
6607
- packageId: normalizeSuiAddress11(authority.packageId),
5594
+ packageId: normalizeSuiAddress9(authority.packageId),
6608
5595
  poolObjectId: resolved.poolObjectId,
6609
5596
  grantObjectId: resolved.grantObjectId,
6610
5597
  policyId: resolved.onChainPolicyId,
@@ -6685,203 +5672,28 @@ function mapErrorCode(code) {
6685
5672
  }
6686
5673
  }
6687
5674
 
6688
- // src/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 };
5675
+ // src/tools.ts
5676
+ function payerTrace(deps) {
5677
+ const hooks = deps.trace;
5678
+ const buyerAccountId = deps.auth?.buyerAccountId;
5679
+ if (!hooks || !buyerAccountId) return null;
5680
+ return {
5681
+ sink: hooks.sink,
5682
+ traceId: hooks.context.traceId,
5683
+ requestId: hooks.context.requestId,
5684
+ buyerAccountId,
5685
+ connectionId: hooks.runtime.connectionId,
5686
+ ...hooks.runtime.secret ? { secret: hooks.runtime.secret } : {},
5687
+ ...hooks.runtime.now ? { now: hooks.runtime.now } : {}
5688
+ };
6873
5689
  }
6874
-
6875
- // src/tool-result.ts
6876
- var MAX_MCP_IMAGE_BYTES = 5 * 1024 * 1024;
5690
+ var AUTH_REQUIRED = "No SuiPay credentials - run suipay_login to create a local delegate key, then bind a spend-account grant in the console.";
6877
5691
  function text(value) {
6878
5692
  return {
6879
- content: [
6880
- {
6881
- type: "text",
6882
- text: typeof value === "string" ? value : JSON.stringify(value, null, 2)
6883
- }
6884
- ]
5693
+ content: [{
5694
+ type: "text",
5695
+ text: typeof value === "string" ? value : JSON.stringify(value, null, 2)
5696
+ }]
6885
5697
  };
6886
5698
  }
6887
5699
  function error(value) {
@@ -6893,6 +5705,7 @@ function closedError(code, detail) {
6893
5705
  function closedPayError(code, detail) {
6894
5706
  return error({ status: "failed", paid: false, code, detail });
6895
5707
  }
5708
+ var MAX_MCP_IMAGE_BYTES = 5 * 1024 * 1024;
6896
5709
  function paidResult(result) {
6897
5710
  if (!isBinaryBody(result.body)) return text(result);
6898
5711
  const body = result.body;
@@ -6913,137 +5726,6 @@ function paidResult(result) {
6913
5726
  }
6914
5727
  return { content };
6915
5728
  }
6916
- function settlePayResult(result) {
6917
- if (result.paid === true) return paidResult(result);
6918
- return error(result);
6919
- }
6920
-
6921
- // src/pay-file.ts
6922
- import { isAbsolute } from "path";
6923
- import { openSync as openSync2, closeSync as closeSync2, fstatSync, readFileSync as readFileSync2, constants } from "fs";
6924
- var MCP_MAX_FILE_FIELD_BYTES = 32 * 1024;
6925
- var FIELD = /^[A-Za-z][A-Za-z0-9_]{0,63}$/;
6926
- var FORBIDDEN = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
6927
- function fail2(code, detail) {
6928
- return { code, detail };
6929
- }
6930
- function parseField(value) {
6931
- if (typeof value !== "string" || !FIELD.test(value) || FORBIDDEN.has(value)) {
6932
- return fail2("INVALID_FILE_FIELD", "fileField must be a simple JSON key");
6933
- }
6934
- return value;
6935
- }
6936
- function parsePath(value) {
6937
- if (typeof value !== "string" || value.length === 0 || value.includes("\0")) {
6938
- return fail2("INVALID_FILE", "file must be a non-empty absolute path");
6939
- }
6940
- if (!isAbsolute(value) || value.includes("://")) {
6941
- return fail2("INVALID_FILE", "file must be an absolute filesystem path, not a URL");
6942
- }
6943
- return value;
6944
- }
6945
- function readRegularFile(path) {
6946
- let fd;
6947
- try {
6948
- const flags = constants.O_RDONLY | (constants.O_NOFOLLOW !== void 0 ? constants.O_NOFOLLOW : 0);
6949
- fd = openSync2(path, flags);
6950
- const st = fstatSync(fd);
6951
- if (!st.isFile()) {
6952
- return fail2("INVALID_FILE", "file must be a regular file");
6953
- }
6954
- if (st.size === 0) {
6955
- return fail2("EMPTY_FILE", "file is empty");
6956
- }
6957
- if (st.size > MCP_MAX_FILE_FIELD_BYTES) {
6958
- return fail2(
6959
- "FILE_TOO_LARGE",
6960
- `file exceeds ${MCP_MAX_FILE_FIELD_BYTES} decoded bytes`
6961
- );
6962
- }
6963
- const bytes = readFileSync2(fd);
6964
- if (bytes.byteLength === 0) {
6965
- return fail2("EMPTY_FILE", "file is empty");
6966
- }
6967
- if (bytes.byteLength > MCP_MAX_FILE_FIELD_BYTES) {
6968
- return fail2(
6969
- "FILE_TOO_LARGE",
6970
- `file exceeds ${MCP_MAX_FILE_FIELD_BYTES} decoded bytes`
6971
- );
6972
- }
6973
- return bytes;
6974
- } catch (err) {
6975
- const code = err.code;
6976
- if (code === "ENOENT") return fail2("FILE_NOT_FOUND", "file does not exist");
6977
- if (code === "ELOOP" || code === "EMLINK") {
6978
- return fail2("INVALID_FILE", "file must be a regular file");
6979
- }
6980
- return fail2("INVALID_FILE", "file could not be read");
6981
- } finally {
6982
- if (fd !== void 0) closeSync2(fd);
6983
- }
6984
- }
6985
- function toCanonicalBase64(bytes) {
6986
- return Buffer.from(bytes).toString("base64");
6987
- }
6988
- function jsonWithLocalFile(input) {
6989
- const hasFile = input.file !== void 0 && input.file !== null && input.file !== "";
6990
- const hasField = input.fileField !== void 0 && input.fileField !== null && input.fileField !== "";
6991
- if (!hasFile && !hasField) return { json: input.json };
6992
- if (hasFile !== hasField) {
6993
- return {
6994
- error: fail2(
6995
- "INVALID_FILE_ARGS",
6996
- "file and fileField are required together"
6997
- )
6998
- };
6999
- }
7000
- const method = input.method === void 0 || input.method === null || input.method === "" ? "GET" : String(input.method).trim().toUpperCase();
7001
- if (method !== "POST") {
7002
- return { error: fail2("INVALID_FILE_ARGS", "file is only valid on POST") };
7003
- }
7004
- const field = parseField(input.fileField);
7005
- if (typeof field !== "string") return { error: field };
7006
- const path = parsePath(input.file);
7007
- if (typeof path !== "string") return { error: path };
7008
- let json;
7009
- if (input.json === void 0) {
7010
- json = {};
7011
- } else if (typeof input.json === "object" && input.json !== null && !Array.isArray(input.json)) {
7012
- json = { ...input.json };
7013
- } else {
7014
- return { error: fail2("MALFORMED_REQUEST", "POST requires json object") };
7015
- }
7016
- if (Object.prototype.hasOwnProperty.call(json, field)) {
7017
- return {
7018
- error: fail2(
7019
- "FILE_FIELD_EXISTS",
7020
- "json already has fileField; refuse to overwrite"
7021
- )
7022
- };
7023
- }
7024
- const bytes = readRegularFile(path);
7025
- if (!(bytes instanceof Uint8Array)) return { error: bytes };
7026
- json[field] = toCanonicalBase64(bytes);
7027
- return { json };
7028
- }
7029
-
7030
- // src/tools.ts
7031
- function payerTrace(deps) {
7032
- const hooks = deps.trace;
7033
- const buyerAccountId = deps.auth?.buyerAccountId;
7034
- if (!hooks || !buyerAccountId) return null;
7035
- return {
7036
- sink: hooks.sink,
7037
- traceId: hooks.context.traceId,
7038
- requestId: hooks.context.requestId,
7039
- buyerAccountId,
7040
- connectionId: hooks.runtime.connectionId,
7041
- ...hooks.runtime.secret ? { secret: hooks.runtime.secret } : {},
7042
- ...hooks.runtime.now ? { now: hooks.runtime.now } : {}
7043
- };
7044
- }
7045
- var AUTH_REQUIRED = "No SuiPay credentials - call suipay_login first. It returns a console URL; approve the spend-account grant in the browser. The next tool call picks up ~/.suipay/credentials.json automatically.";
7046
- var LOGIN_BG_TIMEOUT_MS = 5 * 6e4;
7047
5729
  function preparedResult(result) {
7048
5730
  return text({
7049
5731
  status: "prepared",
@@ -7063,7 +5745,7 @@ function normalizePayIntent(args) {
7063
5745
  if (!/^0x[0-9a-fA-F]{1,64}$/.test(raw)) {
7064
5746
  throw new Error("recipient must be a 0x-prefixed Sui address");
7065
5747
  }
7066
- const normalized = normalizeSuiAddress12(raw);
5748
+ const normalized = normalizeSuiAddress10(raw);
7067
5749
  if (!isValidSuiAddress(normalized) || /^0x0+$/.test(normalized)) {
7068
5750
  throw new Error("recipient must be a non-zero Sui address");
7069
5751
  }
@@ -7082,7 +5764,7 @@ function intentViolation(intent, prepared) {
7082
5764
  if (intent.recipient) {
7083
5765
  let preparedRecipient;
7084
5766
  try {
7085
- preparedRecipient = normalizeSuiAddress12(prepared.recipient);
5767
+ preparedRecipient = normalizeSuiAddress10(prepared.recipient);
7086
5768
  } catch {
7087
5769
  return "prepared payment does not name a valid recipient";
7088
5770
  }
@@ -7105,27 +5787,15 @@ function intentViolation(intent, prepared) {
7105
5787
  }
7106
5788
  async function pay(args, cfg, deps = {}) {
7107
5789
  if (!args.url) return error("url is required");
7108
- const hasFileArg = args.file !== void 0 && args.file !== null && args.file !== "" || args.fileField !== void 0 && args.fileField !== null && args.fileField !== "";
7109
- if (hasFileArg && deps.auth) {
7110
- return closedPayError(
7111
- "FILE_STDIO_ONLY",
7112
- "file is stdio only; hosted /mcp does not read the buyer disk"
7113
- );
7114
- }
7115
5790
  let intent;
7116
5791
  try {
7117
5792
  intent = normalizePayIntent(args);
7118
5793
  } catch {
7119
5794
  return closedPayError("INVALID_INTENT", "payment intent is invalid");
7120
5795
  }
7121
- const attached = jsonWithLocalFile(args);
7122
- if ("error" in attached) {
7123
- return closedPayError(attached.error.code, attached.error.detail);
7124
- }
7125
- const paidArgs = { ...args, json: attached.json };
7126
5796
  let request;
7127
5797
  try {
7128
- request = normalizeMcpPaidHttpRequest(paidArgs);
5798
+ request = normalizeMcpPaidHttpRequest(args);
7129
5799
  } catch (err) {
7130
5800
  const raw = err instanceof Error ? err.message : "";
7131
5801
  const detail = /method/i.test(raw) ? "method must be GET or POST" : /json/i.test(raw) ? "POST requires json" : "request could not be normalized";
@@ -7200,10 +5870,10 @@ async function pay(args, cfg, deps = {}) {
7200
5870
  ...deps.fetchImpl && !deps.agentHeld.payerDeps.fetchImpl ? { fetchImpl: deps.fetchImpl } : {}
7201
5871
  }
7202
5872
  });
7203
- return settlePayResult(settled);
5873
+ return settled.status === "ok" ? paidResult(settled) : error(settled);
7204
5874
  }
7205
5875
  if (result.status === "prepared") return preparedResult(result);
7206
- return settlePayResult(result);
5876
+ return result.status === "ok" ? paidResult(result) : error(result);
7207
5877
  }
7208
5878
  if (deps.auth) {
7209
5879
  return error(
@@ -7218,259 +5888,122 @@ async function pay(args, cfg, deps = {}) {
7218
5888
  }
7219
5889
  const local = loadLocalSigner();
7220
5890
  if (!local) return error(AUTH_REQUIRED);
7221
- return payStdioWithLocalKey({ request, intent, cfg, deps });
5891
+ return payStdioWithLocalKey({ request, intent, cfg, deps, local });
7222
5892
  }
7223
- function stdioGatewayUrl(cfg) {
7224
- return loadLocalSigningKeyRecord()?.gatewayUrl ?? cfg.gatewayUrl;
5893
+ function snapshotFromBinding(binding, delegateAddress) {
5894
+ return {
5895
+ grantId: binding.grantObjectId,
5896
+ status: "active",
5897
+ grantObjectId: binding.grantObjectId,
5898
+ poolObjectId: binding.poolObjectId,
5899
+ coinType: binding.coinType,
5900
+ delegateAddress,
5901
+ policies: binding.policies,
5902
+ accessMode: binding.accessMode,
5903
+ ...binding.maxPerPayment != null ? { maxPerPayment: binding.maxPerPayment } : {},
5904
+ ...binding.sessionCap != null ? { sessionCap: binding.sessionCap } : {}
5905
+ };
7225
5906
  }
7226
- function isExpiredBootstrapGrant(expiresAtMs, nowMs) {
7227
- if (expiresAtMs === void 0 || expiresAtMs === null || expiresAtMs === "" || expiresAtMs === "0" || expiresAtMs === 0) {
7228
- return false;
7229
- }
7230
- const exp = typeof expiresAtMs === "number" ? expiresAtMs : typeof expiresAtMs === "string" && /^\d+$/.test(expiresAtMs) ? Number(expiresAtMs) : NaN;
7231
- if (!Number.isFinite(exp) || exp <= 0) return false;
7232
- return exp <= nowMs;
7233
- }
7234
- function grantObjectIdFromBootstrap(grants, nowMs) {
7235
- let named = 0;
7236
- const live = [];
7237
- for (const grant of grants) {
7238
- if (!grant || typeof grant !== "object") continue;
7239
- const g = grant;
7240
- const id = g.grantObjectId;
7241
- if (typeof id !== "string" || !id.trim()) continue;
7242
- named += 1;
7243
- if (isExpiredBootstrapGrant(g.expiresAtMs, nowMs)) continue;
7244
- live.push(id.trim());
7245
- }
7246
- if (live.length === 1) return { kind: "live", grantObjectId: live[0] };
7247
- if (live.length > 1) return { kind: "ambiguous" };
7248
- if (named > 0) return { kind: "absent" };
7249
- if (grants.length > 0) return { kind: "unknown", detail: "delegate grants list was not usable" };
7250
- return { kind: "absent" };
7251
- }
7252
- async function checkLiveDelegateGrant(args) {
7253
- const deployment = await fetchGatewayDeployment({
7254
- gatewayUrl: args.gatewayUrl,
7255
- ...args.fetchImpl ? { fetchImpl: args.fetchImpl } : {}
7256
- });
7257
- if (!deployment.ok) {
7258
- return {
7259
- kind: "unknown",
7260
- detail: "gateway deployment identity could not be verified"
7261
- };
7262
- }
7263
- const listed = await bootstrapPayerGrants({
7264
- gatewayUrl: args.gatewayUrl,
7265
- context: {
7266
- network: deployment.value.network,
7267
- packageId: deployment.value.packageId,
7268
- delegateAddress: args.delegateAddress
5907
+ async function payStdioWithLocalKey(args) {
5908
+ const { request, intent, cfg, deps, local } = args;
5909
+ const resolved = await resolvePayerSession({
5910
+ gatewayUrl: cfg.gatewayUrl,
5911
+ delegateAddress: local.address,
5912
+ signPersonalMessage: local.signPersonalMessage,
5913
+ signer: {
5914
+ address: local.address,
5915
+ signTransaction: local.signTransaction,
5916
+ signPersonalMessage: local.signPersonalMessage
7269
5917
  },
7270
- signPersonalMessage: args.signPersonalMessage,
7271
- ...args.fetchImpl ? { fetchImpl: args.fetchImpl } : {}
5918
+ ...deps.fetchImpl ? { fetchImpl: deps.fetchImpl } : {}
7272
5919
  });
7273
- if (!listed.ok) {
7274
- return {
7275
- kind: "unknown",
7276
- detail: `delegate grants read ${listed.error} (HTTP ${listed.status})`
7277
- };
7278
- }
7279
- if (!Array.isArray(listed.data.grants) || listed.data.grants.length === 0) {
7280
- return { kind: "absent" };
7281
- }
7282
- return grantObjectIdFromBootstrap(listed.data.grants, Date.now());
7283
- }
7284
- async function payStdioWithLocalKey(args) {
7285
- const { request, intent, cfg, deps } = args;
7286
- const built = await stdioPayerAndReader(cfg, deps);
7287
- if (!built.ok) return built.result;
7288
- const payer = built.payer;
7289
- const grantCeiling = payer.grant.maxPerPayment;
7290
- const maxAmount = intent.maxAmount !== void 0 ? intent.maxAmount.toString() : grantCeiling != null && grantCeiling !== "" ? grantCeiling : null;
7291
- if (maxAmount === null) {
7292
- return error({
7293
- status: "failed",
7294
- paid: false,
7295
- code: "POLICY_REJECTED",
7296
- detail: "stdio pay requires maxAmount or a grant maxPerPayment; refusing to default the ceiling to the offer"
7297
- });
5920
+ if (!resolved.ok) {
5921
+ return closedPayError(resolved.error.code, resolved.error.detail);
7298
5922
  }
7299
- const settled = await payer.pay(request.url, {
7300
- method: request.method,
7301
- ...request.method === "POST" ? { body: request.body } : {},
7302
- ...request.contentType ? { contentType: request.contentType } : {},
7303
- intent: {
7304
- maxAmount,
7305
- asset: payer.grant.coinType,
7306
- ...intent.recipient ? { recipient: intent.recipient } : {}
5923
+ const snapshot = snapshotFromBinding(resolved.value.binding, local.address);
5924
+ const packageId = resolved.value.session.packageId;
5925
+ const result = await prepareSpendAccountPayment({
5926
+ url: request.url,
5927
+ request,
5928
+ session: {
5929
+ snapshot,
5930
+ packageId,
5931
+ ...resolved.value.session.assertLive ? { assertLive: resolved.value.session.assertLive } : {}
5932
+ },
5933
+ cfg,
5934
+ deps: {
5935
+ ...deps.fetchImpl ? { fetchImpl: deps.fetchImpl } : {},
5936
+ ...deps.buildPayKind ? { buildPayKind: deps.buildPayKind } : {}
7307
5937
  }
7308
5938
  });
7309
- return settlePayResult(settled);
7310
- }
7311
- async function stdioPayerAndReader(cfg, deps) {
7312
- const keypair = loadLocalKeypair();
7313
- if (!keypair) return { ok: false, result: error(AUTH_REQUIRED) };
7314
- const gatewayUrl = stdioGatewayUrl(cfg);
7315
- const local = loadLocalSigner();
7316
- let select;
7317
- const shouldSelect = Boolean(local) && (deps.fetchImpl !== void 0 || deps.createPayer === void 0);
7318
- if (local && shouldSelect) {
7319
- const check = await checkLiveDelegateGrant({
7320
- gatewayUrl,
7321
- delegateAddress: local.address,
7322
- signPersonalMessage: local.signPersonalMessage,
7323
- ...deps.fetchImpl ? { fetchImpl: deps.fetchImpl } : {}
7324
- });
7325
- if (check.kind === "ambiguous") {
7326
- return {
7327
- ok: false,
7328
- result: closedError(
7329
- "AMBIGUOUS_GRANT",
7330
- "multiple live grants name this key; pass a selector (grantObjectId / asset / coinType)"
7331
- )
7332
- };
5939
+ if (result.status === "prepared") {
5940
+ const violation = intentViolation(intent, result.signingRequest);
5941
+ if (violation) {
5942
+ return error({
5943
+ status: "failed",
5944
+ paid: false,
5945
+ code: "POLICY_REJECTED",
5946
+ detail: violation
5947
+ });
7333
5948
  }
7334
- if (check.kind === "live") select = { grantObjectId: check.grantObjectId };
7335
- }
7336
- const built = await (deps.createPayer ?? createPayer)({
7337
- gatewayUrl,
7338
- keypair,
7339
- ...select ? { select } : {},
7340
- ...deps.fetchImpl ? { fetchImpl: deps.fetchImpl } : {},
7341
- ...cfg.rpcUrl ? { fullnodeUrl: cfg.rpcUrl } : {}
7342
- });
7343
- if (!built.ok) {
7344
- return {
7345
- ok: false,
7346
- result: closedError(built.error.code, built.error.detail)
5949
+ const agentHeld = deps.agentHeld ?? settlementFromLocalSigner(cfg, local, packageId);
5950
+ const grantCeiling = resolved.value.binding.maxPerPayment;
5951
+ const maxAmount = intent.maxAmount !== void 0 ? intent.maxAmount.toString() : grantCeiling != null && grantCeiling !== "" ? grantCeiling : null;
5952
+ if (maxAmount === null) {
5953
+ return error({
5954
+ status: "failed",
5955
+ paid: false,
5956
+ code: "POLICY_REJECTED",
5957
+ detail: "stdio pay requires maxAmount or a grant maxPerPayment; refusing to default the ceiling to the offer"
5958
+ });
5959
+ }
5960
+ const spendIntent = {
5961
+ maxAmount,
5962
+ asset: resolved.value.binding.coinType,
5963
+ ...intent.recipient ? { recipient: intent.recipient } : {}
7347
5964
  };
5965
+ const settled = await settlePreparedMcpPayment({
5966
+ signingRequest: result.signingRequest,
5967
+ request,
5968
+ expected: {
5969
+ amount: result.signingRequest.amount,
5970
+ recipient: intent.recipient ?? result.signingRequest.recipient,
5971
+ delegateAddress: local.address
5972
+ },
5973
+ signer: local,
5974
+ getObjectJson: agentHeld.getObjectJson,
5975
+ session: {
5976
+ ...resolved.value.session,
5977
+ signer: local
5978
+ },
5979
+ cfg: { gatewayUrl: cfg.gatewayUrl },
5980
+ deps: {
5981
+ ...agentHeld.payerDeps,
5982
+ ...deps.fetchImpl && !agentHeld.payerDeps.fetchImpl ? { fetchImpl: deps.fetchImpl } : {}
5983
+ },
5984
+ intent: spendIntent
5985
+ });
5986
+ return settled.status === "ok" ? paidResult(settled) : error(settled);
7348
5987
  }
7349
- return { ok: true, payer: built.value };
7350
- }
7351
- function stdioReaderForPayer(cfg, deps, payer) {
7352
- return (deps.createReader ?? createReader)({
7353
- gatewayUrl: stdioGatewayUrl(cfg),
7354
- context: {
7355
- network: payer.deployment.network,
7356
- packageId: payer.deployment.packageId,
7357
- grantObjectId: payer.grant.grantObjectId,
7358
- delegateAddress: payer.address
7359
- },
7360
- signPersonalMessage: async (message) => {
7361
- const { signature } = await loadLocalKeypair().signPersonalMessage(message);
7362
- return signature;
7363
- },
7364
- ...deps.fetchImpl ? { fetchImpl: deps.fetchImpl } : {}
7365
- });
7366
- }
7367
- function budgetFromBalance(balance, grant) {
7368
- if (!balance.ok || !balance.data) return void 0;
7369
- return {
7370
- sessionRemaining: balance.data.sessionRemaining ?? null,
7371
- maxPerPayment: balance.data.maxPerPayment ?? grant.maxPerPayment ?? null,
7372
- coinType: balance.data.coinType ?? grant.coinType ?? null
7373
- };
7374
- }
7375
- async function fetchPublicCatalog(gatewayUrl, fetchImpl) {
7376
- const catalogUrl = new URL("/v1/services", gatewayUrl);
7377
- const response = await (fetchImpl ?? fetch)(catalogUrl);
7378
- if (!response.ok) return { ok: false, result: error(`gateway unavailable: HTTP ${response.status}`) };
7379
- return { ok: true, catalog: await response.json() };
7380
- }
7381
- async function publicCatalog(gatewayUrl, query, fetchImpl) {
7382
- const loaded = await fetchPublicCatalog(gatewayUrl, fetchImpl);
7383
- if (!loaded.ok) return loaded.result;
7384
- return text(
7385
- joinDiscoverCatalog({
7386
- catalog: loaded.catalog,
7387
- gatewayUrl,
7388
- query
7389
- })
7390
- );
5988
+ return result.status === "ok" ? paidResult(result) : error(result);
7391
5989
  }
7392
5990
  async function discover(args, cfg, deps = {}) {
7393
- if (deps.auth || !loadLocalKeypair()) {
7394
- return publicCatalog(cfg.gatewayUrl, args.query, deps.fetchImpl);
5991
+ const url = new URL("/v1/services", cfg.gatewayUrl);
5992
+ if (args.query) url.searchParams.set("q", args.query);
5993
+ const response = await (deps.fetchImpl ?? fetch)(url);
5994
+ if (!response.ok) return error(`gateway unavailable: HTTP ${response.status}`);
5995
+ return text(await response.json());
5996
+ }
5997
+ async function accessContext(deps = {}) {
5998
+ if (!deps.auth) return error(AUTH_REQUIRED);
5999
+ if (!deps.auth.access) {
6000
+ return error(
6001
+ "SuiPay access context unavailable. Reconnect this MCP session or inspect the connection in buyer console."
6002
+ );
7395
6003
  }
7396
- const local = loadLocalSigner();
7397
- if (local) {
7398
- const check = await checkLiveDelegateGrant({
7399
- gatewayUrl: stdioGatewayUrl(cfg),
7400
- delegateAddress: local.address,
7401
- signPersonalMessage: local.signPersonalMessage,
7402
- ...deps.fetchImpl ? { fetchImpl: deps.fetchImpl } : {}
7403
- });
7404
- if (check.kind !== "live") {
7405
- return publicCatalog(stdioGatewayUrl(cfg), args.query, deps.fetchImpl);
7406
- }
7407
- }
7408
- const resolved = await stdioPayerAndReader(cfg, deps);
7409
- if (!resolved.ok) return resolved.result;
7410
- const gatewayUrl = stdioGatewayUrl(cfg);
7411
- const loaded = await fetchPublicCatalog(gatewayUrl, deps.fetchImpl);
7412
- if (!loaded.ok) return loaded.result;
7413
- const scoped = resolved.payer.grant.accessMode === "scoped";
7414
- const reader = stdioReaderForPayer(cfg, deps, resolved.payer);
7415
- const [policies, balance] = await Promise.all([
7416
- scoped ? reader.policies() : Promise.resolve({ ok: true, data: { policies: void 0 } }),
7417
- reader.balance()
7418
- ]);
7419
- if (scoped && !policies.ok) {
7420
- return closedError("POLICIES_UNAVAILABLE", "grant policies could not be loaded");
7421
- }
7422
- return text(
7423
- joinDiscoverCatalog({
7424
- catalog: loaded.catalog,
7425
- gatewayUrl,
7426
- query: args.query,
7427
- ...scoped && policies.ok ? { policies: policies.data.policies } : {},
7428
- budget: budgetFromBalance(balance, resolved.payer.grant)
7429
- })
7430
- );
7431
- }
7432
- async function accessContext(deps = {}, cfg) {
7433
- if (deps.auth) {
7434
- if (!deps.auth.access) {
7435
- return error(
7436
- "SuiPay access context unavailable. Reconnect this MCP session or inspect the connection in buyer console."
7437
- );
7438
- }
7439
- return text(deps.auth.access);
7440
- }
7441
- if (!cfg) return error(AUTH_REQUIRED);
7442
- const resolved = await stdioPayerAndReader(cfg, deps);
7443
- if (!resolved.ok) return resolved.result;
7444
- const gatewayUrl = stdioGatewayUrl(cfg);
7445
- const reader = stdioReaderForPayer(cfg, deps, resolved.payer);
7446
- const scoped = resolved.payer.grant.accessMode === "scoped";
7447
- const [policies, balance, loaded] = await Promise.all([
7448
- reader.policies(),
7449
- reader.balance(),
7450
- fetchPublicCatalog(gatewayUrl, deps.fetchImpl)
7451
- ]);
7452
- const budget = budgetFromBalance(balance, resolved.payer.grant);
7453
- const joined = loaded.ok && (!scoped || policies.ok) ? joinDiscoverCatalog({
7454
- catalog: loaded.catalog,
7455
- gatewayUrl,
7456
- ...scoped && policies.ok ? { policies: policies.data.policies } : {},
7457
- budget
7458
- }) : null;
7459
- return text({
7460
- accessMode: resolved.payer.grant.accessMode,
7461
- grant: {
7462
- id: resolved.payer.grant.grantObjectId,
7463
- poolObjectId: resolved.payer.grant.poolObjectId,
7464
- coinType: resolved.payer.grant.coinType,
7465
- maxPerPayment: resolved.payer.grant.maxPerPayment ?? null,
7466
- sessionCap: resolved.payer.grant.sessionCap ?? null
7467
- },
7468
- policies: policies.ok ? policies.data : { error: policies.error },
7469
- balance: balance.ok ? balance.data : { error: balance.error },
7470
- ...joined ? { services: joined.services, count: joined.count } : {}
7471
- });
6004
+ return text(deps.auth.access);
7472
6005
  }
7473
- async function receipts(args, cfg, deps = {}) {
6006
+ async function receipts(args, _cfg, deps = {}) {
7474
6007
  if (deps.auth) {
7475
6008
  if (!deps.auth.listReceipts) {
7476
6009
  return error(
@@ -7484,144 +6017,28 @@ async function receipts(args, cfg, deps = {}) {
7484
6017
  return closedError("RECEIPTS_UNAVAILABLE", "receipts could not be loaded");
7485
6018
  }
7486
6019
  }
7487
- if (profile(deps)) {
7488
- return error(
7489
- "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"
7490
- );
7491
- }
7492
- const resolved = await stdioPayerAndReader(cfg, deps);
7493
- if (!resolved.ok) return resolved.result;
7494
- const reader = (deps.createReader ?? createReader)({
7495
- gatewayUrl: stdioGatewayUrl(cfg),
7496
- context: {
7497
- network: resolved.payer.deployment.network,
7498
- packageId: resolved.payer.deployment.packageId,
7499
- grantObjectId: resolved.payer.grant.grantObjectId,
7500
- delegateAddress: resolved.payer.address
7501
- },
7502
- signPersonalMessage: async (message) => {
7503
- const { signature } = await loadLocalKeypair().signPersonalMessage(message);
7504
- return signature;
7505
- },
7506
- ...deps.fetchImpl ? { fetchImpl: deps.fetchImpl } : {}
7507
- });
7508
- const listed = await reader.receipts();
7509
- if (!listed.ok) {
7510
- return closedError("RECEIPTS_UNAVAILABLE", "receipts could not be loaded");
7511
- }
7512
- const receiptsOut = listed.data.receipts;
7513
- const filtered = args.challengeId && Array.isArray(receiptsOut) ? receiptsOut.filter((row) => {
7514
- if (!row || typeof row !== "object") return false;
7515
- const rec = row;
7516
- return rec.challengeId === args.challengeId || rec.receipt?.challengeId === args.challengeId;
7517
- }) : receiptsOut;
7518
- return text({ count: Array.isArray(filtered) ? filtered.length : 0, receipts: filtered });
7519
- }
7520
- function parseLoginTarget(value) {
7521
- if (value === void 0 || value === null || value === "") return void 0;
7522
- if (typeof value !== "string") return { error: 'target must be "dev" or "local"' };
7523
- const target = value.trim().toLowerCase();
7524
- if (target === "dev" || target === "local") return target;
7525
- return { error: 'target must be "dev" or "local"' };
6020
+ if (!profile(deps)) return error(AUTH_REQUIRED);
6021
+ return error(
6022
+ "receipts is available on remote OAuth (/mcp) sessions only; the local allowance profile has no settlement feed access"
6023
+ );
7526
6024
  }
7527
6025
  async function suipayLogin(args, cfg, deps = {}) {
7528
- const parsedTarget = parseLoginTarget(args.target);
7529
- if (parsedTarget && typeof parsedTarget === "object" && "error" in parsedTarget) {
7530
- return error({
7531
- status: "failed",
7532
- code: "INVALID_TARGET",
7533
- detail: parsedTarget.error
7534
- });
7535
- }
7536
- const target = parsedTarget;
7537
6026
  const label = args.label?.trim() || cfg.label.trim();
7538
6027
  const identity = await ensureLocalSigningKey(label ? { label } : {});
7539
- const local = loadLocalSigner();
7540
- const stored = loadLocalSigningKeyRecord();
7541
- const endpoints = target ? MCP_TARGET_PRESETS[target] : {
7542
- consoleUrl: cfg.consoleUrl,
7543
- gatewayUrl: stored?.gatewayUrl ?? cfg.gatewayUrl
7544
- };
7545
- if (local) {
7546
- const live = await checkLiveDelegateGrant({
7547
- gatewayUrl: endpoints.gatewayUrl,
7548
- delegateAddress: local.address,
7549
- signPersonalMessage: local.signPersonalMessage,
7550
- ...deps.fetchImpl ? { fetchImpl: deps.fetchImpl } : {}
7551
- });
7552
- if (live.kind === "ambiguous") {
7553
- return error({
7554
- status: "failed",
7555
- code: "AMBIGUOUS_GRANT",
7556
- detail: "multiple live grants name this key; revoke extras or pass a selector before paying"
7557
- });
7558
- }
7559
- if (live.kind === "live") {
7560
- const storedUrl = stored?.gatewayUrl;
7561
- if (!storedUrl || storedUrl !== endpoints.gatewayUrl && (sameMcpGateway(storedUrl, endpoints.gatewayUrl) || target !== void 0)) {
7562
- persistGatewayUrl(endpoints.gatewayUrl);
7563
- }
7564
- return text({
7565
- status: "ok",
7566
- reused: true,
7567
- target: target ?? cfg.target,
7568
- delegateAddress: identity.delegateAddress,
7569
- gatewayUrl: loadLocalSigningKeyRecord()?.gatewayUrl ?? endpoints.gatewayUrl,
7570
- grantObjectId: live.grantObjectId,
7571
- message: "Existing spend-account grant is live for this key. No browser round-trip."
7572
- });
7573
- }
7574
- if (live.kind === "unknown") {
7575
- return error({
7576
- status: "failed",
7577
- code: "GRANT_STATUS_UNKNOWN",
7578
- detail: "Could not verify the existing grant. Credentials were not modified. Retry later; do not start a new consent (that would mint a second grant)."
7579
- });
7580
- }
6028
+ const connect = new URL("/connect/agent", cfg.consoleUrl);
6029
+ connect.searchParams.set("delegateAddress", identity.delegateAddress);
6030
+ const url = connect.toString();
6031
+ deps.onUrl?.(url);
6032
+ try {
6033
+ await open(url);
6034
+ } catch {
7581
6035
  }
7582
- const started = await (deps.startStdioLogin ?? startStdioLogin)({
7583
- consoleUrl: endpoints.consoleUrl,
7584
- gatewayUrl: endpoints.gatewayUrl,
7585
- identity,
7586
- label,
7587
- timeoutMs: LOGIN_BG_TIMEOUT_MS,
7588
- openBrowser: false,
7589
- onUrl: deps.onUrl
6036
+ return text({
6037
+ status: "ok",
6038
+ delegateAddress: identity.delegateAddress,
6039
+ connectUrl: url,
6040
+ message: "Open the console to bind a spend-account grant to this delegate address. The private key never leaves this process."
7590
6041
  });
7591
- const url = started.connectUrl;
7592
- return text(
7593
- [
7594
- `## ACTION REQUIRED: User must click this URL to sign in`,
7595
- ``,
7596
- `**Target:** ${target ?? cfg.target ?? "custom"} \xB7 ${endpoints.gatewayUrl}`,
7597
- ``,
7598
- `**URL:** ${url}`,
7599
- ``,
7600
- "```",
7601
- url,
7602
- "```",
7603
- ``,
7604
- `[Click here to open SuiPay sign-in](${url})`,
7605
- ``,
7606
- `**IMPORTANT for the assistant**: do NOT summarize or omit the URL above.`,
7607
- `The user CANNOT proceed without seeing the exact URL. Surface it verbatim.`,
7608
- ``,
7609
- `1. Open the URL in any browser`,
7610
- `2. Connect the owner wallet and approve the spend-account grant (Access keys / policies)`,
7611
- `3. Once Connected appears, retry the original request \u2014 credentials at \`~/.suipay/credentials.json\` are picked up on the next tool call`,
7612
- ``,
7613
- `_The login link stays valid for 5 minutes. If it expires, call \`suipay_login\` again._`,
7614
- ``,
7615
- JSON.stringify({
7616
- status: "ok",
7617
- target: target ?? cfg.target,
7618
- delegateAddress: identity.delegateAddress,
7619
- connectUrl: url,
7620
- consoleUrl: endpoints.consoleUrl,
7621
- gatewayUrl: endpoints.gatewayUrl
7622
- })
7623
- ].join("\n")
7624
- );
7625
6042
  }
7626
6043
  async function logout(args, _cfg, deps = {}) {
7627
6044
  if (args.force) {
@@ -7661,14 +6078,6 @@ var TOOLS = [
7661
6078
  type: "object",
7662
6079
  description: "JSON request body for a POST. Sent as application/json and bound to the payment."
7663
6080
  },
7664
- file: {
7665
- type: "string",
7666
- description: "Absolute local path whose bytes fill json[fileField] as canonical base64. Stdio only; requires fileField. GET must omit this."
7667
- },
7668
- fileField: {
7669
- type: "string",
7670
- description: "JSON key to set from file (e.g. contentBase64). Required with file. Must not already be present on json."
7671
- },
7672
6081
  recipient: {
7673
6082
  type: "string",
7674
6083
  description: "Sui address you expect to be paid. The payment is refused, unsigned, if the prepared transaction pays anyone else."
@@ -7683,7 +6092,7 @@ var TOOLS = [
7683
6092
  },
7684
6093
  {
7685
6094
  name: "discover",
7686
- description: "Search SuiPay gateway resources. 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).",
6095
+ description: "Search SuiPay gateway resources.",
7687
6096
  inputSchema: {
7688
6097
  type: "object",
7689
6098
  properties: {
@@ -7709,16 +6118,11 @@ var TOOLS = [
7709
6118
  },
7710
6119
  {
7711
6120
  name: "suipay_login",
7712
- description: '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.',
6121
+ description: "Mint or reuse the local delegate key (version-2, mode 0600) and open the console with the public address only so the owner can bind a spend-account grant. Stdio only; hosted /mcp never holds a spendable key.",
7713
6122
  inputSchema: {
7714
6123
  type: "object",
7715
6124
  properties: {
7716
6125
  label: { type: "string", description: "Label shown in SuiPay console." },
7717
- target: {
7718
- type: "string",
7719
- enum: ["dev", "local"],
7720
- description: 'Where to bind the grant. Default is Railway suipay-dev. Pass "local" for localhost:3000 / 127.0.0.1:4340.'
7721
- },
7722
6126
  gatewayResourceUrl: {
7723
6127
  type: "string",
7724
6128
  description: "Gateway resource URL whose 402 defines recipient, asset, and package."
@@ -7774,7 +6178,7 @@ function toolError(value) {
7774
6178
  isError: true
7775
6179
  };
7776
6180
  }
7777
- function createServer2(cfg, auth, trace, options = {}) {
6181
+ function createServer(cfg, auth, trace, options = {}) {
7778
6182
  const resolveAgentHeld = options.resolveAgentHeld ?? resolveAgentHeldSettlement;
7779
6183
  const server = new Server(
7780
6184
  { name: "suipay", version: "1.0.0" },
@@ -7799,7 +6203,7 @@ function createServer2(cfg, auth, trace, options = {}) {
7799
6203
  }
7800
6204
  switch (name) {
7801
6205
  case "access_context":
7802
- return await accessContext({ auth }, cfg);
6206
+ return await accessContext({ auth });
7803
6207
  case "pay": {
7804
6208
  const agentHeld = auth?.spendAccountPay ? resolveAgentHeld(cfg, auth.spendAccountPay) : null;
7805
6209
  return await pay(
@@ -7807,8 +6211,6 @@ function createServer2(cfg, auth, trace, options = {}) {
7807
6211
  url: String(args.url ?? ""),
7808
6212
  ...args.method !== void 0 ? { method: args.method } : {},
7809
6213
  ...args.json !== void 0 ? { json: args.json } : {},
7810
- ...args.file !== void 0 ? { file: args.file } : {},
7811
- ...args.fileField !== void 0 ? { fileField: args.fileField } : {},
7812
6214
  ...args.recipient !== void 0 ? { recipient: args.recipient } : {},
7813
6215
  ...args.maxAmount !== void 0 ? { maxAmount: args.maxAmount } : {}
7814
6216
  },
@@ -7845,7 +6247,6 @@ function createServer2(cfg, auth, trace, options = {}) {
7845
6247
  return await suipayLogin(
7846
6248
  {
7847
6249
  label: typeof args.label === "string" ? args.label : void 0,
7848
- target: typeof args.target === "string" ? args.target : void 0,
7849
6250
  gatewayResourceUrl: typeof args.gatewayResourceUrl === "string" ? args.gatewayResourceUrl : void 0
7850
6251
  },
7851
6252
  cfg,
@@ -8344,7 +6745,7 @@ async function handleSuipayMcpHttpRequest(req, cfg, auth, trace, options) {
8344
6745
  sessionIdGenerator: void 0,
8345
6746
  enableJsonResponse: true
8346
6747
  });
8347
- const server = createServer2(cfg, auth, hooks, options);
6748
+ const server = createServer(cfg, auth, hooks, options);
8348
6749
  await server.connect(transport);
8349
6750
  return transport.handleRequest(request);
8350
6751
  }
@@ -8353,15 +6754,10 @@ async function handleSuipayMcpHttpRequest(req, cfg, auth, trace, options) {
8353
6754
 
8354
6755
  export {
8355
6756
  resolveGrantTarget,
8356
- MCP_TARGET_PRESETS,
8357
- parseMcpTargetFlag,
8358
- resolveMcpTarget,
8359
- sameMcpGateway,
8360
6757
  loadMcpConfig,
8361
6758
  loadPaymentProfile,
8362
6759
  ensureLocalSigningKey,
8363
6760
  loadLocalSigner,
8364
- loadLocalSigningKeyRecord,
8365
6761
  resetAgentHeldProcessLedger,
8366
6762
  resolveAgentHeldSettlement,
8367
6763
  settlePreparedMcpPayment,
@@ -8372,7 +6768,7 @@ export {
8372
6768
  receipts,
8373
6769
  PACKAGE_NAME,
8374
6770
  TOOLS,
8375
- createServer2 as createServer,
6771
+ createServer,
8376
6772
  canonicalMcpRequestId,
8377
6773
  inspectMcpEnvelope,
8378
6774
  traceMcpHttpRequest,