@cmdoss/suipay-mcp 0.2.2-dev.3 → 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"
6034
- };
6035
- }
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 {
6159
- }
6160
- let preflightVerified = false;
6161
- let completed = false;
6162
- let error2 = null;
6163
- const done = new Promise((resolve) => {
6164
- const timer = setTimeout(() => {
6165
- if (!completed) {
6166
- error2 = new Error(`login timed out after ${timeoutMs}ms`);
6167
- server.close();
6168
- resolve();
6169
- }
6170
- }, timeoutMs);
6171
- timer.unref?.();
6172
- server.on("request", async (req, res) => {
6173
- const hostHeader = (req.headers.host ?? "").toLowerCase();
6174
- const origin = req.headers.origin;
6175
- const url = new URL(req.url ?? "/", `http://127.0.0.1:${port}`);
6176
- const okHost = allowedHosts.has(hostHeader);
6177
- let requestOrigin = null;
6178
- if (typeof origin === "string") {
6179
- try {
6180
- requestOrigin = new URL(origin).origin;
6181
- } catch {
6182
- requestOrigin = null;
6183
- }
6184
- }
6185
- const okOrigin = requestOrigin !== null && allowedCorsOrigins.has(requestOrigin);
6186
- const cors = {
6187
- "access-control-allow-origin": okOrigin && requestOrigin ? requestOrigin : expectedOrigin,
6188
- "access-control-allow-methods": "POST, OPTIONS",
6189
- "access-control-allow-headers": "content-type",
6190
- vary: "origin"
6191
- };
6192
- const send = (code, body, type = "text/plain", extra = {}) => {
6193
- res.writeHead(code, { "content-type": type, ...SECURITY_HEADERS, ...extra });
6194
- res.end(body);
6195
- };
6196
- const okPath = url.pathname === "/preflight" || url.pathname === "/callback";
6197
- if (req.method === "OPTIONS") {
6198
- const extra = { ...cors };
6199
- if (okHost && okOrigin && okPath && req.headers["access-control-request-private-network"] === "true") {
6200
- extra["access-control-allow-private-network"] = "true";
6201
- }
6202
- res.writeHead(204, { ...SECURITY_HEADERS, ...extra });
6203
- res.end();
6204
- return;
6205
- }
6206
- if (req.method !== "POST" || !okPath) {
6207
- send(404, "Not found");
6208
- return;
6209
- }
6210
- if (!okHost) {
6211
- send(403, "Forbidden");
6212
- return;
6213
- }
6214
- if (!okOrigin) {
6215
- send(403, "Forbidden");
6216
- return;
6217
- }
6218
- const ct = (req.headers["content-type"] ?? "").toLowerCase();
6219
- if (!ct.startsWith("application/json")) {
6220
- send(415, "Content-Type must be application/json");
6221
- return;
6222
- }
6223
- try {
6224
- const parsed = JSON.parse(await readBody2(req));
6225
- if (url.pathname === "/preflight") {
6226
- if (!isPreflight(parsed)) {
6227
- res.writeHead(400, {
6228
- "content-type": "application/json",
6229
- ...cors,
6230
- ...SECURITY_HEADERS
6231
- });
6232
- res.end(JSON.stringify({ ok: false, error: "invalid preflight payload" }));
6233
- return;
6234
- }
6235
- const derived = deriveSuiAddress(
6236
- Uint8Array.from(Buffer.from(parsed.publicKey, "hex"))
6237
- );
6238
- const valid = stateEquals(parsed.state, connectState) && stateEquals(
6239
- parsed.publicKey.toLowerCase(),
6240
- opts.identity.publicKeyHex.toLowerCase()
6241
- ) && parsed.delegateAddress.toLowerCase() === opts.identity.delegateAddress.toLowerCase() && derived.toLowerCase() === opts.identity.delegateAddress.toLowerCase() && normalizeUrl(parsed.gateway) === expectedGateway;
6242
- if (!valid) {
6243
- res.writeHead(403, {
6244
- "content-type": "application/json",
6245
- ...cors,
6246
- ...SECURITY_HEADERS
6247
- });
6248
- res.end(JSON.stringify({ ok: false, error: "preflight mismatch" }));
6249
- return;
6250
- }
6251
- preflightVerified = true;
6252
- res.writeHead(200, {
6253
- "content-type": "application/json",
6254
- ...cors,
6255
- ...SECURITY_HEADERS
6256
- });
6257
- res.end(
6258
- JSON.stringify({
6259
- ok: true,
6260
- publicKey: opts.identity.publicKeyHex,
6261
- label,
6262
- gateway: opts.gatewayUrl
6263
- })
6264
- );
6265
- return;
6266
- }
6267
- if (!preflightVerified) {
6268
- res.writeHead(428, {
6269
- "content-type": "text/html",
6270
- ...cors,
6271
- ...SECURITY_HEADERS
6272
- });
6273
- res.end(
6274
- failHtml("Local MCP bridge verification is required before the callback.")
6275
- );
6276
- return;
6277
- }
6278
- if (!isCallback(parsed)) {
6279
- res.writeHead(400, {
6280
- "content-type": "text/html",
6281
- ...cors,
6282
- ...SECURITY_HEADERS
6283
- });
6284
- res.end(failHtml("Callback payload missing required fields."));
6285
- return;
6286
- }
6287
- if (!stateEquals(parsed.state, connectState)) {
6288
- res.writeHead(403, {
6289
- "content-type": "text/html",
6290
- ...cors,
6291
- ...SECURITY_HEADERS
6292
- });
6293
- res.end(failHtml("Callback state mismatch \u2014 refusing to save."));
6294
- return;
6295
- }
6296
- if (normalizeUrl(parsed.gatewayUrl) !== expectedGateway) {
6297
- res.writeHead(403, {
6298
- "content-type": "text/html",
6299
- ...cors,
6300
- ...SECURITY_HEADERS
6301
- });
6302
- res.end(failHtml("Callback gateway mismatch \u2014 refusing to save."));
6303
- return;
6304
- }
6305
- preflightVerified = false;
6306
- persistGatewayUrl(opts.gatewayUrl);
6307
- completed = true;
6308
- res.writeHead(200, {
6309
- "content-type": "text/html",
6310
- ...cors,
6311
- ...SECURITY_HEADERS
6312
- });
6313
- res.end(SUCCESS_HTML);
6314
- setTimeout(() => server.close(), 100);
6315
- resolve();
6316
- } catch (err) {
6317
- error2 = err instanceof Error ? err : new Error(String(err));
6318
- res.writeHead(500, {
6319
- "content-type": "text/html",
6320
- ...cors,
6321
- ...SECURITY_HEADERS
6322
- });
6323
- res.end(failHtml("Internal error parsing callback."));
6324
- setTimeout(() => server.close(), 100);
6325
- resolve();
6326
- }
6327
- });
6328
- });
6329
- if (openBrowser) {
6330
- open(connectUrl).catch(() => {
6331
- });
6332
- }
6333
- void done.then(() => {
6334
- if (error2) {
5299
+ code: DELEGATED_PAY_ERROR_CODES.PREPARED_VERIFY_UNAVAILABLE,
5300
+ detail: err.message
5301
+ };
6335
5302
  }
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
+ };
5309
+ }
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
+ };
5318
+ }
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,249 +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 };
6873
- }
6874
-
6875
- // src/tool-result.ts
6876
- import { pathToFileURL } from "url";
6877
-
6878
- // src/delivery-file.ts
6879
- import { mkdirSync as mkdirSync2, writeFileSync, chmodSync as chmodSync2 } from "fs";
6880
- import { dirname as dirname2, join as join3 } from "path";
6881
- function mcpDeliveriesDir() {
6882
- return join3(dirname2(mcpCredentialsPath()), "deliveries");
6883
- }
6884
- var EXT = {
6885
- "image/png": ".png",
6886
- "image/jpeg": ".jpg",
6887
- "image/jpg": ".jpg",
6888
- "image/webp": ".webp",
6889
- "image/gif": ".gif",
6890
- "image/svg+xml": ".svg",
6891
- "audio/mpeg": ".mp3",
6892
- "audio/wav": ".wav",
6893
- "audio/ogg": ".ogg",
6894
- "video/mp4": ".mp4",
6895
- "application/pdf": ".pdf",
6896
- "application/json": ".json",
6897
- "text/plain": ".txt"
6898
- };
6899
- function extensionFor(contentType) {
6900
- const type = contentType.split(";")[0]?.trim().toLowerCase() ?? "";
6901
- return EXT[type] ?? ".bin";
6902
- }
6903
- function fileStem(digest, challengeId) {
6904
- const raw = (digest ?? challengeId ?? `delivery-${Date.now()}`).trim();
6905
- const safe = raw.replace(/[^A-Za-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "");
6906
- return (safe.slice(0, 80) || `delivery-${Date.now()}`).replace(/^\.+$/, "delivery");
6907
- }
6908
- function saveDeliveredBody(input) {
6909
- if (input.bytes.byteLength === 0) return void 0;
6910
- const dir = mcpDeliveriesDir();
6911
- mkdirSync2(dir, { recursive: true, mode: 448 });
6912
- const path = join3(
6913
- dir,
6914
- `${fileStem(input.digest, input.challengeId)}${extensionFor(input.contentType)}`
6915
- );
6916
- writeFileSync(path, input.bytes, { mode: 384 });
6917
- chmodSync2(path, 384);
6918
- return path;
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
+ };
6919
5689
  }
