@cmdoss/suipay-mcp 0.2.2-dev.7 → 0.2.2-dev.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -4
- package/dist/bin/suipay.js +2 -2
- package/dist/{chunk-KR6WRUDY.js → chunk-LQOZBRH6.js} +1 -1
- package/dist/{chunk-6ZSOXII5.js → chunk-NDJ7BFI4.js} +273 -54
- package/dist/{http-DggnqM91.d.ts → http-RP-Xlg60.d.ts} +15 -5
- package/dist/http.d.ts +1 -1
- package/dist/http.js +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/package.json +15 -15
package/README.md
CHANGED
|
@@ -5,13 +5,13 @@ buyer-funded spend account. The buyer owns the account. The agent holds the
|
|
|
5
5
|
delegate key and can spend only under the on-chain grant (recipient bind,
|
|
6
6
|
per-payment cap, budget).
|
|
7
7
|
|
|
8
|
-
Pin `@cmdoss/suipay-mcp@0.2.2` in MCP config. `1.0.1` remains on the
|
|
8
|
+
Pin `@cmdoss/suipay-mcp@0.2.2-dev.9` in MCP config. `1.0.1` remains on the
|
|
9
9
|
registry as a historical release; do not install it for new agents.
|
|
10
10
|
|
|
11
11
|
## Install
|
|
12
12
|
|
|
13
13
|
```bash
|
|
14
|
-
claude mcp add --scope user suipay -- npx -y @cmdoss/suipay-mcp@0.2.2
|
|
14
|
+
claude mcp add --scope user suipay -- npx -y @cmdoss/suipay-mcp@0.2.2-dev.9
|
|
15
15
|
```
|
|
16
16
|
|
|
17
17
|
No wallet secret belongs in this command or MCP config. There is no
|
|
@@ -112,10 +112,10 @@ Default is Railway **suipay-dev**. `--local` / `target: "local"` is laptop-only.
|
|
|
112
112
|
|
|
113
113
|
```bash
|
|
114
114
|
# Railway suipay-dev (default)
|
|
115
|
-
npx @cmdoss/suipay-mcp@0.2.2
|
|
115
|
+
npx @cmdoss/suipay-mcp@0.2.2-dev.9
|
|
116
116
|
|
|
117
117
|
# Laptop stack
|
|
118
|
-
npx @cmdoss/suipay-mcp@0.2.2 --local
|
|
118
|
+
npx @cmdoss/suipay-mcp@0.2.2-dev.9 --local
|
|
119
119
|
```
|
|
120
120
|
|
|
121
121
|
`SUIPAY_MCP_TARGET=local|dev` selects the same preset. Explicit
|
package/dist/bin/suipay.js
CHANGED
|
@@ -538,6 +538,32 @@ function createSpendAccountChain(options) {
|
|
|
538
538
|
kindBytes: toBase64(await tx.build({ client, onlyTransactionKind: true }))
|
|
539
539
|
};
|
|
540
540
|
}
|
|
541
|
+
function appendCreatePolicyGrant(tx, input) {
|
|
542
|
+
if (!input.onChainPolicyIds.length) {
|
|
543
|
+
throw new Error("create_grant requires at least one on-chain policy id");
|
|
544
|
+
}
|
|
545
|
+
if (!input.maxPerPayment || BigInt(input.maxPerPayment) <= 0n) {
|
|
546
|
+
throw new Error("create_grant requires a non-zero max_per_payment");
|
|
547
|
+
}
|
|
548
|
+
if (!input.sessionCap || BigInt(input.sessionCap) <= 0n) {
|
|
549
|
+
throw new Error("create_grant requires a non-zero session_cap");
|
|
550
|
+
}
|
|
551
|
+
const expires = input.expiresAtMs === void 0 || input.expiresAtMs === null ? 0n : BigInt(input.expiresAtMs);
|
|
552
|
+
tx.moveCall({
|
|
553
|
+
target: target("create_policy_grant"),
|
|
554
|
+
typeArguments: [input.coinType],
|
|
555
|
+
arguments: [
|
|
556
|
+
tx.object(input.poolObjectId),
|
|
557
|
+
tx.pure.address(input.delegate),
|
|
558
|
+
// The unified Grant<T> takes Option<u64> caps. This builder rejects
|
|
559
|
+
// absent or zero caps above, so both are always Some here.
|
|
560
|
+
tx.pure.option("u64", BigInt(input.sessionCap)),
|
|
561
|
+
tx.pure.option("u64", BigInt(input.maxPerPayment)),
|
|
562
|
+
tx.pure.u64(expires),
|
|
563
|
+
tx.pure.vector("u64", input.onChainPolicyIds.map((id) => BigInt(id)))
|
|
564
|
+
]
|
|
565
|
+
});
|
|
566
|
+
}
|
|
541
567
|
async function fundingCoin(tx, owner, coinType, amount) {
|
|
542
568
|
if (!isSuiCoinType(coinType)) {
|
|
543
569
|
return coinWithBalance({ type: coinType, balance: amount });
|
|
@@ -737,32 +763,23 @@ function createSpendAccountChain(options) {
|
|
|
737
763
|
return kindBytes(tx);
|
|
738
764
|
},
|
|
739
765
|
async buildCreateGrantKind(input) {
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
766
|
+
const tx = new Transaction2();
|
|
767
|
+
tx.setSender(input.owner);
|
|
768
|
+
appendCreatePolicyGrant(tx, input);
|
|
769
|
+
return kindBytes(tx);
|
|
770
|
+
},
|
|
771
|
+
async buildCreateGrantBatchKind(inputs) {
|
|
772
|
+
if (!inputs.length) {
|
|
773
|
+
throw new Error("create_grant batch requires at least one grant");
|
|
745
774
|
}
|
|
746
|
-
|
|
747
|
-
|
|
775
|
+
const owner = inputs[0].owner;
|
|
776
|
+
if (inputs.some((input) => input.owner !== owner)) {
|
|
777
|
+
throw new Error("create_grant batch requires one owner");
|
|
748
778
|
}
|
|
749
779
|
const tx = new Transaction2();
|
|
750
|
-
tx.setSender(
|
|
751
|
-
const
|
|
752
|
-
|
|
753
|
-
target: target("create_policy_grant"),
|
|
754
|
-
typeArguments: [input.coinType],
|
|
755
|
-
arguments: [
|
|
756
|
-
tx.object(input.poolObjectId),
|
|
757
|
-
tx.pure.address(input.delegate),
|
|
758
|
-
// The unified Grant<T> takes Option<u64> caps. This builder rejects
|
|
759
|
-
// absent or zero caps above, so both are always Some here.
|
|
760
|
-
tx.pure.option("u64", BigInt(input.sessionCap)),
|
|
761
|
-
tx.pure.option("u64", BigInt(input.maxPerPayment)),
|
|
762
|
-
tx.pure.u64(expires),
|
|
763
|
-
tx.pure.vector("u64", input.onChainPolicyIds.map((id) => BigInt(id)))
|
|
764
|
-
]
|
|
765
|
-
});
|
|
780
|
+
tx.setSender(owner);
|
|
781
|
+
for (const input of inputs)
|
|
782
|
+
appendCreatePolicyGrant(tx, input);
|
|
766
783
|
return kindBytes(tx);
|
|
767
784
|
},
|
|
768
785
|
async buildCreateSpendGrantKind(input) {
|
|
@@ -5563,7 +5580,7 @@ var MCP_TARGET_PRESETS = {
|
|
|
5563
5580
|
gatewayUrl: "https://gateway-dev-ba84.up.railway.app"
|
|
5564
5581
|
},
|
|
5565
5582
|
local: {
|
|
5566
|
-
consoleUrl: "
|
|
5583
|
+
consoleUrl: "https://suipay-dev.up.railway.app",
|
|
5567
5584
|
gatewayUrl: "http://127.0.0.1:4340"
|
|
5568
5585
|
}
|
|
5569
5586
|
};
|
|
@@ -5675,6 +5692,35 @@ function loadMcpConfig(env = process.env, argv = []) {
|
|
|
5675
5692
|
exclusionDirectoryObjectId: rawDir || null
|
|
5676
5693
|
};
|
|
5677
5694
|
}
|
|
5695
|
+
var OBJECT_ID = /^0x(?:[0-9a-fA-F]{1,64})$/;
|
|
5696
|
+
async function publishedExclusionDirectoryId(gatewayUrl, fetchImpl = fetch) {
|
|
5697
|
+
let url;
|
|
5698
|
+
try {
|
|
5699
|
+
url = new URL("/v1/delegate/deployment", gatewayUrl).toString();
|
|
5700
|
+
} catch {
|
|
5701
|
+
return null;
|
|
5702
|
+
}
|
|
5703
|
+
let response;
|
|
5704
|
+
try {
|
|
5705
|
+
response = await fetchImpl(url, {
|
|
5706
|
+
method: "GET",
|
|
5707
|
+
redirect: "manual",
|
|
5708
|
+
signal: AbortSignal.timeout(1500)
|
|
5709
|
+
});
|
|
5710
|
+
} catch {
|
|
5711
|
+
return null;
|
|
5712
|
+
}
|
|
5713
|
+
if (response.status !== 200) return null;
|
|
5714
|
+
let body;
|
|
5715
|
+
try {
|
|
5716
|
+
body = await response.json();
|
|
5717
|
+
} catch {
|
|
5718
|
+
return null;
|
|
5719
|
+
}
|
|
5720
|
+
const id = body && typeof body === "object" && "exclusionDirectoryObjectId" in body ? body.exclusionDirectoryObjectId : void 0;
|
|
5721
|
+
if (typeof id !== "string" || !OBJECT_ID.test(id.trim())) return null;
|
|
5722
|
+
return id.trim();
|
|
5723
|
+
}
|
|
5678
5724
|
function rpcClient(cfg) {
|
|
5679
5725
|
return new SuiGrpcClient2({ network: cfg.network, baseUrl: cfg.rpcUrl });
|
|
5680
5726
|
}
|
|
@@ -5967,9 +6013,29 @@ function recordFromKeypair(kp, opts = {}, existing, operatorSecret) {
|
|
|
5967
6013
|
};
|
|
5968
6014
|
}
|
|
5969
6015
|
var ENV_KEY_MISMATCH = "SUIPAY_MCP_KEY does not match the stored delegate key - refusing to overwrite";
|
|
6016
|
+
function signingMaterial(parsed) {
|
|
6017
|
+
if (!parsed || typeof parsed !== "object") return null;
|
|
6018
|
+
if (isLocalSigningKeyRecord(parsed)) return parsed;
|
|
6019
|
+
const c = parsed;
|
|
6020
|
+
if (typeof c.delegatePrivateKey !== "string" || !HEX64.test(c.delegatePrivateKey) || typeof c.delegatePublicKeyHex !== "string" || !HEX64.test(c.delegatePublicKeyHex) || typeof c.delegateAddress !== "string" || !ADDR.test(c.delegateAddress)) {
|
|
6021
|
+
return null;
|
|
6022
|
+
}
|
|
6023
|
+
const label = typeof c.label === "string" ? c.label.trim() : "";
|
|
6024
|
+
return {
|
|
6025
|
+
version: 2,
|
|
6026
|
+
delegatePrivateKey: c.delegatePrivateKey,
|
|
6027
|
+
delegatePublicKeyHex: c.delegatePublicKeyHex,
|
|
6028
|
+
delegateAddress: c.delegateAddress,
|
|
6029
|
+
createdAt: typeof c.createdAt === "string" && c.createdAt ? c.createdAt : (/* @__PURE__ */ new Date(0)).toISOString(),
|
|
6030
|
+
...isHttpUrl(c.gatewayUrl) ? { gatewayUrl: c.gatewayUrl } : {},
|
|
6031
|
+
...label ? { label } : {}
|
|
6032
|
+
};
|
|
6033
|
+
}
|
|
5970
6034
|
function existingRecordForEnv(parsed) {
|
|
5971
6035
|
if (parsed === null) return null;
|
|
5972
|
-
if (
|
|
6036
|
+
if (isValid(parsed)) return parsed;
|
|
6037
|
+
const material = signingMaterial(parsed);
|
|
6038
|
+
if (material) return material;
|
|
5973
6039
|
throw new Error(
|
|
5974
6040
|
`credentials file at ${credsPath()} failed schema validation - refusing to continue`
|
|
5975
6041
|
);
|
|
@@ -6007,16 +6073,25 @@ async function ensureLocalSigningKey(opts = {}) {
|
|
|
6007
6073
|
}
|
|
6008
6074
|
const parsed = readCredentialsJson();
|
|
6009
6075
|
if (parsed !== null) {
|
|
6010
|
-
if (isLocalSigningKeyRecord(parsed)) {
|
|
6011
|
-
return identityFromStored(
|
|
6012
|
-
parsed.delegatePrivateKey,
|
|
6013
|
-
parsed.delegatePublicKeyHex,
|
|
6014
|
-
parsed.delegateAddress
|
|
6015
|
-
);
|
|
6016
|
-
}
|
|
6017
|
-
if (isValid(parsed)) {
|
|
6076
|
+
if (isValid(parsed) && !isLocalSigningKeyRecord(parsed)) {
|
|
6018
6077
|
return rewriteV1ToLocalSigningKey(parsed, opts);
|
|
6019
6078
|
}
|
|
6079
|
+
const material = signingMaterial(parsed);
|
|
6080
|
+
if (material) {
|
|
6081
|
+
const identity = identityFromStored(
|
|
6082
|
+
material.delegatePrivateKey,
|
|
6083
|
+
material.delegatePublicKeyHex,
|
|
6084
|
+
material.delegateAddress
|
|
6085
|
+
);
|
|
6086
|
+
if (!isLocalSigningKeyRecord(parsed)) {
|
|
6087
|
+
const label = opts.label?.trim() || material.label;
|
|
6088
|
+
writeCredentialsFile({
|
|
6089
|
+
...material,
|
|
6090
|
+
...label ? { label } : {}
|
|
6091
|
+
});
|
|
6092
|
+
}
|
|
6093
|
+
return identity;
|
|
6094
|
+
}
|
|
6020
6095
|
throw new Error(`credentials file at ${credsPath()} failed schema validation - refusing to continue`);
|
|
6021
6096
|
}
|
|
6022
6097
|
const kp = await generateKeypair();
|
|
@@ -6107,6 +6182,32 @@ function loadLocalKeypair() {
|
|
|
6107
6182
|
Uint8Array.from(Buffer.from(parsed.delegatePrivateKey, "hex"))
|
|
6108
6183
|
);
|
|
6109
6184
|
}
|
|
6185
|
+
function replaceLocalSigningKey(input) {
|
|
6186
|
+
identityFromStored(
|
|
6187
|
+
input.delegatePrivateKey,
|
|
6188
|
+
input.delegatePublicKeyHex,
|
|
6189
|
+
input.delegateAddress
|
|
6190
|
+
);
|
|
6191
|
+
const parsed = readCredentialsJson();
|
|
6192
|
+
let gatewayUrl;
|
|
6193
|
+
let label;
|
|
6194
|
+
if (parsed && typeof parsed === "object") {
|
|
6195
|
+
const record = parsed;
|
|
6196
|
+
if (isHttpUrl(record.gatewayUrl)) gatewayUrl = record.gatewayUrl;
|
|
6197
|
+
if (typeof record.label === "string" && record.label.trim()) {
|
|
6198
|
+
label = record.label.trim();
|
|
6199
|
+
}
|
|
6200
|
+
}
|
|
6201
|
+
writeCredentialsFile({
|
|
6202
|
+
version: 2,
|
|
6203
|
+
delegatePrivateKey: input.delegatePrivateKey,
|
|
6204
|
+
delegatePublicKeyHex: input.delegatePublicKeyHex,
|
|
6205
|
+
delegateAddress: input.delegateAddress,
|
|
6206
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
6207
|
+
...gatewayUrl ? { gatewayUrl } : {},
|
|
6208
|
+
...label ? { label } : {}
|
|
6209
|
+
});
|
|
6210
|
+
}
|
|
6110
6211
|
function persistGatewayUrl(gatewayUrl) {
|
|
6111
6212
|
if (!isHttpUrl(gatewayUrl)) {
|
|
6112
6213
|
throw new Error("gatewayUrl must be an http(s) origin");
|
|
@@ -6938,7 +7039,23 @@ async function fetchGatewayDeployment(options) {
|
|
|
6938
7039
|
}
|
|
6939
7040
|
};
|
|
6940
7041
|
}
|
|
6941
|
-
|
|
7042
|
+
if (d.exclusionDirectoryObjectId !== void 0 && !isNonEmptyString2(d.exclusionDirectoryObjectId)) {
|
|
7043
|
+
return {
|
|
7044
|
+
ok: false,
|
|
7045
|
+
error: {
|
|
7046
|
+
code: MANIFEST_ERROR_CODES.MALFORMED_MANIFEST,
|
|
7047
|
+
detail: "gateway deployment identity carries an unusable exclusionDirectoryObjectId"
|
|
7048
|
+
}
|
|
7049
|
+
};
|
|
7050
|
+
}
|
|
7051
|
+
return {
|
|
7052
|
+
ok: true,
|
|
7053
|
+
value: {
|
|
7054
|
+
network: d.network,
|
|
7055
|
+
packageId: d.packageId,
|
|
7056
|
+
...isNonEmptyString2(d.exclusionDirectoryObjectId) ? { exclusionDirectoryObjectId: d.exclusionDirectoryObjectId } : {}
|
|
7057
|
+
}
|
|
7058
|
+
};
|
|
6942
7059
|
}
|
|
6943
7060
|
function explainBlockedGrant(raw, error2) {
|
|
6944
7061
|
if (error2.code !== MANIFEST_ERROR_CODES.MISSING_POLICY_BINDING)
|
|
@@ -7030,7 +7147,7 @@ async function resolvePayerSession(options) {
|
|
|
7030
7147
|
...options.select ? { select: options.select } : {}
|
|
7031
7148
|
});
|
|
7032
7149
|
if (whole.ok)
|
|
7033
|
-
return whole;
|
|
7150
|
+
return { ok: true, value: { ...whole.value, deployment } };
|
|
7034
7151
|
const data = bootstrap.data;
|
|
7035
7152
|
const rawGrants = Array.isArray(data.grants) ? data.grants : [];
|
|
7036
7153
|
const usable = [];
|
|
@@ -7065,11 +7182,12 @@ async function resolvePayerSession(options) {
|
|
|
7065
7182
|
const pick = scoped ?? blocked[0];
|
|
7066
7183
|
return pick ? { ok: false, error: explainBlockedGrant(pick.raw, pick.error) } : whole;
|
|
7067
7184
|
}
|
|
7068
|
-
|
|
7185
|
+
const resolved = manifestToSession({
|
|
7069
7186
|
manifest: { ...data, grants: usable },
|
|
7070
7187
|
signer: options.signer,
|
|
7071
7188
|
...options.select ? { select: options.select } : {}
|
|
7072
7189
|
});
|
|
7190
|
+
return resolved.ok ? { ok: true, value: { ...resolved.value, deployment } } : resolved;
|
|
7073
7191
|
}
|
|
7074
7192
|
|
|
7075
7193
|
// ../sdk/dist/buyer/transport.js
|
|
@@ -7099,6 +7217,9 @@ function mergeObjectEnvelope(object) {
|
|
|
7099
7217
|
var SECRET_KEY_SET = new Set(FORBIDDEN_SECRET_KEYS.map((k) => k.toLowerCase()));
|
|
7100
7218
|
|
|
7101
7219
|
// ../sdk/dist/agent-sdk/delegate-agent.js
|
|
7220
|
+
function resolveExclusionDirectoryId(explicit, discovered) {
|
|
7221
|
+
return explicit?.trim() ? explicit : discovered;
|
|
7222
|
+
}
|
|
7102
7223
|
function createExclusionRegistryResolver(client, exclusionDirectoryObjectId) {
|
|
7103
7224
|
if (!exclusionDirectoryObjectId || !exclusionDirectoryObjectId.trim())
|
|
7104
7225
|
return void 0;
|
|
@@ -7167,8 +7288,17 @@ async function createPayer(options) {
|
|
|
7167
7288
|
const { session, binding } = resolved.value;
|
|
7168
7289
|
const deployment = {
|
|
7169
7290
|
network: session.network,
|
|
7170
|
-
packageId: session.packageId
|
|
7291
|
+
packageId: session.packageId,
|
|
7292
|
+
// What the GATEWAY published, not what this payer settled on: a caller
|
|
7293
|
+
// override belongs to the payer's configuration, not to the deployment it
|
|
7294
|
+
// is describing. Dropping this left the field permanently undefined on the
|
|
7295
|
+
// one instance a consumer can reach, so anything asking the payer whether
|
|
7296
|
+
// guarded settles are available got "no" while it was doing them.
|
|
7297
|
+
...resolved.value.deployment.exclusionDirectoryObjectId ? {
|
|
7298
|
+
exclusionDirectoryObjectId: resolved.value.deployment.exclusionDirectoryObjectId
|
|
7299
|
+
} : {}
|
|
7171
7300
|
};
|
|
7301
|
+
const exclusionDirectoryObjectId = resolveExclusionDirectoryId(options.exclusionDirectoryObjectId, resolved.value.deployment.exclusionDirectoryObjectId);
|
|
7172
7302
|
let network;
|
|
7173
7303
|
try {
|
|
7174
7304
|
network = toGrpcNetwork(deployment.network);
|
|
@@ -7227,7 +7357,7 @@ async function createPayer(options) {
|
|
|
7227
7357
|
// directory id was configured, so such an offer fails closed (BUILD_FAILED)
|
|
7228
7358
|
// rather than building an unguarded settle. Unguarded offers never call it.
|
|
7229
7359
|
...(() => {
|
|
7230
|
-
const resolve = createExclusionRegistryResolver(chain.client,
|
|
7360
|
+
const resolve = createExclusionRegistryResolver(chain.client, exclusionDirectoryObjectId);
|
|
7231
7361
|
return resolve ? { resolveExclusionRegistry: resolve } : {};
|
|
7232
7362
|
})(),
|
|
7233
7363
|
// x402 bounds the transaction's expiration by the current epoch, read from
|
|
@@ -7286,6 +7416,7 @@ async function createPayer(options) {
|
|
|
7286
7416
|
import { createServer } from "http";
|
|
7287
7417
|
import { randomBytes as randomBytes3, timingSafeEqual as timingSafeEqual2 } from "crypto";
|
|
7288
7418
|
import open from "open";
|
|
7419
|
+
import { Ed25519Keypair as Ed25519Keypair3 } from "@mysten/sui/keypairs/ed25519";
|
|
7289
7420
|
var HEX642 = /^[0-9a-f]{64}$/;
|
|
7290
7421
|
var PUBHEX = /^[0-9a-fA-F]{64}$/;
|
|
7291
7422
|
function isPreflight(obj) {
|
|
@@ -7293,6 +7424,11 @@ function isPreflight(obj) {
|
|
|
7293
7424
|
const o = obj;
|
|
7294
7425
|
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;
|
|
7295
7426
|
}
|
|
7427
|
+
function isAdoptKey(obj) {
|
|
7428
|
+
if (!obj || typeof obj !== "object") return false;
|
|
7429
|
+
const o = obj;
|
|
7430
|
+
return typeof o.state === "string" && HEX642.test(o.state) && typeof o.delegatePrivateKey === "string" && HEX642.test(o.delegatePrivateKey) && typeof o.delegatePublicKeyHex === "string" && PUBHEX.test(o.delegatePublicKeyHex) && typeof o.delegateAddress === "string" && /^0x[0-9a-fA-F]{64}$/.test(o.delegateAddress);
|
|
7431
|
+
}
|
|
7296
7432
|
function isCallback(obj) {
|
|
7297
7433
|
if (!obj || typeof obj !== "object") return false;
|
|
7298
7434
|
const o = obj;
|
|
@@ -7417,7 +7553,7 @@ async function startStdioLogin(opts) {
|
|
|
7417
7553
|
res.writeHead(code, { "content-type": type, ...SECURITY_HEADERS, ...extra });
|
|
7418
7554
|
res.end(body);
|
|
7419
7555
|
};
|
|
7420
|
-
const okPath = url.pathname === "/preflight" || url.pathname === "/callback";
|
|
7556
|
+
const okPath = url.pathname === "/preflight" || url.pathname === "/callback" || url.pathname === "/key";
|
|
7421
7557
|
if (req.method === "OPTIONS") {
|
|
7422
7558
|
const extra = { ...cors };
|
|
7423
7559
|
if (okHost && okOrigin && okPath && req.headers["access-control-request-private-network"] === "true") {
|
|
@@ -7488,6 +7624,55 @@ async function startStdioLogin(opts) {
|
|
|
7488
7624
|
);
|
|
7489
7625
|
return;
|
|
7490
7626
|
}
|
|
7627
|
+
if (url.pathname === "/key") {
|
|
7628
|
+
if (!preflightVerified) {
|
|
7629
|
+
res.writeHead(428, {
|
|
7630
|
+
"content-type": "application/json",
|
|
7631
|
+
...cors,
|
|
7632
|
+
...SECURITY_HEADERS
|
|
7633
|
+
});
|
|
7634
|
+
res.end(JSON.stringify({ ok: false, error: "preflight required" }));
|
|
7635
|
+
return;
|
|
7636
|
+
}
|
|
7637
|
+
if (!isAdoptKey(parsed) || !stateEquals(parsed.state, connectState)) {
|
|
7638
|
+
res.writeHead(400, {
|
|
7639
|
+
"content-type": "application/json",
|
|
7640
|
+
...cors,
|
|
7641
|
+
...SECURITY_HEADERS
|
|
7642
|
+
});
|
|
7643
|
+
res.end(JSON.stringify({ ok: false, error: "invalid key payload" }));
|
|
7644
|
+
return;
|
|
7645
|
+
}
|
|
7646
|
+
try {
|
|
7647
|
+
const kp = Ed25519Keypair3.fromSecretKey(
|
|
7648
|
+
Uint8Array.from(Buffer.from(parsed.delegatePrivateKey, "hex"))
|
|
7649
|
+
);
|
|
7650
|
+
const derived = kp.getPublicKey().toSuiAddress();
|
|
7651
|
+
if (derived.toLowerCase() !== parsed.delegateAddress.toLowerCase()) {
|
|
7652
|
+
throw new Error("address mismatch");
|
|
7653
|
+
}
|
|
7654
|
+
replaceLocalSigningKey({
|
|
7655
|
+
delegatePrivateKey: parsed.delegatePrivateKey,
|
|
7656
|
+
delegatePublicKeyHex: parsed.delegatePublicKeyHex,
|
|
7657
|
+
delegateAddress: derived
|
|
7658
|
+
});
|
|
7659
|
+
} catch {
|
|
7660
|
+
res.writeHead(400, {
|
|
7661
|
+
"content-type": "application/json",
|
|
7662
|
+
...cors,
|
|
7663
|
+
...SECURITY_HEADERS
|
|
7664
|
+
});
|
|
7665
|
+
res.end(JSON.stringify({ ok: false, error: "key rejected" }));
|
|
7666
|
+
return;
|
|
7667
|
+
}
|
|
7668
|
+
res.writeHead(200, {
|
|
7669
|
+
"content-type": "application/json",
|
|
7670
|
+
...cors,
|
|
7671
|
+
...SECURITY_HEADERS
|
|
7672
|
+
});
|
|
7673
|
+
res.end(JSON.stringify({ ok: true }));
|
|
7674
|
+
return;
|
|
7675
|
+
}
|
|
7491
7676
|
if (!preflightVerified) {
|
|
7492
7677
|
res.writeHead(428, {
|
|
7493
7678
|
"content-type": "text/html",
|
|
@@ -7591,6 +7776,22 @@ import { fromBase64 as fromBase6410, toBase64 as toBase646 } from "@mysten/sui/u
|
|
|
7591
7776
|
|
|
7592
7777
|
// src/spend-account-pay.ts
|
|
7593
7778
|
var SUI_CLOCK3 = "0x6";
|
|
7779
|
+
var sharedObjectReaderForTests = null;
|
|
7780
|
+
function chainSharedObjectReader(cfg, packageId) {
|
|
7781
|
+
const chain = createSpendAccountChain({
|
|
7782
|
+
packageId,
|
|
7783
|
+
network: cfg.network,
|
|
7784
|
+
url: cfg.rpcUrl
|
|
7785
|
+
});
|
|
7786
|
+
return async (objectId) => {
|
|
7787
|
+
const { object } = await chain.client.core.getObject({
|
|
7788
|
+
objectId,
|
|
7789
|
+
include: { json: true }
|
|
7790
|
+
});
|
|
7791
|
+
if (!object?.owner) return null;
|
|
7792
|
+
return { owner: object.owner };
|
|
7793
|
+
};
|
|
7794
|
+
}
|
|
7594
7795
|
function traceEmitter(trace) {
|
|
7595
7796
|
if (!trace) return () => void 0;
|
|
7596
7797
|
const now = trace.now ?? Date.now;
|
|
@@ -7650,12 +7851,12 @@ async function buildSpendAccountPayKind2(input) {
|
|
|
7650
7851
|
}),
|
|
7651
7852
|
tx.sharedObjectRef({
|
|
7652
7853
|
objectId: input.poolObjectId,
|
|
7653
|
-
initialSharedVersion: "1",
|
|
7854
|
+
initialSharedVersion: input.poolInitialSharedVersion?.trim() || "1",
|
|
7654
7855
|
mutable: true
|
|
7655
7856
|
}),
|
|
7656
7857
|
tx.sharedObjectRef({
|
|
7657
7858
|
objectId: input.grantObjectId,
|
|
7658
|
-
initialSharedVersion: "1",
|
|
7859
|
+
initialSharedVersion: input.grantInitialSharedVersion?.trim() || "1",
|
|
7659
7860
|
mutable: true
|
|
7660
7861
|
}),
|
|
7661
7862
|
tx.pure.u64(BigInt(input.policyId)),
|
|
@@ -7666,7 +7867,7 @@ async function buildSpendAccountPayKind2(input) {
|
|
|
7666
7867
|
tx.pure.u64(BigInt(guarded.expiresAtMs)),
|
|
7667
7868
|
tx.sharedObjectRef({
|
|
7668
7869
|
objectId: SUI_CLOCK3,
|
|
7669
|
-
initialSharedVersion: "1",
|
|
7870
|
+
initialSharedVersion: input.clockInitialSharedVersion?.trim() || "1",
|
|
7670
7871
|
mutable: false
|
|
7671
7872
|
})
|
|
7672
7873
|
]
|
|
@@ -7754,6 +7955,12 @@ async function prepareSpendAccountPayment(args) {
|
|
|
7754
7955
|
...guardedFields
|
|
7755
7956
|
});
|
|
7756
7957
|
}
|
|
7958
|
+
const readSharedObject = deps.readSharedObject ?? sharedObjectReaderForTests ?? chainSharedObjectReader(cfg, input.packageId);
|
|
7959
|
+
const versions = await resolveSharedObjectVersionsFromGetObject(readSharedObject, {
|
|
7960
|
+
poolObjectId: input.poolObjectId,
|
|
7961
|
+
grantObjectId: input.grantObjectId,
|
|
7962
|
+
clockObjectId: SUI_CLOCK3
|
|
7963
|
+
});
|
|
7757
7964
|
if (isOpen) {
|
|
7758
7965
|
return buildSpendAccountSettleOpenRecipientKind({
|
|
7759
7966
|
packageId: input.packageId,
|
|
@@ -7761,8 +7968,9 @@ async function prepareSpendAccountPayment(args) {
|
|
|
7761
7968
|
coinType: input.coinType,
|
|
7762
7969
|
poolObjectId: input.poolObjectId,
|
|
7763
7970
|
grantObjectId: input.grantObjectId,
|
|
7764
|
-
poolInitialSharedVersion:
|
|
7765
|
-
grantInitialSharedVersion:
|
|
7971
|
+
poolInitialSharedVersion: versions.poolInitialSharedVersion,
|
|
7972
|
+
grantInitialSharedVersion: versions.grantInitialSharedVersion,
|
|
7973
|
+
clockInitialSharedVersion: versions.clockInitialSharedVersion,
|
|
7766
7974
|
recipient: input.recipient,
|
|
7767
7975
|
targetHash: input.targetHash,
|
|
7768
7976
|
amount: input.amount,
|
|
@@ -7782,6 +7990,9 @@ async function prepareSpendAccountPayment(args) {
|
|
|
7782
7990
|
amount: input.amount,
|
|
7783
7991
|
paymentIdHash: input.paymentIdHash,
|
|
7784
7992
|
termsHash: input.termsHash,
|
|
7993
|
+
poolInitialSharedVersion: versions.poolInitialSharedVersion,
|
|
7994
|
+
grantInitialSharedVersion: versions.grantInitialSharedVersion,
|
|
7995
|
+
clockInitialSharedVersion: versions.clockInitialSharedVersion,
|
|
7785
7996
|
...guardedFields
|
|
7786
7997
|
});
|
|
7787
7998
|
};
|
|
@@ -7802,7 +8013,8 @@ async function prepareSpendAccountPayment(args) {
|
|
|
7802
8013
|
authority: session.snapshot,
|
|
7803
8014
|
assertLive: session.assertLive
|
|
7804
8015
|
};
|
|
7805
|
-
const
|
|
8016
|
+
const directorySwitch = cfg.exclusionDirectoryObjectId ? cfg.exclusionDirectoryObjectId : deps.resolveExclusionRegistry ? null : await publishedExclusionDirectoryId(cfg.gatewayUrl);
|
|
8017
|
+
const resolveExclusionRegistry2 = deps.resolveExclusionRegistry ?? (directorySwitch ? (() => {
|
|
7806
8018
|
const reader = createGrpcExclusionRegistryReader(rpcClient(cfg));
|
|
7807
8019
|
return async ({ directoryId, recipient }) => resolveExclusionRegistry({
|
|
7808
8020
|
directoryObjectId: directoryId,
|
|
@@ -9022,10 +9234,15 @@ async function suipayLogin(args, cfg, deps = {}) {
|
|
|
9022
9234
|
});
|
|
9023
9235
|
}
|
|
9024
9236
|
if (live.kind === "unknown") {
|
|
9025
|
-
return
|
|
9026
|
-
status: "
|
|
9237
|
+
return text({
|
|
9238
|
+
status: "ok",
|
|
9239
|
+
reused: true,
|
|
9240
|
+
grantStatus: "unknown",
|
|
9027
9241
|
code: "GRANT_STATUS_UNKNOWN",
|
|
9028
|
-
|
|
9242
|
+
target: target ?? cfg.target,
|
|
9243
|
+
delegateAddress: identity.delegateAddress,
|
|
9244
|
+
gatewayUrl: endpoints.gatewayUrl,
|
|
9245
|
+
message: "Credentials are already saved for this agent. The existing grant could not be checked just now, so no new sign-in was started and the key file was left unchanged. Retry suipay_login in a moment."
|
|
9029
9246
|
});
|
|
9030
9247
|
}
|
|
9031
9248
|
}
|
|
@@ -9035,13 +9252,13 @@ async function suipayLogin(args, cfg, deps = {}) {
|
|
|
9035
9252
|
identity,
|
|
9036
9253
|
label,
|
|
9037
9254
|
timeoutMs: LOGIN_BG_TIMEOUT_MS,
|
|
9038
|
-
openBrowser:
|
|
9255
|
+
openBrowser: true,
|
|
9039
9256
|
onUrl: deps.onUrl
|
|
9040
9257
|
});
|
|
9041
9258
|
const url = started.connectUrl;
|
|
9042
9259
|
return text(
|
|
9043
9260
|
[
|
|
9044
|
-
`## ACTION REQUIRED:
|
|
9261
|
+
`## ACTION REQUIRED: The sign-in page was opened in the browser`,
|
|
9045
9262
|
``,
|
|
9046
9263
|
`**Target:** ${target ?? cfg.target ?? "custom"} \xB7 ${endpoints.gatewayUrl}`,
|
|
9047
9264
|
``,
|
|
@@ -9053,11 +9270,13 @@ async function suipayLogin(args, cfg, deps = {}) {
|
|
|
9053
9270
|
``,
|
|
9054
9271
|
`[Click here to open SuiPay sign-in](${url})`,
|
|
9055
9272
|
``,
|
|
9056
|
-
`**IMPORTANT for the assistant**:
|
|
9057
|
-
`
|
|
9273
|
+
`**IMPORTANT for the assistant**: the browser was already opened to this exact URL.`,
|
|
9274
|
+
`Do NOT navigate to /connect/agent yourself. That path without these query`,
|
|
9275
|
+
`parameters shows "Authorization link incomplete". If the page did not`,
|
|
9276
|
+
`appear, surface the URL above verbatim and do not shorten it.`,
|
|
9058
9277
|
``,
|
|
9059
|
-
`1.
|
|
9060
|
-
`2. Connect the owner wallet
|
|
9278
|
+
`1. Use the browser window that just opened, or open the URL above`,
|
|
9279
|
+
`2. Connect the owner wallet, choose or create a key, then Authorize on the review step`,
|
|
9061
9280
|
`3. Once Connected appears, retry the original request \u2014 credentials at \`~/.suipay/credentials.json\` are picked up on the next tool call`,
|
|
9062
9281
|
``,
|
|
9063
9282
|
`_The login link stays valid for 5 minutes. If it expires, call \`suipay_login\` again._`,
|
|
@@ -83,11 +83,10 @@ interface McpConfig {
|
|
|
83
83
|
minMaxPerPayment: bigint;
|
|
84
84
|
label: string;
|
|
85
85
|
/**
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
* unset — a guarded offer then FAILS CLOSED (no unguarded settle).
|
|
86
|
+
* Optional override of the gateway's published `ExclusionDirectory` id.
|
|
87
|
+
* When null, pay reads `GET /v1/delegate/deployment` and turns the resolver
|
|
88
|
+
* on only if that response names one. The id here does not choose the
|
|
89
|
+
* directory a settle uses — the offer's `registryIdentity` does.
|
|
91
90
|
*/
|
|
92
91
|
exclusionDirectoryObjectId: string | null;
|
|
93
92
|
}
|
|
@@ -304,6 +303,9 @@ type PayResult = {
|
|
|
304
303
|
* See docs/design/mcp-non-custodial-signing.md and ADR-0013 §3.
|
|
305
304
|
*/
|
|
306
305
|
|
|
306
|
+
type SharedObjectReader = (objectId: string) => Promise<{
|
|
307
|
+
owner?: unknown;
|
|
308
|
+
} | null | undefined>;
|
|
307
309
|
/**
|
|
308
310
|
* Read/prepare-only session for the non-custodial MCP pay tool. It carries the
|
|
309
311
|
* immutable grant snapshot and an optional prepare-time live gate — and no key,
|
|
@@ -355,6 +357,11 @@ interface SpendAccountPayDeps {
|
|
|
355
357
|
* configured `ExclusionDirectory` id when present, else fails closed.
|
|
356
358
|
*/
|
|
357
359
|
resolveExclusionRegistry?: PayOperationDeps['resolveExclusionRegistry'];
|
|
360
|
+
/**
|
|
361
|
+
* Read a shared object's owner so prepare binds the real initial version.
|
|
362
|
+
* Missing owner fails the prepare; version 1 is not a substitute.
|
|
363
|
+
*/
|
|
364
|
+
readSharedObject?: SharedObjectReader;
|
|
358
365
|
/**
|
|
359
366
|
* Live trace for this prepare. Purely observational: emission records what the
|
|
360
367
|
* canonical read decisions already returned — an event is written after a check
|
|
@@ -437,6 +444,9 @@ declare function buildSpendAccountPayKind(input: {
|
|
|
437
444
|
amount: string;
|
|
438
445
|
paymentIdHash: Uint8Array;
|
|
439
446
|
termsHash: Uint8Array;
|
|
447
|
+
poolInitialSharedVersion?: string;
|
|
448
|
+
grantInitialSharedVersion?: string;
|
|
449
|
+
clockInitialSharedVersion?: string;
|
|
440
450
|
} & GuardedSettleFields): Promise<{
|
|
441
451
|
kindBytes: string;
|
|
442
452
|
}>;
|
package/dist/http.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { r as handleSuipayMcpHttpRequest } from './http-
|
|
1
|
+
export { r as handleSuipayMcpHttpRequest } from './http-RP-Xlg60.js';
|
|
2
2
|
import '@cmdoss/suipay-core/policy/grant-target';
|
|
3
3
|
import '@cmdoss/suipay-sdk/mcp/access-context';
|
|
4
4
|
import '@cmdoss/suipay-sdk/buyer/settlement-dto';
|
package/dist/http.js
CHANGED
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Wallet } from '@cmdoss/suipay-core/chain/types';
|
|
2
|
-
import { S as SpendAccountSigningRequest, M as McpConfig, a as McpAuthContext, b as McpTraceHooks, c as SpendAccountPayDeps } from './http-
|
|
3
|
-
export { A as AgentHeldResolver, d as AgentHeldSettlement, e as MCP_TARGET_PRESETS, f as McpEnvelope, g as McpEnvelopeEntry, h as McpServerOptions, i as McpTarget, j as McpTraceContext, k as McpTraceRuntime, P as PACKAGE_NAME, l as PreparedPayment, m as SpendAccountPrepareResult, n as SpendAccountPrepareSession, T as TOOLS, o as buildSpendAccountPayKind, p as canonicalMcpRequestId, q as createServer, r as handleSuipayMcpHttpRequest, s as inspectMcpEnvelope, t as loadMcpConfig, u as parseMcpTargetFlag, v as prepareSpendAccountPayment, w as resetAgentHeldProcessLedger, x as resolveAgentHeldSettlement, y as resolveMcpTarget, z as sameMcpGateway, B as traceMcpHttpRequest, C as verifySpendAccountPayReconstructs } from './http-
|
|
2
|
+
import { S as SpendAccountSigningRequest, M as McpConfig, a as McpAuthContext, b as McpTraceHooks, c as SpendAccountPayDeps } from './http-RP-Xlg60.js';
|
|
3
|
+
export { A as AgentHeldResolver, d as AgentHeldSettlement, e as MCP_TARGET_PRESETS, f as McpEnvelope, g as McpEnvelopeEntry, h as McpServerOptions, i as McpTarget, j as McpTraceContext, k as McpTraceRuntime, P as PACKAGE_NAME, l as PreparedPayment, m as SpendAccountPrepareResult, n as SpendAccountPrepareSession, T as TOOLS, o as buildSpendAccountPayKind, p as canonicalMcpRequestId, q as createServer, r as handleSuipayMcpHttpRequest, s as inspectMcpEnvelope, t as loadMcpConfig, u as parseMcpTargetFlag, v as prepareSpendAccountPayment, w as resetAgentHeldProcessLedger, x as resolveAgentHeldSettlement, y as resolveMcpTarget, z as sameMcpGateway, B as traceMcpHttpRequest, C as verifySpendAccountPayReconstructs } from './http-RP-Xlg60.js';
|
|
4
4
|
import { GetObjectJson, createReader } from '@cmdoss/suipay-sdk/agent-sdk/reads';
|
|
5
5
|
import { DelegatedKeySigner, DelegatedPayerSession, DelegatedPayerConfig, DelegatedPayerDeps, SpendIntent, DelegatedPayResult } from '@cmdoss/suipay-sdk/delegated-signer/payer';
|
|
6
6
|
import { PaidHttpRequest } from '@cmdoss/suipay-sdk/protocol/paid-http-request';
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
loadBootProfile,
|
|
3
3
|
main
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-LQOZBRH6.js";
|
|
5
5
|
import {
|
|
6
6
|
MCP_TARGET_PRESETS,
|
|
7
7
|
PACKAGE_NAME,
|
|
@@ -26,7 +26,7 @@ import {
|
|
|
26
26
|
settlePreparedMcpPayment,
|
|
27
27
|
traceMcpHttpRequest,
|
|
28
28
|
verifySpendAccountPayReconstructs
|
|
29
|
-
} from "./chunk-
|
|
29
|
+
} from "./chunk-NDJ7BFI4.js";
|
|
30
30
|
export {
|
|
31
31
|
MCP_TARGET_PRESETS,
|
|
32
32
|
PACKAGE_NAME,
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cmdoss/suipay-mcp",
|
|
3
|
-
"version": "0.2.2-dev.
|
|
3
|
+
"version": "0.2.2-dev.9",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"description": "SuiPay MCP
|
|
5
|
+
"description": "SuiPay MCP — pay MPP-gated APIs from a spend-account grant with an agent-held delegate key.",
|
|
6
6
|
"main": "./dist/index.js",
|
|
7
7
|
"types": "./dist/index.d.ts",
|
|
8
8
|
"exports": {
|
|
@@ -20,16 +20,11 @@
|
|
|
20
20
|
],
|
|
21
21
|
"publishConfig": {
|
|
22
22
|
"access": "public",
|
|
23
|
-
"provenance":
|
|
23
|
+
"provenance": false
|
|
24
24
|
},
|
|
25
25
|
"engines": {
|
|
26
26
|
"node": ">=20"
|
|
27
27
|
},
|
|
28
|
-
"scripts": {
|
|
29
|
-
"build": "tsup",
|
|
30
|
-
"test": "vitest run",
|
|
31
|
-
"typecheck": "tsc -p tsconfig.json --noEmit"
|
|
32
|
-
},
|
|
33
28
|
"dependencies": {
|
|
34
29
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
35
30
|
"@mysten/sui": "2.24.0",
|
|
@@ -38,11 +33,16 @@
|
|
|
38
33
|
"open": "^11.0.0"
|
|
39
34
|
},
|
|
40
35
|
"devDependencies": {
|
|
41
|
-
"@
|
|
42
|
-
"
|
|
43
|
-
"
|
|
44
|
-
"
|
|
45
|
-
"
|
|
46
|
-
"
|
|
36
|
+
"@types/node": "^26",
|
|
37
|
+
"tsup": "^8.0.0",
|
|
38
|
+
"typescript": "^6.0.3",
|
|
39
|
+
"vitest": "^4.1.10",
|
|
40
|
+
"@cmdoss/suipay-core": "0.1.0",
|
|
41
|
+
"@cmdoss/suipay-sdk": "0.1.0"
|
|
42
|
+
},
|
|
43
|
+
"scripts": {
|
|
44
|
+
"build": "tsup",
|
|
45
|
+
"test": "vitest run",
|
|
46
|
+
"typecheck": "tsc -p tsconfig.json --noEmit"
|
|
47
47
|
}
|
|
48
|
-
}
|
|
48
|
+
}
|