6920
-
6921
- // src/tool-result.ts
6922
- 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.";
6923
5691
  function text(value) {
6924
5692
  return {
6925
- content: [
6926
- {
6927
- type: "text",
6928
- text: typeof value === "string" ? value : JSON.stringify(value, null, 2)
6929
- }
6930
- ]
5693
+ content: [{
5694
+ type: "text",
5695
+ text: typeof value === "string" ? value : JSON.stringify(value, null, 2)
5696
+ }]
6931
5697
  };
6932
5698
  }
6933
5699
  function error(value) {
@@ -6939,11 +5705,7 @@ function closedError(code, detail) {
6939
5705
  function closedPayError(code, detail) {
6940
5706
  return error({ status: "failed", paid: false, code, detail });
6941
5707
  }
6942
- function receiptChallengeId(receipt) {
6943
- if (!receipt || typeof receipt !== "object") return void 0;
6944
- const id = receipt.challengeId;
6945
- return typeof id === "string" && id.length > 0 ? id : void 0;
6946
- }
5708
+ var MAX_MCP_IMAGE_BYTES = 5 * 1024 * 1024;
6947
5709
  function paidResult(result) {
6948
5710
  if (!isBinaryBody(result.body)) return text(result);
6949
5711
  const body = result.body;
@@ -6952,28 +5714,8 @@ function paidResult(result) {
6952
5714
  contentType: body.contentType,
6953
5715
  byteLength: body.byteLength
6954
5716
  };
6955
- let savedTo;
6956
- try {
6957
- savedTo = saveDeliveredBody({
6958
- bytes: body.bytes,
6959
- contentType: body.contentType,
6960
- digest: result.txDigest,
6961
- challengeId: receiptChallengeId(result.receipt)
6962
- });
6963
- } catch {
6964
- savedTo = void 0;
6965
- }
6966
- const payload = savedTo ? {
6967
- savedTo,
6968
- savedToUrl: pathToFileURL(savedTo).href,
6969
- ...result,
6970
- body: shape
6971
- } : { ...result, body: shape };
6972
5717
  const content = [
6973
- {
6974
- type: "text",
6975
- text: JSON.stringify(payload, null, 2)
6976
- }
5718
+ { type: "text", text: JSON.stringify({ ...result, body: shape }, null, 2) }
6977
5719
  ];
6978
5720
  if (body.contentType.startsWith("image/") && body.bytes.byteLength > 0 && body.bytes.byteLength <= MAX_MCP_IMAGE_BYTES) {
6979
5721
  content.push({
@@ -6984,28 +5726,6 @@ function paidResult(result) {
6984
5726
  }
6985
5727
  return { content };
6986
5728
  }
6987
- function settlePayResult(result) {
6988
- if (result.paid === true) return paidResult(result);
6989
- return error(result);
6990
- }
6991
-
6992
- // src/tools.ts
6993
- function payerTrace(deps) {
6994
- const hooks = deps.trace;
6995
- const buyerAccountId = deps.auth?.buyerAccountId;
6996
- if (!hooks || !buyerAccountId) return null;
6997
- return {
6998
- sink: hooks.sink,
6999
- traceId: hooks.context.traceId,
7000
- requestId: hooks.context.requestId,
7001
- buyerAccountId,
7002
- connectionId: hooks.runtime.connectionId,
7003
- ...hooks.runtime.secret ? { secret: hooks.runtime.secret } : {},
7004
- ...hooks.runtime.now ? { now: hooks.runtime.now } : {}
7005
- };
7006
- }
7007
- 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.";
7008
- var LOGIN_BG_TIMEOUT_MS = 5 * 6e4;
7009
5729
  function preparedResult(result) {
7010
5730
  return text({
7011
5731
  status: "prepared",
@@ -7025,7 +5745,7 @@ function normalizePayIntent(args) {
7025
5745
  if (!/^0x[0-9a-fA-F]{1,64}$/.test(raw)) {
7026
5746
  throw new Error("recipient must be a 0x-prefixed Sui address");
7027
5747
  }
7028
- const normalized = normalizeSuiAddress12(raw);
5748
+ const normalized = normalizeSuiAddress10(raw);
7029
5749
  if (!isValidSuiAddress(normalized) || /^0x0+$/.test(normalized)) {
7030
5750
  throw new Error("recipient must be a non-zero Sui address");
7031
5751
  }
@@ -7044,7 +5764,7 @@ function intentViolation(intent, prepared) {
7044
5764
  if (intent.recipient) {
7045
5765
  let preparedRecipient;
7046
5766
  try {
7047
- preparedRecipient = normalizeSuiAddress12(prepared.recipient);
5767
+ preparedRecipient = normalizeSuiAddress10(prepared.recipient);
7048
5768
  } catch {
7049
5769
  return "prepared payment does not name a valid recipient";
7050
5770
  }
@@ -7150,10 +5870,10 @@ async function pay(args, cfg, deps = {}) {
7150
5870
  ...deps.fetchImpl && !deps.agentHeld.payerDeps.fetchImpl ? { fetchImpl: deps.fetchImpl } : {}
7151
5871
  }
7152
5872
  });
7153
- return settlePayResult(settled);
5873
+ return settled.status === "ok" ? paidResult(settled) : error(settled);
7154
5874
  }
7155
5875
  if (result.status === "prepared") return preparedResult(result);
7156
- return settlePayResult(result);
5876
+ return result.status === "ok" ? paidResult(result) : error(result);
7157
5877
  }
7158
5878
  if (deps.auth) {
7159
5879
  return error(
@@ -7168,259 +5888,122 @@ async function pay(args, cfg, deps = {}) {
7168
5888
  }
7169
5889
  const local = loadLocalSigner();
7170
5890
  if (!local) return error(AUTH_REQUIRED);
7171
- return payStdioWithLocalKey({ request, intent, cfg, deps });
5891
+ return payStdioWithLocalKey({ request, intent, cfg, deps, local });
7172
5892
  }
7173
- function stdioGatewayUrl(cfg) {
7174
- 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
+ };
7175
5906
  }
7176
- function isExpiredBootstrapGrant(expiresAtMs, nowMs) {
7177
- if (expiresAtMs === void 0 || expiresAtMs === null || expiresAtMs === "" || expiresAtMs === "0" || expiresAtMs === 0) {
7178
- return false;
7179
- }
7180
- const exp = typeof expiresAtMs === "number" ? expiresAtMs : typeof expiresAtMs === "string" && /^\d+$/.test(expiresAtMs) ? Number(expiresAtMs) : NaN;
7181
- if (!Number.isFinite(exp) || exp <= 0) return false;
7182
- return exp <= nowMs;
7183
- }
7184
- function grantObjectIdFromBootstrap(grants, nowMs) {
7185
- let named = 0;
7186
- const live = [];
7187
- for (const grant of grants) {
7188
- if (!grant || typeof grant !== "object") continue;
7189
- const g = grant;
7190
- const id = g.grantObjectId;
7191
- if (typeof id !== "string" || !id.trim()) continue;
7192
- named += 1;
7193
- if (isExpiredBootstrapGrant(g.expiresAtMs, nowMs)) continue;
7194
- live.push(id.trim());
7195
- }
7196
- if (live.length === 1) return { kind: "live", grantObjectId: live[0] };
7197
- if (live.length > 1) return { kind: "ambiguous" };
7198
- if (named > 0) return { kind: "absent" };
7199
- if (grants.length > 0) return { kind: "unknown", detail: "delegate grants list was not usable" };
7200
- return { kind: "absent" };
7201
- }
7202
- async function checkLiveDelegateGrant(args) {
7203
- const deployment = await fetchGatewayDeployment({
7204
- gatewayUrl: args.gatewayUrl,
7205
- ...args.fetchImpl ? { fetchImpl: args.fetchImpl } : {}
7206
- });
7207
- if (!deployment.ok) {
7208
- return {
7209
- kind: "unknown",
7210
- detail: "gateway deployment identity could not be verified"
7211
- };
7212
- }
7213
- const listed = await bootstrapPayerGrants({
7214
- gatewayUrl: args.gatewayUrl,
7215
- context: {
7216
- network: deployment.value.network,
7217
- packageId: deployment.value.packageId,
7218
- 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
7219
5917
  },
7220
- signPersonalMessage: args.signPersonalMessage,
7221
- ...args.fetchImpl ? { fetchImpl: args.fetchImpl } : {}
5918
+ ...deps.fetchImpl ? { fetchImpl: deps.fetchImpl } : {}
7222
5919
  });
7223
- if (!listed.ok) {
7224
- return {
7225
- kind: "unknown",
7226
- detail: `delegate grants read ${listed.error} (HTTP ${listed.status})`
7227
- };
7228
- }
7229
- if (!Array.isArray(listed.data.grants) || listed.data.grants.length === 0) {
7230
- return { kind: "absent" };
7231
- }
7232
- return grantObjectIdFromBootstrap(listed.data.grants, Date.now());
7233
- }
7234
- async function payStdioWithLocalKey(args) {
7235
- const { request, intent, cfg, deps } = args;
7236
- const built = await stdioPayerAndReader(cfg, deps);
7237
- if (!built.ok) return built.result;
7238
- const payer = built.payer;
7239
- const grantCeiling = payer.grant.maxPerPayment;
7240
- const maxAmount = intent.maxAmount !== void 0 ? intent.maxAmount.toString() : grantCeiling != null && grantCeiling !== "" ? grantCeiling : null;
7241
- if (maxAmount === null) {
7242
- return error({
7243
- status: "failed",
7244
- paid: false,
7245
- code: "POLICY_REJECTED",
7246
- detail: "stdio pay requires maxAmount or a grant maxPerPayment; refusing to default the ceiling to the offer"
7247
- });
5920
+ if (!resolved.ok) {
5921
+ return closedPayError(resolved.error.code, resolved.error.detail);
7248
5922
  }
7249
- const settled = await payer.pay(request.url, {
7250
- method: request.method,
7251
- ...request.method === "POST" ? { body: request.body } : {},
7252
- ...request.contentType ? { contentType: request.contentType } : {},
7253
- intent: {
7254
- maxAmount,
7255
- asset: payer.grant.coinType,
7256
- ...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 } : {}
7257
5937
  }
7258
5938
  });
7259
- return settlePayResult(settled);
7260
- }
7261
- async function stdioPayerAndReader(cfg, deps) {
7262
- const keypair = loadLocalKeypair();
7263
- if (!keypair) return { ok: false, result: error(AUTH_REQUIRED) };
7264
- const gatewayUrl = stdioGatewayUrl(cfg);
7265
- const local = loadLocalSigner();
7266
- let select;
7267
- const shouldSelect = Boolean(local) && (deps.fetchImpl !== void 0 || deps.createPayer === void 0);
7268
- if (local && shouldSelect) {
7269
- const check = await checkLiveDelegateGrant({
7270
- gatewayUrl,
7271
- delegateAddress: local.address,
7272
- signPersonalMessage: local.signPersonalMessage,
7273
- ...deps.fetchImpl ? { fetchImpl: deps.fetchImpl } : {}
7274
- });
7275
- if (check.kind === "ambiguous") {
7276
- return {
7277
- ok: false,
7278
- result: closedError(
7279
- "AMBIGUOUS_GRANT",
7280
- "multiple live grants name this key; pass a selector (grantObjectId / asset / coinType)"
7281
- )
7282
- };
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
+ });
7283
5948
  }
7284
- if (check.kind === "live") select = { grantObjectId: check.grantObjectId };
7285
- }
7286
- const built = await (deps.createPayer ?? createPayer)({
7287
- gatewayUrl,
7288
- keypair,
7289
- ...select ? { select } : {},
7290
- ...deps.fetchImpl ? { fetchImpl: deps.fetchImpl } : {},
7291
- ...cfg.rpcUrl ? { fullnodeUrl: cfg.rpcUrl } : {}
7292
- });
7293
- if (!built.ok) {
7294
- return {
7295
- ok: false,
7296
- 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 } : {}
7297
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);
7298
5987
  }
7299
- return { ok: true, payer: built.value };
7300
- }
7301
- function stdioReaderForPayer(cfg, deps, payer) {
7302
- return (deps.createReader ?? createReader)({
7303
- gatewayUrl: stdioGatewayUrl(cfg),
7304
- context: {
7305
- network: payer.deployment.network,
7306
- packageId: payer.deployment.packageId,
7307
- grantObjectId: payer.grant.grantObjectId,
7308
- delegateAddress: payer.address
7309
- },
7310
- signPersonalMessage: async (message) => {
7311
- const { signature } = await loadLocalKeypair().signPersonalMessage(message);
7312
- return signature;
7313
- },
7314
- ...deps.fetchImpl ? { fetchImpl: deps.fetchImpl } : {}
7315
- });
7316
- }
7317
- function budgetFromBalance(balance, grant) {
7318
- if (!balance.ok || !balance.data) return void 0;
7319
- return {
7320
- sessionRemaining: balance.data.sessionRemaining ?? null,
7321
- maxPerPayment: balance.data.maxPerPayment ?? grant.maxPerPayment ?? null,
7322
- coinType: balance.data.coinType ?? grant.coinType ?? null
7323
- };
7324
- }
7325
- async function fetchPublicCatalog(gatewayUrl, fetchImpl) {
7326
- const catalogUrl = new URL("/v1/services", gatewayUrl);
7327
- const response = await (fetchImpl ?? fetch)(catalogUrl);
7328
- if (!response.ok) return { ok: false, result: error(`gateway unavailable: HTTP ${response.status}`) };
7329
- return { ok: true, catalog: await response.json() };
7330
- }
7331
- async function publicCatalog(gatewayUrl, query, fetchImpl) {
7332
- const loaded = await fetchPublicCatalog(gatewayUrl, fetchImpl);
7333
- if (!loaded.ok) return loaded.result;
7334
- return text(
7335
- joinDiscoverCatalog({
7336
- catalog: loaded.catalog,
7337
- gatewayUrl,
7338
- query
7339
- })
7340
- );
5988
+ return result.status === "ok" ? paidResult(result) : error(result);
7341
5989
  }
7342
5990
  async function discover(args, cfg, deps = {}) {
7343
- if (deps.auth || !loadLocalKeypair()) {
7344
- 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
+ );
7345
6003
  }
7346
- const local = loadLocalSigner();
7347
- if (local) {
7348
- const check = await checkLiveDelegateGrant({
7349
- gatewayUrl: stdioGatewayUrl(cfg),
7350
- delegateAddress: local.address,
7351
- signPersonalMessage: local.signPersonalMessage,
7352
- ...deps.fetchImpl ? { fetchImpl: deps.fetchImpl } : {}
7353
- });
7354
- if (check.kind !== "live") {
7355
- return publicCatalog(stdioGatewayUrl(cfg), args.query, deps.fetchImpl);
7356
- }
7357
- }
7358
- const resolved = await stdioPayerAndReader(cfg, deps);
7359
- if (!resolved.ok) return resolved.result;
7360
- const gatewayUrl = stdioGatewayUrl(cfg);
7361
- const loaded = await fetchPublicCatalog(gatewayUrl, deps.fetchImpl);
7362
- if (!loaded.ok) return loaded.result;
7363
- const scoped = resolved.payer.grant.accessMode === "scoped";
7364
- const reader = stdioReaderForPayer(cfg, deps, resolved.payer);
7365
- const [policies, balance] = await Promise.all([
7366
- scoped ? reader.policies() : Promise.resolve({ ok: true, data: { policies: void 0 } }),
7367
- reader.balance()
7368
- ]);
7369
- if (scoped && !policies.ok) {
7370
- return closedError("POLICIES_UNAVAILABLE", "grant policies could not be loaded");
7371
- }
7372
- return text(
7373
- joinDiscoverCatalog({
7374
- catalog: loaded.catalog,
7375
- gatewayUrl,
7376
- query: args.query,
7377
- ...scoped && policies.ok ? { policies: policies.data.policies } : {},
7378
- budget: budgetFromBalance(balance, resolved.payer.grant)
7379
- })
7380
- );
7381
- }
7382
- async function accessContext(deps = {}, cfg) {
7383
- if (deps.auth) {
7384
- if (!deps.auth.access) {
7385
- return error(
7386
- "SuiPay access context unavailable. Reconnect this MCP session or inspect the connection in buyer console."
7387
- );
7388
- }
7389
- return text(deps.auth.access);
7390
- }
7391
- if (!cfg) return error(AUTH_REQUIRED);
7392
- const resolved = await stdioPayerAndReader(cfg, deps);
7393
- if (!resolved.ok) return resolved.result;
7394
- const gatewayUrl = stdioGatewayUrl(cfg);
7395
- const reader = stdioReaderForPayer(cfg, deps, resolved.payer);
7396
- const scoped = resolved.payer.grant.accessMode === "scoped";
7397
- const [policies, balance, loaded] = await Promise.all([
7398
- reader.policies(),
7399
- reader.balance(),
7400
- fetchPublicCatalog(gatewayUrl, deps.fetchImpl)
7401
- ]);
7402
- const budget = budgetFromBalance(balance, resolved.payer.grant);
7403
- const joined = loaded.ok && (!scoped || policies.ok) ? joinDiscoverCatalog({
7404
- catalog: loaded.catalog,
7405
- gatewayUrl,
7406
- ...scoped && policies.ok ? { policies: policies.data.policies } : {},
7407
- budget
7408
- }) : null;
7409
- return text({
7410
- accessMode: resolved.payer.grant.accessMode,
7411
- grant: {
7412
- id: resolved.payer.grant.grantObjectId,
7413
- poolObjectId: resolved.payer.grant.poolObjectId,
7414
- coinType: resolved.payer.grant.coinType,
7415
- maxPerPayment: resolved.payer.grant.maxPerPayment ?? null,
7416
- sessionCap: resolved.payer.grant.sessionCap ?? null
7417
- },
7418
- policies: policies.ok ? policies.data : { error: policies.error },
7419
- balance: balance.ok ? balance.data : { error: balance.error },
7420
- ...joined ? { services: joined.services, count: joined.count } : {}
7421
- });
6004
+ return text(deps.auth.access);
7422
6005
  }
7423
- async function receipts(args, cfg, deps = {}) {
6006
+ async function receipts(args, _cfg, deps = {}) {
7424
6007
  if (deps.auth) {
7425
6008
  if (!deps.auth.listReceipts) {
7426
6009
  return error(
@@ -7434,144 +6017,28 @@ async function receipts(args, cfg, deps = {}) {
7434
6017
  return closedError("RECEIPTS_UNAVAILABLE", "receipts could not be loaded");
7435
6018
  }
7436
6019
  }
7437
- if (profile(deps)) {
7438
- return error(
7439
- "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"
7440
- );
7441
- }
7442
- const resolved = await stdioPayerAndReader(cfg, deps);
7443
- if (!resolved.ok) return resolved.result;
7444
- const reader = (deps.createReader ?? createReader)({
7445
- gatewayUrl: stdioGatewayUrl(cfg),
7446
- context: {
7447
- network: resolved.payer.deployment.network,
7448
- packageId: resolved.payer.deployment.packageId,
7449
- grantObjectId: resolved.payer.grant.grantObjectId,
7450
- delegateAddress: resolved.payer.address
7451
- },
7452
- signPersonalMessage: async (message) => {
7453
- const { signature } = await loadLocalKeypair().signPersonalMessage(message);
7454
- return signature;
7455
- },
7456
- ...deps.fetchImpl ? { fetchImpl: deps.fetchImpl } : {}
7457
- });
7458
- const listed = await reader.receipts();
7459
- if (!listed.ok) {
7460
- return closedError("RECEIPTS_UNAVAILABLE", "receipts could not be loaded");
7461
- }
7462
- const receiptsOut = listed.data.receipts;
7463
- const filtered = args.challengeId && Array.isArray(receiptsOut) ? receiptsOut.filter((row) => {
7464
- if (!row || typeof row !== "object") return false;
7465
- const rec = row;
7466
- return rec.challengeId === args.challengeId || rec.receipt?.challengeId === args.challengeId;
7467
- }) : receiptsOut;
7468
- return text({ count: Array.isArray(filtered) ? filtered.length : 0, receipts: filtered });
7469
- }
7470
- function parseLoginTarget(value) {
7471
- if (value === void 0 || value === null || value === "") return void 0;
7472
- if (typeof value !== "string") return { error: 'target must be "dev" or "local"' };
7473
- const target = value.trim().toLowerCase();
7474
- if (target === "dev" || target === "local") return target;
7475
- 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
+ );
7476
6024
  }
7477
6025
  async function suipayLogin(args, cfg, deps = {}) {
7478
- const parsedTarget = parseLoginTarget(args.target);
7479
- if (parsedTarget && typeof parsedTarget === "object" && "error" in parsedTarget) {
7480
- return error({
7481
- status: "failed",
7482
- code: "INVALID_TARGET",
7483
- detail: parsedTarget.error
7484
- });
7485
- }
7486
- const target = parsedTarget;
7487
6026
  const label = args.label?.trim() || cfg.label.trim();
7488
6027
  const identity = await ensureLocalSigningKey(label ? { label } : {});
7489
- const local = loadLocalSigner();
7490
- const stored = loadLocalSigningKeyRecord();
7491
- const endpoints = target ? MCP_TARGET_PRESETS[target] : {
7492
- consoleUrl: cfg.consoleUrl,
7493
- gatewayUrl: stored?.gatewayUrl ?? cfg.gatewayUrl
7494
- };
7495
- if (local) {
7496
- const live = await checkLiveDelegateGrant({
7497
- gatewayUrl: endpoints.gatewayUrl,
7498
- delegateAddress: local.address,
7499
- signPersonalMessage: local.signPersonalMessage,
7500
- ...deps.fetchImpl ? { fetchImpl: deps.fetchImpl } : {}
7501
- });
7502
- if (live.kind === "ambiguous") {
7503
- return error({
7504
- status: "failed",
7505
- code: "AMBIGUOUS_GRANT",
7506
- detail: "multiple live grants name this key; revoke extras or pass a selector before paying"
7507
- });
7508
- }
7509
- if (live.kind === "live") {
7510
- const storedUrl = stored?.gatewayUrl;
7511
- if (!storedUrl || storedUrl !== endpoints.gatewayUrl && (sameMcpGateway(storedUrl, endpoints.gatewayUrl) || target !== void 0)) {
7512
- persistGatewayUrl(endpoints.gatewayUrl);
7513
- }
7514
- return text({
7515
- status: "ok",
7516
- reused: true,
7517
- target: target ?? cfg.target,
7518
- delegateAddress: identity.delegateAddress,
7519
- gatewayUrl: loadLocalSigningKeyRecord()?.gatewayUrl ?? endpoints.gatewayUrl,
7520
- grantObjectId: live.grantObjectId,
7521
- message: "Existing spend-account grant is live for this key. No browser round-trip."
7522
- });
7523
- }
7524
- if (live.kind === "unknown") {
7525
- return error({
7526
- status: "failed",
7527
- code: "GRANT_STATUS_UNKNOWN",
7528
- 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)."
7529
- });
7530
- }
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 {
7531
6035
  }
7532
- const started = await (deps.startStdioLogin ?? startStdioLogin)({
7533
- consoleUrl: endpoints.consoleUrl,
7534
- gatewayUrl: endpoints.gatewayUrl,
7535
- identity,
7536
- label,
7537
- timeoutMs: LOGIN_BG_TIMEOUT_MS,
7538
- openBrowser: false,
7539
- 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."
7540
6041
  });
7541
- const url = started.connectUrl;
7542
- return text(
7543
- [
7544
- `## ACTION REQUIRED: User must click this URL to sign in`,
7545
- ``,
7546
- `**Target:** ${target ?? cfg.target ?? "custom"} \xB7 ${endpoints.gatewayUrl}`,
7547
- ``,
7548
- `**URL:** ${url}`,
7549
- ``,
7550
- "```",
7551
- url,
7552
- "```",
7553
- ``,
7554
- `[Click here to open SuiPay sign-in](${url})`,
7555
- ``,
7556
- `**IMPORTANT for the assistant**: do NOT summarize or omit the URL above.`,
7557
- `The user CANNOT proceed without seeing the exact URL. Surface it verbatim.`,
7558
- ``,
7559
- `1. Open the URL in any browser`,
7560
- `2. Connect the owner wallet and approve the spend-account grant (Access keys / policies)`,
7561
- `3. Once Connected appears, retry the original request \u2014 credentials at \`~/.suipay/credentials.json\` are picked up on the next tool call`,
7562
- ``,
7563
- `_The login link stays valid for 5 minutes. If it expires, call \`suipay_login\` again._`,
7564
- ``,
7565
- JSON.stringify({
7566
- status: "ok",
7567
- target: target ?? cfg.target,
7568
- delegateAddress: identity.delegateAddress,
7569
- connectUrl: url,
7570
- consoleUrl: endpoints.consoleUrl,
7571
- gatewayUrl: endpoints.gatewayUrl
7572
- })
7573
- ].join("\n")
7574
- );
7575
6042
  }
7576
6043
  async function logout(args, _cfg, deps = {}) {
7577
6044
  if (args.force) {
@@ -7597,7 +6064,7 @@ var TOOLS = [
7597
6064
  },
7598
6065
  {
7599
6066
  name: "pay",
7600
- description: "Pay a HTTP resource via delegate. On hosted /mcp the gateway PREPARES an unsigned transaction and returns it (prepare-only \u2014 it never holds the delegate key and cannot settle). Only a process holding the delegate key the grant names can complete settlement (e.g. local stdio MCP, or any self-hosted process with the matching key). Pass recipient and/or maxAmount to state who you intend to pay and what you will spend; both are checked before anything is signed or handed back. Local V1 allowance pay is retired. A paid binary body is written under ~/.suipay/deliveries/ and named as savedTo (absolute path) and savedToUrl (file://) at the top of the JSON. Always quote savedTo to the user so they can open the file. Do not assume the host rendered an image block. Never call pay again to display or save a delivery that already settled.",
6067
+ description: "Pay a HTTP resource via delegate. On hosted /mcp the gateway PREPARES an unsigned transaction and returns it (prepare-only \u2014 it never holds the delegate key and cannot settle). Only a process holding the delegate key the grant names can complete settlement (e.g. local stdio MCP, or any self-hosted process with the matching key). Pass recipient and/or maxAmount to state who you intend to pay and what you will spend; both are checked before anything is signed or handed back. Local V1 allowance pay is retired.",
7601
6068
  inputSchema: {
7602
6069
  type: "object",
7603
6070
  properties: {
@@ -7625,7 +6092,7 @@ var TOOLS = [
7625
6092
  },
7626
6093
  {
7627
6094
  name: "discover",
7628
- 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.",
7629
6096
  inputSchema: {
7630
6097
  type: "object",
7631
6098
  properties: {
@@ -7651,16 +6118,11 @@ var TOOLS = [
7651
6118
  },
7652
6119
  {
7653
6120
  name: "suipay_login",
7654
- 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.",
7655
6122
  inputSchema: {
7656
6123
  type: "object",
7657
6124
  properties: {
7658
6125
  label: { type: "string", description: "Label shown in SuiPay console." },
7659
- target: {
7660
- type: "string",
7661
- enum: ["dev", "local"],
7662
- description: 'Where to bind the grant. Default is Railway suipay-dev. Pass "local" for localhost:3000 / 127.0.0.1:4340.'
7663
- },
7664
6126
  gatewayResourceUrl: {
7665
6127
  type: "string",
7666
6128
  description: "Gateway resource URL whose 402 defines recipient, asset, and package."
@@ -7716,7 +6178,7 @@ function toolError(value) {
7716
6178
  isError: true
7717
6179
  };
7718
6180
  }
7719
- function createServer2(cfg, auth, trace, options = {}) {
6181
+ function createServer(cfg, auth, trace, options = {}) {
7720
6182
  const resolveAgentHeld = options.resolveAgentHeld ?? resolveAgentHeldSettlement;
7721
6183
  const server = new Server(
7722
6184
  { name: "suipay", version: "1.0.0" },
@@ -7741,7 +6203,7 @@ function createServer2(cfg, auth, trace, options = {}) {
7741
6203
  }
7742
6204
  switch (name) {
7743
6205
  case "access_context":
7744
- return await accessContext({ auth }, cfg);
6206
+ return await accessContext({ auth });
7745
6207
  case "pay": {
7746
6208
  const agentHeld = auth?.spendAccountPay ? resolveAgentHeld(cfg, auth.spendAccountPay) : null;
7747
6209
  return await pay(
@@ -7785,7 +6247,6 @@ function createServer2(cfg, auth, trace, options = {}) {
7785
6247
  return await suipayLogin(
7786
6248
  {
7787
6249
  label: typeof args.label === "string" ? args.label : void 0,
7788
- target: typeof args.target === "string" ? args.target : void 0,
7789
6250
  gatewayResourceUrl: typeof args.gatewayResourceUrl === "string" ? args.gatewayResourceUrl : void 0
7790
6251
  },
7791
6252
  cfg,
@@ -8284,7 +6745,7 @@ async function handleSuipayMcpHttpRequest(req, cfg, auth, trace, options) {
8284
6745
  sessionIdGenerator: void 0,
8285
6746
  enableJsonResponse: true
8286
6747
  });
8287
- const server = createServer2(cfg, auth, hooks, options);
6748
+ const server = createServer(cfg, auth, hooks, options);
8288
6749
  await server.connect(transport);
8289
6750
  return transport.handleRequest(request);
8290
6751
  }
@@ -8293,15 +6754,10 @@ async function handleSuipayMcpHttpRequest(req, cfg, auth, trace, options) {
8293
6754
 
8294
6755
  export {
8295
6756
  resolveGrantTarget,
8296
- MCP_TARGET_PRESETS,
8297
- parseMcpTargetFlag,
8298
- resolveMcpTarget,
8299
- sameMcpGateway,
8300
6757
  loadMcpConfig,
8301
6758
  loadPaymentProfile,
8302
6759
  ensureLocalSigningKey,
8303
6760
  loadLocalSigner,
8304
- loadLocalSigningKeyRecord,
8305
6761
  resetAgentHeldProcessLedger,
8306
6762
  resolveAgentHeldSettlement,
8307
6763
  settlePreparedMcpPayment,
@@ -8312,7 +6768,7 @@ export {
8312
6768
  receipts,
8313
6769
  PACKAGE_NAME,
8314
6770
  TOOLS,
8315
- createServer2 as createServer,
6771
+ createServer,
8316
6772
  canonicalMcpRequestId,
8317
6773
  inspectMcpEnvelope,
8318
6774
  traceMcpHttpRequest,