@cmdoss/suipay-mcp 0.2.2-dev.0 → 0.2.2
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 +6 -59
- package/dist/bin/suipay.js +3 -2
- package/dist/{chunk-NVTYOOMY.js → chunk-26NKQ37M.js} +1616 -3688
- package/dist/chunk-A3JIIDYT.js +161 -0
- package/dist/chunk-EM7PKWVL.js +39 -0
- package/dist/http-BnIjiwcR.d.ts +1149 -0
- package/dist/http.d.ts +2 -7
- package/dist/http.js +2 -1
- package/dist/index.d.ts +35 -109
- package/dist/index.js +3 -14
- package/dist/personal-message-5PF4KVLF.js +284 -0
- package/package.json +7 -9
- package/dist/chunk-K6WM4Z7H.js +0 -46
- package/dist/http-B4YTVw0k.d.ts +0 -392
package/dist/http.d.ts
CHANGED
|
@@ -1,8 +1,3 @@
|
|
|
1
|
-
export {
|
|
2
|
-
import '@cmdoss/suipay-core/policy/grant-target';
|
|
3
|
-
import '@cmdoss/suipay-sdk/mcp/access-context';
|
|
4
|
-
import '@cmdoss/suipay-sdk/buyer/settlement-dto';
|
|
1
|
+
export { q as handleSuipayMcpHttpRequest } from './http-BnIjiwcR.js';
|
|
5
2
|
import '@modelcontextprotocol/sdk/server/index.js';
|
|
6
|
-
import '@
|
|
7
|
-
import '@cmdoss/suipay-sdk/delegated-signer/payer';
|
|
8
|
-
import '@cmdoss/suipay-core/trace/types';
|
|
3
|
+
import '@mysten/sui/keypairs/ed25519';
|
package/dist/http.js
CHANGED
package/dist/index.d.ts
CHANGED
|
@@ -1,18 +1,25 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
export { A as AgentHeldResolver, c as AgentHeldSettlement, d as MCP_TARGET_PRESETS, e as McpEnvelope, f as McpEnvelopeEntry, g as McpServerOptions, h as McpTarget, i as McpTraceContext, j as McpTraceRuntime, P as PACKAGE_NAME, T as TOOLS, k as canonicalMcpRequestId, l as createServer, m as handleSuipayMcpHttpRequest, n as inspectMcpEnvelope, o as loadMcpConfig, p as parseMcpTargetFlag, r as resetAgentHeldProcessLedger, q as resolveAgentHeldSettlement, s as resolveMcpTarget, t as sameMcpGateway, u as traceMcpHttpRequest } from './http-B4YTVw0k.js';
|
|
4
|
-
import { GrantSnapshot } from '@cmdoss/suipay-core/policy/grant-target';
|
|
5
|
-
export { resolveGrantTarget } from '@cmdoss/suipay-core/policy/grant-target';
|
|
6
|
-
import { PaidHttpRequest } from '@cmdoss/suipay-sdk/protocol/paid-http-request';
|
|
7
|
-
import { TraceSink } from '@cmdoss/suipay-core/trace/types';
|
|
8
|
-
import { Dialect } from '@cmdoss/suipay-sdk/protocol/types';
|
|
9
|
-
import { GetObjectJson, createReader } from '@cmdoss/suipay-sdk/agent-sdk/reads';
|
|
10
|
-
import { DelegatedKeySigner, DelegatedPayerSession, DelegatedPayerConfig, DelegatedPayerDeps, SpendIntent, DelegatedPayResult } from '@cmdoss/suipay-sdk/delegated-signer/payer';
|
|
11
|
-
import { createPayer } from '@cmdoss/suipay-sdk/agent-sdk/delegate-agent';
|
|
12
|
-
import { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
|
|
13
|
-
import '@cmdoss/suipay-sdk/mcp/access-context';
|
|
14
|
-
import '@cmdoss/suipay-sdk/buyer/settlement-dto';
|
|
1
|
+
import { D as Dialect, G as GrantSnapshot, M as McpConfig, T as TraceSink, a as DelegatedKeySigner, b as GetObjectJson, c as DelegatedPayerSession, d as DelegatedPayerConfig, e as DelegatedPayerDeps, S as SpendIntent, f as DelegatedPayResult } from './http-BnIjiwcR.js';
|
|
2
|
+
export { A as AgentHeldResolver, g as AgentHeldSettlement, h as McpEnvelope, i as McpEnvelopeEntry, j as McpServerOptions, k as McpTraceContext, l as McpTraceHooks, m as McpTraceRuntime, P as PACKAGE_NAME, n as TOOLS, o as canonicalMcpRequestId, p as createServer, q as handleSuipayMcpHttpRequest, r as inspectMcpEnvelope, s as loadMcpConfig, t as resetAgentHeldProcessLedger, u as resolveAgentHeldSettlement, v as resolveGrantTarget, w as traceMcpHttpRequest } from './http-BnIjiwcR.js';
|
|
15
3
|
import '@modelcontextprotocol/sdk/server/index.js';
|
|
4
|
+
import '@mysten/sui/keypairs/ed25519';
|
|
5
|
+
|
|
6
|
+
type PaidHttpMethod = 'GET' | 'POST';
|
|
7
|
+
interface PaidHttpRequest {
|
|
8
|
+
url: string;
|
|
9
|
+
method: PaidHttpMethod;
|
|
10
|
+
/** Exact wire bytes. Empty for GET. */
|
|
11
|
+
body: Uint8Array;
|
|
12
|
+
/** Normalized media type, or null when no body is sent. */
|
|
13
|
+
contentType: string | null;
|
|
14
|
+
/** `hashRequestBody(body)` — the single canonical body hash. */
|
|
15
|
+
bodyHash: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** Holds a key and signs transaction bytes. Never leaves the payer. */
|
|
19
|
+
interface Wallet {
|
|
20
|
+
readonly address: string;
|
|
21
|
+
signTransaction(txBytes: string): Promise<string>;
|
|
22
|
+
}
|
|
16
23
|
|
|
17
24
|
interface SuiPayCredentials {
|
|
18
25
|
/** 64-hex Ed25519 seed. NEVER logged. */
|
|
@@ -46,9 +53,14 @@ interface PaymentProfile {
|
|
|
46
53
|
/**
|
|
47
54
|
* Load a historical V1 allowance payment profile, if present.
|
|
48
55
|
* ADR-0010: not for new settlement. Callers that pay must refuse this profile
|
|
49
|
-
* and point operators at
|
|
56
|
+
* and point operators at shared_pool OAuth / delegated-signer flows.
|
|
50
57
|
*/
|
|
51
58
|
declare function loadPaymentProfile(): PaymentProfile | null;
|
|
59
|
+
/**
|
|
60
|
+
* First authorization generates a local keypair and persists it at 0600.
|
|
61
|
+
* Re-authorization reuses the stored key so a second key cannot orphan the grant.
|
|
62
|
+
* Returns only the public identity.
|
|
63
|
+
*/
|
|
52
64
|
declare function ensureLocalSigningKey(opts?: {
|
|
53
65
|
label?: string;
|
|
54
66
|
}): Promise<LocalSigningKeyIdentity>;
|
|
@@ -73,8 +85,8 @@ declare function loadLocalSigner(): {
|
|
|
73
85
|
*
|
|
74
86
|
* The V1 allowance sponsored payer (ADR-0010) is retired; only the
|
|
75
87
|
* rail-neutral request surface the SpendAccount payer depends on survives here:
|
|
76
|
-
* the `PayResult` shape every payer returns
|
|
77
|
-
*
|
|
88
|
+
* the `PayResult` shape every payer returns and `paidRequestInit`, which builds
|
|
89
|
+
* the one RequestInit replayed byte for byte on the unpaid and the paid call.
|
|
78
90
|
*/
|
|
79
91
|
|
|
80
92
|
type PayResult = {
|
|
@@ -203,11 +215,11 @@ interface SpendAccountSigningRequest {
|
|
|
203
215
|
moveCallTarget: string;
|
|
204
216
|
};
|
|
205
217
|
/**
|
|
206
|
-
*
|
|
207
|
-
*
|
|
208
|
-
*
|
|
218
|
+
* Non-secret wallet/console handoff for hosts that cannot run a local signer:
|
|
219
|
+
* the delegate key signs and submits out of band there. Parameterized only by
|
|
220
|
+
* public Connection-manifest fields — never any secret.
|
|
209
221
|
*/
|
|
210
|
-
|
|
222
|
+
handoffUrl: string;
|
|
211
223
|
/** Restated hard rule for the agent host. */
|
|
212
224
|
note: string;
|
|
213
225
|
}
|
|
@@ -247,17 +259,11 @@ declare function verifySpendAccountPayReconstructs(bytes: string, expected: {
|
|
|
247
259
|
amount: string;
|
|
248
260
|
}): void;
|
|
249
261
|
/**
|
|
250
|
-
* Prepare a
|
|
262
|
+
* Prepare a shared_pool payment WITHOUT signing. Reads the 402 offer, binds it,
|
|
251
263
|
* gates on the immutable grant snapshot + a fresh authority check, builds the
|
|
252
264
|
* unsigned transaction, and returns a signing request. The client holding the
|
|
253
265
|
* delegate key signs locally and submits. This function never decrypts, signs,
|
|
254
266
|
* sponsors, or submits.
|
|
255
|
-
*
|
|
256
|
-
* Uses `resolvePayment` from the SDK pay-operation module (SAIP-170) for the
|
|
257
|
-
* shared offer-acquisition + authority-resolution pipeline (steps 1–5 + assertLive
|
|
258
|
-
* + hash derivation + buildPayKind). This module wraps that with MCP-specific
|
|
259
|
-
* concerns: trace events, dialect-mode ceiling check, and signing-request
|
|
260
|
-
* construction.
|
|
261
267
|
*/
|
|
262
268
|
declare function prepareSpendAccountPayment(args: {
|
|
263
269
|
url: string;
|
|
@@ -286,12 +292,6 @@ declare function prepareSpendAccountPayment(args: {
|
|
|
286
292
|
|
|
287
293
|
declare function settlePreparedMcpPayment(args: {
|
|
288
294
|
signingRequest: SpendAccountSigningRequest;
|
|
289
|
-
/**
|
|
290
|
-
* The same paid request that minted the prepare hop. Reconstructing from
|
|
291
|
-
* url+method alone drops a POST body and the settlement hop then prices
|
|
292
|
-
* (or 400s) a different request than the one the tool was called with.
|
|
293
|
-
*/
|
|
294
|
-
request: PaidHttpRequest;
|
|
295
295
|
/**
|
|
296
296
|
* What this payment is supposed to move, sourced from the caller's intent —
|
|
297
297
|
* never re-read from the object being verified. Comparing a prepared kind
|
|
@@ -315,84 +315,10 @@ declare function settlePreparedMcpPayment(args: {
|
|
|
315
315
|
intent?: SpendIntent;
|
|
316
316
|
}): Promise<DelegatedPayResult>;
|
|
317
317
|
|
|
318
|
-
interface StdioLoginOptions {
|
|
319
|
-
consoleUrl: string;
|
|
320
|
-
gatewayUrl: string;
|
|
321
|
-
identity: {
|
|
322
|
-
delegateAddress: string;
|
|
323
|
-
publicKeyHex: string;
|
|
324
|
-
};
|
|
325
|
-
label?: string;
|
|
326
|
-
timeoutMs?: number;
|
|
327
|
-
openBrowser?: boolean;
|
|
328
|
-
onUrl?: (connectUrl: string) => void;
|
|
329
|
-
}
|
|
330
|
-
interface StartedStdioLogin {
|
|
331
|
-
connectUrl: string;
|
|
332
|
-
port: number;
|
|
333
|
-
connectState: string;
|
|
334
|
-
done: Promise<void>;
|
|
335
|
-
close: () => void;
|
|
336
|
-
}
|
|
337
|
-
/**
|
|
338
|
-
* Bind the loopback listener and return the console URL immediately.
|
|
339
|
-
* The wallet callback is awaited on `done`, not on the MCP tool call.
|
|
340
|
-
*/
|
|
341
|
-
declare function startStdioLogin(opts: StdioLoginOptions): Promise<StartedStdioLogin>;
|
|
342
|
-
|
|
343
|
-
/**
|
|
344
|
-
* MCP tool payloads. Settlement honesty matches the playground stamp:
|
|
345
|
-
* money that moved is never reported as a tool error (that invites a second pay).
|
|
346
|
-
*/
|
|
347
|
-
|
|
348
|
-
type ToolResult = CallToolResult;
|
|
349
|
-
|
|
350
|
-
interface ToolDeps {
|
|
351
|
-
fetchImpl?: typeof fetch;
|
|
352
|
-
profileLoader?: () => PaymentProfile | null;
|
|
353
|
-
/** Remote HTTP auth (grant + scope + optional shared-pool pay session). */
|
|
354
|
-
auth?: McpAuthContext;
|
|
355
|
-
/**
|
|
356
|
-
* Proxy-resolved live trace for this MCP request. Observation only: it never
|
|
357
|
-
* changes which rail runs, what is paid, or whether a payment is allowed.
|
|
358
|
-
*/
|
|
359
|
-
trace?: McpTraceHooks;
|
|
360
|
-
/**
|
|
361
|
-
* Agent-held delegate key. Present only in the process that stores the
|
|
362
|
-
* key — never on the gateway. When set after a server-side prepare, `pay`
|
|
363
|
-
* verifies the prepared kind and settles through `payDelegatedSpendAccount`.
|
|
364
|
-
*/
|
|
365
|
-
agentHeld?: {
|
|
366
|
-
signer: DelegatedKeySigner;
|
|
367
|
-
getObjectJson: GetObjectJson;
|
|
368
|
-
payerDeps: DelegatedPayerDeps;
|
|
369
|
-
};
|
|
370
|
-
/** Override the prepare-time kind builder (tests / offline). */
|
|
371
|
-
buildPayKind?: SpendAccountPayDeps['buildPayKind'];
|
|
372
|
-
/**
|
|
373
|
-
* Stdio login surfaces the console URL (MCP progress notification). The URL
|
|
374
|
-
* carries the delegate address only — never the seed.
|
|
375
|
-
*/
|
|
376
|
-
onUrl?: (url: string) => void;
|
|
377
|
-
createPayer?: typeof createPayer;
|
|
378
|
-
createReader?: typeof createReader;
|
|
379
|
-
startStdioLogin?: typeof startStdioLogin;
|
|
380
|
-
}
|
|
381
|
-
declare function pay(args: {
|
|
382
|
-
url: string;
|
|
383
|
-
method?: unknown;
|
|
384
|
-
json?: unknown;
|
|
385
|
-
recipient?: unknown;
|
|
386
|
-
maxAmount?: unknown;
|
|
387
|
-
}, cfg: McpConfig, deps?: ToolDeps): Promise<ToolResult>;
|
|
388
|
-
declare function receipts(args: {
|
|
389
|
-
challengeId?: string;
|
|
390
|
-
}, cfg: McpConfig, deps?: ToolDeps): Promise<ToolResult>;
|
|
391
|
-
|
|
392
318
|
declare function loadBootProfile(load?: typeof loadPaymentProfile): {
|
|
393
319
|
profile: ReturnType<typeof loadPaymentProfile>;
|
|
394
320
|
unreadable: boolean;
|
|
395
321
|
};
|
|
396
322
|
declare function main(): Promise<void>;
|
|
397
323
|
|
|
398
|
-
export {
|
|
324
|
+
export { type PreparedPayment, type SpendAccountPrepareResult, type SpendAccountPrepareSession, type SpendAccountSigningRequest, buildSpendAccountPayKind, ensureLocalSigningKey, loadBootProfile, loadLocalSigner, main, prepareSpendAccountPayment, settlePreparedMcpPayment, verifySpendAccountPayReconstructs };
|
package/dist/index.js
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
2
|
loadBootProfile,
|
|
3
3
|
main
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-EM7PKWVL.js";
|
|
5
5
|
import {
|
|
6
|
-
MCP_TARGET_PRESETS,
|
|
7
6
|
PACKAGE_NAME,
|
|
8
7
|
TOOLS,
|
|
9
8
|
buildSpendAccountPayKind,
|
|
@@ -14,21 +13,16 @@ import {
|
|
|
14
13
|
inspectMcpEnvelope,
|
|
15
14
|
loadLocalSigner,
|
|
16
15
|
loadMcpConfig,
|
|
17
|
-
parseMcpTargetFlag,
|
|
18
|
-
pay,
|
|
19
16
|
prepareSpendAccountPayment,
|
|
20
|
-
receipts,
|
|
21
17
|
resetAgentHeldProcessLedger,
|
|
22
18
|
resolveAgentHeldSettlement,
|
|
23
19
|
resolveGrantTarget,
|
|
24
|
-
resolveMcpTarget,
|
|
25
|
-
sameMcpGateway,
|
|
26
20
|
settlePreparedMcpPayment,
|
|
27
21
|
traceMcpHttpRequest,
|
|
28
22
|
verifySpendAccountPayReconstructs
|
|
29
|
-
} from "./chunk-
|
|
23
|
+
} from "./chunk-26NKQ37M.js";
|
|
24
|
+
import "./chunk-A3JIIDYT.js";
|
|
30
25
|
export {
|
|
31
|
-
MCP_TARGET_PRESETS,
|
|
32
26
|
PACKAGE_NAME,
|
|
33
27
|
TOOLS,
|
|
34
28
|
buildSpendAccountPayKind,
|
|
@@ -41,15 +35,10 @@ export {
|
|
|
41
35
|
loadLocalSigner,
|
|
42
36
|
loadMcpConfig,
|
|
43
37
|
main,
|
|
44
|
-
parseMcpTargetFlag,
|
|
45
|
-
pay,
|
|
46
38
|
prepareSpendAccountPayment,
|
|
47
|
-
receipts,
|
|
48
39
|
resetAgentHeldProcessLedger,
|
|
49
40
|
resolveAgentHeldSettlement,
|
|
50
41
|
resolveGrantTarget,
|
|
51
|
-
resolveMcpTarget,
|
|
52
|
-
sameMcpGateway,
|
|
53
42
|
settlePreparedMcpPayment,
|
|
54
43
|
traceMcpHttpRequest,
|
|
55
44
|
verifySpendAccountPayReconstructs
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
import {
|
|
2
|
+
SPEND_ACCOUNT_DEBIT_FUNCTIONS
|
|
3
|
+
} from "./chunk-A3JIIDYT.js";
|
|
4
|
+
|
|
5
|
+
// ../../src/gateway/sponsor/personal-message.ts
|
|
6
|
+
import { createHash, randomBytes } from "crypto";
|
|
7
|
+
import { bcs } from "@mysten/sui/bcs";
|
|
8
|
+
import {
|
|
9
|
+
isValidPersonalMessageSignature,
|
|
10
|
+
verifyPersonalMessageSignature
|
|
11
|
+
} from "@mysten/sui/verify";
|
|
12
|
+
import {
|
|
13
|
+
fromBase64,
|
|
14
|
+
normalizeSuiAddress,
|
|
15
|
+
toBase64
|
|
16
|
+
} from "@mysten/sui/utils";
|
|
17
|
+
import { Transaction } from "@mysten/sui/transactions";
|
|
18
|
+
var SPONSOR_PERSONAL_MESSAGE_DOMAIN = "suipay:sponsor-personal-message:v1";
|
|
19
|
+
var SPONSOR_PERSONAL_MESSAGE_DEFAULT_TTL_MS = 6e4;
|
|
20
|
+
var SPONSOR_PERSONAL_MESSAGE_DEFAULT_GAS_BUDGET_MIST = 50000000n;
|
|
21
|
+
function encodeSponsorPersonalMessagePayload(payload) {
|
|
22
|
+
if (payload.domain !== SPONSOR_PERSONAL_MESSAGE_DOMAIN) {
|
|
23
|
+
throw new Error(`domain must be ${SPONSOR_PERSONAL_MESSAGE_DOMAIN}`);
|
|
24
|
+
}
|
|
25
|
+
if (payload.txKindHash.length !== 32) {
|
|
26
|
+
throw new Error("txKindHash must be 32 bytes");
|
|
27
|
+
}
|
|
28
|
+
if (!payload.nonce.trim()) throw new Error("nonce is required");
|
|
29
|
+
if (!Number.isFinite(payload.expiryMs) || payload.expiryMs <= 0) {
|
|
30
|
+
throw new Error("expiryMs must be a positive finite number");
|
|
31
|
+
}
|
|
32
|
+
if (!payload.requestId.trim()) throw new Error("requestId is required");
|
|
33
|
+
if (!payload.network.trim()) throw new Error("network is required");
|
|
34
|
+
const parts = [];
|
|
35
|
+
const push = (u) => parts.push(u);
|
|
36
|
+
push(
|
|
37
|
+
bcs.vector(bcs.u8()).serialize([...Buffer.from(payload.domain, "utf8")]).toBytes()
|
|
38
|
+
);
|
|
39
|
+
push(
|
|
40
|
+
bcs.vector(bcs.u8()).serialize([...Buffer.from(payload.nonce, "utf8")]).toBytes()
|
|
41
|
+
);
|
|
42
|
+
push(bcs.u64().serialize(BigInt(payload.expiryMs)).toBytes());
|
|
43
|
+
push(bcs.Address.serialize(normalizeSuiAddress(payload.delegate)).toBytes());
|
|
44
|
+
push(bcs.Address.serialize(normalizeSuiAddress(payload.poolId)).toBytes());
|
|
45
|
+
push(bcs.Address.serialize(normalizeSuiAddress(payload.grantId)).toBytes());
|
|
46
|
+
push(
|
|
47
|
+
bcs.vector(bcs.u8()).serialize([...Buffer.from(payload.network, "utf8")]).toBytes()
|
|
48
|
+
);
|
|
49
|
+
push(bcs.Address.serialize(normalizeSuiAddress(payload.packageId)).toBytes());
|
|
50
|
+
push(
|
|
51
|
+
bcs.vector(bcs.u8()).serialize([...Buffer.from(payload.requestId, "utf8")]).toBytes()
|
|
52
|
+
);
|
|
53
|
+
push(bcs.vector(bcs.u8()).serialize([...payload.txKindHash]).toBytes());
|
|
54
|
+
return Buffer.concat(parts.map((p) => Buffer.from(p)));
|
|
55
|
+
}
|
|
56
|
+
function hashTransactionKindBytes(kindBytes) {
|
|
57
|
+
const raw = typeof kindBytes === "string" ? fromBase64(kindBytes) : kindBytes;
|
|
58
|
+
return createHash("sha256").update(raw).digest();
|
|
59
|
+
}
|
|
60
|
+
function txKindHashHex(kindBytes) {
|
|
61
|
+
return Buffer.from(hashTransactionKindBytes(kindBytes)).toString("hex");
|
|
62
|
+
}
|
|
63
|
+
function buildSponsorPersonalMessagePayload(input) {
|
|
64
|
+
return {
|
|
65
|
+
domain: SPONSOR_PERSONAL_MESSAGE_DOMAIN,
|
|
66
|
+
nonce: input.nonce,
|
|
67
|
+
expiryMs: input.expiryMs,
|
|
68
|
+
delegate: normalizeSuiAddress(input.delegate),
|
|
69
|
+
poolId: normalizeSuiAddress(input.poolId),
|
|
70
|
+
grantId: normalizeSuiAddress(input.grantId),
|
|
71
|
+
network: input.network,
|
|
72
|
+
packageId: normalizeSuiAddress(input.packageId),
|
|
73
|
+
requestId: input.requestId,
|
|
74
|
+
txKindHash: hashTransactionKindBytes(input.transactionKindBytes)
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
async function verifySponsorPersonalMessageSignature(input) {
|
|
78
|
+
try {
|
|
79
|
+
const expected = normalizeSuiAddress(input.expectedDelegate);
|
|
80
|
+
const encoded = encodeSponsorPersonalMessagePayload(input.payload);
|
|
81
|
+
const addressMatches = normalizeSuiAddress(input.payload.delegate) === expected;
|
|
82
|
+
if (!addressMatches) {
|
|
83
|
+
return { ok: false, reason: "payload_delegate_mismatch" };
|
|
84
|
+
}
|
|
85
|
+
try {
|
|
86
|
+
const publicKey = await verifyPersonalMessageSignature(
|
|
87
|
+
encoded,
|
|
88
|
+
input.signature,
|
|
89
|
+
{ address: expected }
|
|
90
|
+
);
|
|
91
|
+
const derived = publicKey.toSuiAddress();
|
|
92
|
+
if (normalizeSuiAddress(derived) !== expected) {
|
|
93
|
+
return { ok: false, reason: "address_derivation_mismatch" };
|
|
94
|
+
}
|
|
95
|
+
return { ok: true, address: expected };
|
|
96
|
+
} catch {
|
|
97
|
+
const ok = await isValidPersonalMessageSignature(
|
|
98
|
+
encoded,
|
|
99
|
+
input.signature,
|
|
100
|
+
{ address: expected }
|
|
101
|
+
);
|
|
102
|
+
if (!ok) return { ok: false, reason: "bad_signature" };
|
|
103
|
+
return { ok: true, address: expected };
|
|
104
|
+
}
|
|
105
|
+
} catch {
|
|
106
|
+
return { ok: false, reason: "bad_signature" };
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
function assertPersonalMessageKindIsPermittedPay(input) {
|
|
110
|
+
const allowed = new Set(
|
|
111
|
+
input.allowedFunctions ?? SPEND_ACCOUNT_DEBIT_FUNCTIONS
|
|
112
|
+
);
|
|
113
|
+
let data;
|
|
114
|
+
try {
|
|
115
|
+
data = Transaction.fromKind(
|
|
116
|
+
fromBase64(input.transactionKindBytes)
|
|
117
|
+
).getData();
|
|
118
|
+
} catch {
|
|
119
|
+
return { ok: false, reason: "kind_invalid" };
|
|
120
|
+
}
|
|
121
|
+
if (JSON.stringify(data.commands).includes('"GasCoin"')) {
|
|
122
|
+
return { ok: false, reason: "kind_gas_coin" };
|
|
123
|
+
}
|
|
124
|
+
const commands = data.commands ?? [];
|
|
125
|
+
if (commands.length !== 1) {
|
|
126
|
+
return { ok: false, reason: "kind_extra_commands" };
|
|
127
|
+
}
|
|
128
|
+
const cmd = commands[0];
|
|
129
|
+
if (cmd.$kind !== "MoveCall") {
|
|
130
|
+
return { ok: false, reason: "kind_not_move_call" };
|
|
131
|
+
}
|
|
132
|
+
const move = cmd.MoveCall;
|
|
133
|
+
if (!move) return { ok: false, reason: "kind_missing_move_call" };
|
|
134
|
+
const pkg = normalizeSuiAddress(String(move.package));
|
|
135
|
+
const expectedPkg = normalizeSuiAddress(input.packageId);
|
|
136
|
+
if (pkg !== expectedPkg) {
|
|
137
|
+
return { ok: false, reason: "kind_package_mismatch" };
|
|
138
|
+
}
|
|
139
|
+
if (String(move.module) !== "spend_account") {
|
|
140
|
+
return { ok: false, reason: "kind_module_not_shared_pool" };
|
|
141
|
+
}
|
|
142
|
+
const fn = String(move.function);
|
|
143
|
+
if (!allowed.has(fn)) {
|
|
144
|
+
return { ok: false, reason: "kind_function_not_permitted" };
|
|
145
|
+
}
|
|
146
|
+
const args = move.arguments ?? [];
|
|
147
|
+
if (args.length < 2) {
|
|
148
|
+
return { ok: false, reason: "kind_args_incomplete" };
|
|
149
|
+
}
|
|
150
|
+
const poolObj = objectIdFromArg(args[0], data.inputs);
|
|
151
|
+
const grantObj = objectIdFromArg(args[1], data.inputs);
|
|
152
|
+
if (!poolObj || !grantObj) {
|
|
153
|
+
return { ok: false, reason: "kind_object_unresolved" };
|
|
154
|
+
}
|
|
155
|
+
if (normalizeSuiAddress(poolObj) !== normalizeSuiAddress(input.poolObjectId)) {
|
|
156
|
+
return { ok: false, reason: "kind_pool_mismatch" };
|
|
157
|
+
}
|
|
158
|
+
if (normalizeSuiAddress(grantObj) !== normalizeSuiAddress(input.grantObjectId)) {
|
|
159
|
+
return { ok: false, reason: "kind_grant_mismatch" };
|
|
160
|
+
}
|
|
161
|
+
return { ok: true, functionName: String(move.function) };
|
|
162
|
+
}
|
|
163
|
+
function assertPersonalMessageDeploymentBinding(input) {
|
|
164
|
+
const wantNet = input.expectedNetwork.trim().toLowerCase();
|
|
165
|
+
const gotNet = input.network.trim().toLowerCase();
|
|
166
|
+
if (!wantNet || gotNet !== wantNet) {
|
|
167
|
+
return { ok: false, reason: "network_mismatch" };
|
|
168
|
+
}
|
|
169
|
+
try {
|
|
170
|
+
if (normalizeSuiAddress(input.packageId) !== normalizeSuiAddress(input.expectedPackageId)) {
|
|
171
|
+
return { ok: false, reason: "package_mismatch" };
|
|
172
|
+
}
|
|
173
|
+
} catch {
|
|
174
|
+
return { ok: false, reason: "package_mismatch" };
|
|
175
|
+
}
|
|
176
|
+
return { ok: true };
|
|
177
|
+
}
|
|
178
|
+
function publicSponsorGrantDenyReason(reason) {
|
|
179
|
+
const r = reason.toLowerCase();
|
|
180
|
+
if (r.includes("expired")) return "grant_expired";
|
|
181
|
+
if (r.includes("revok")) return "grant_revoked";
|
|
182
|
+
if (r.includes("paus")) return "grant_paused";
|
|
183
|
+
if (r.includes("not active") || r.includes("not_active")) {
|
|
184
|
+
return "grant_not_active";
|
|
185
|
+
}
|
|
186
|
+
if (r.includes("no active") || r.includes("not found") || r.includes("missing")) {
|
|
187
|
+
return "grant_not_found";
|
|
188
|
+
}
|
|
189
|
+
return "grant_not_authorized";
|
|
190
|
+
}
|
|
191
|
+
function objectIdFromArg(arg, inputs) {
|
|
192
|
+
if (!arg || typeof arg !== "object") return null;
|
|
193
|
+
const a = arg;
|
|
194
|
+
if (a.$kind !== "Input" || !Number.isInteger(a.Input)) return null;
|
|
195
|
+
const input = inputs?.[Number(a.Input)];
|
|
196
|
+
if (!input || typeof input !== "object") return null;
|
|
197
|
+
const value = input;
|
|
198
|
+
if (value.$kind !== "Object" || !value.Object) return null;
|
|
199
|
+
const object = value.Object;
|
|
200
|
+
const id = object.$kind === "SharedObject" ? object.SharedObject?.objectId : object.$kind === "ImmOrOwnedObject" ? object.ImmOrOwnedObject?.objectId : object.$kind === "Receiving" ? object.Receiving?.objectId : void 0;
|
|
201
|
+
return typeof id === "string" ? id : null;
|
|
202
|
+
}
|
|
203
|
+
function createResolveSponsorGrant(store) {
|
|
204
|
+
return async (query) => {
|
|
205
|
+
const found = await store.findActiveGrantForPersonalMessage({
|
|
206
|
+
grantObjectId: query.grantObjectId,
|
|
207
|
+
poolObjectId: query.poolObjectId,
|
|
208
|
+
delegateAddress: query.delegateAddress
|
|
209
|
+
});
|
|
210
|
+
if (!found) {
|
|
211
|
+
return { ok: false, reason: "grant_not_found" };
|
|
212
|
+
}
|
|
213
|
+
const expiresAtMs = Number(found.grant.expiresAtMs);
|
|
214
|
+
if (Number.isFinite(expiresAtMs) && expiresAtMs > 0 && expiresAtMs <= query.nowMs) {
|
|
215
|
+
return { ok: false, reason: "grant_expired" };
|
|
216
|
+
}
|
|
217
|
+
if (found.grant.status === "revoked") {
|
|
218
|
+
return { ok: false, reason: "grant_revoked" };
|
|
219
|
+
}
|
|
220
|
+
if (found.grant.status !== "active") {
|
|
221
|
+
return { ok: false, reason: "grant_not_active" };
|
|
222
|
+
}
|
|
223
|
+
if (found.aggregateStatus === "paused" || found.aggregateStatus === "pausing") {
|
|
224
|
+
return { ok: false, reason: "grant_paused" };
|
|
225
|
+
}
|
|
226
|
+
if (found.aggregateStatus === "revoking" || found.aggregateStatus === "partially_revoked" || found.aggregateStatus === "revoked") {
|
|
227
|
+
return { ok: false, reason: "grant_revoked" };
|
|
228
|
+
}
|
|
229
|
+
if (!found.grant.grantObjectId) {
|
|
230
|
+
return { ok: false, reason: "grant_not_found" };
|
|
231
|
+
}
|
|
232
|
+
return {
|
|
233
|
+
ok: true,
|
|
234
|
+
grant: {
|
|
235
|
+
delegateAddress: normalizeSuiAddress(found.agentAddress),
|
|
236
|
+
poolObjectId: normalizeSuiAddress(found.grant.poolObjectId),
|
|
237
|
+
grantObjectId: normalizeSuiAddress(found.grant.grantObjectId),
|
|
238
|
+
expiresAtMs: Number.isFinite(expiresAtMs) ? expiresAtMs : 0,
|
|
239
|
+
status: "active"
|
|
240
|
+
}
|
|
241
|
+
};
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
function mintSponsorChallengeNonce() {
|
|
245
|
+
return randomBytes(24).toString("base64url");
|
|
246
|
+
}
|
|
247
|
+
function mintSponsorExecuteToken() {
|
|
248
|
+
return randomBytes(24).toString("base64url");
|
|
249
|
+
}
|
|
250
|
+
async function signSponsorPersonalMessagePayload(input) {
|
|
251
|
+
const encoded = encodeSponsorPersonalMessagePayload(input.payload);
|
|
252
|
+
return input.signPersonalMessage(encoded);
|
|
253
|
+
}
|
|
254
|
+
function bytesToHex(bytes) {
|
|
255
|
+
return Buffer.from(bytes).toString("hex");
|
|
256
|
+
}
|
|
257
|
+
function hexToBytes32(hex) {
|
|
258
|
+
const h = hex.replace(/^0x/i, "").toLowerCase();
|
|
259
|
+
if (!/^[0-9a-f]{64}$/.test(h)) {
|
|
260
|
+
throw new Error(`expected 32-byte hex, got ${hex}`);
|
|
261
|
+
}
|
|
262
|
+
return Buffer.from(h, "hex");
|
|
263
|
+
}
|
|
264
|
+
export {
|
|
265
|
+
SPONSOR_PERSONAL_MESSAGE_DEFAULT_GAS_BUDGET_MIST,
|
|
266
|
+
SPONSOR_PERSONAL_MESSAGE_DEFAULT_TTL_MS,
|
|
267
|
+
SPONSOR_PERSONAL_MESSAGE_DOMAIN,
|
|
268
|
+
assertPersonalMessageDeploymentBinding,
|
|
269
|
+
assertPersonalMessageKindIsPermittedPay,
|
|
270
|
+
buildSponsorPersonalMessagePayload,
|
|
271
|
+
bytesToHex,
|
|
272
|
+
createResolveSponsorGrant,
|
|
273
|
+
encodeSponsorPersonalMessagePayload,
|
|
274
|
+
fromBase64,
|
|
275
|
+
hashTransactionKindBytes,
|
|
276
|
+
hexToBytes32,
|
|
277
|
+
mintSponsorChallengeNonce,
|
|
278
|
+
mintSponsorExecuteToken,
|
|
279
|
+
publicSponsorGrantDenyReason,
|
|
280
|
+
signSponsorPersonalMessagePayload,
|
|
281
|
+
toBase64,
|
|
282
|
+
txKindHashHex,
|
|
283
|
+
verifySponsorPersonalMessageSignature
|
|
284
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cmdoss/suipay-mcp",
|
|
3
|
-
"version": "0.2.2
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"type": "module",
|
|
5
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",
|
|
@@ -19,7 +19,8 @@
|
|
|
19
19
|
"README.md"
|
|
20
20
|
],
|
|
21
21
|
"publishConfig": {
|
|
22
|
-
"access": "public"
|
|
22
|
+
"access": "public",
|
|
23
|
+
"provenance": true
|
|
23
24
|
},
|
|
24
25
|
"engines": {
|
|
25
26
|
"node": ">=20"
|
|
@@ -31,17 +32,14 @@
|
|
|
31
32
|
},
|
|
32
33
|
"dependencies": {
|
|
33
34
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
34
|
-
"@mysten/sui": "
|
|
35
|
+
"@mysten/sui": "2.24.0",
|
|
35
36
|
"@noble/ed25519": "^2.1.0",
|
|
36
37
|
"@noble/hashes": "^2.3.0",
|
|
37
38
|
"open": "^11.0.0"
|
|
38
39
|
},
|
|
39
40
|
"devDependencies": {
|
|
40
|
-
"
|
|
41
|
-
"
|
|
42
|
-
"
|
|
43
|
-
"tsup": "catalog:",
|
|
44
|
-
"typescript": "catalog:",
|
|
45
|
-
"vitest": "catalog:"
|
|
41
|
+
"tsup": "^8.0.0",
|
|
42
|
+
"typescript": "^6.0.3",
|
|
43
|
+
"vitest": "^3.0.0"
|
|
46
44
|
}
|
|
47
45
|
}
|
package/dist/chunk-K6WM4Z7H.js
DELETED
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
createServer,
|
|
3
|
-
loadLocalSigner,
|
|
4
|
-
loadLocalSigningKeyRecord,
|
|
5
|
-
loadMcpConfig,
|
|
6
|
-
loadPaymentProfile
|
|
7
|
-
} from "./chunk-NVTYOOMY.js";
|
|
8
|
-
|
|
9
|
-
// src/index.ts
|
|
10
|
-
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
11
|
-
function loadBootProfile(load = loadPaymentProfile) {
|
|
12
|
-
try {
|
|
13
|
-
return { profile: load(), unreadable: false };
|
|
14
|
-
} catch {
|
|
15
|
-
return { profile: null, unreadable: true };
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
async function main() {
|
|
19
|
-
const argv = process.argv.slice(2);
|
|
20
|
-
const cfg = loadMcpConfig(process.env, argv);
|
|
21
|
-
const target = cfg.target ?? "dev";
|
|
22
|
-
const server = createServer(cfg);
|
|
23
|
-
await server.connect(new StdioServerTransport());
|
|
24
|
-
try {
|
|
25
|
-
const boundGateway = loadLocalSigningKeyRecord()?.gatewayUrl ?? cfg.gatewayUrl;
|
|
26
|
-
const local = loadLocalSigner();
|
|
27
|
-
if (local) {
|
|
28
|
-
console.error(
|
|
29
|
-
`[suipay-mcp] ${target} ${boundGateway} \xB7 delegate ${local.address} \xB7 stdio ready`
|
|
30
|
-
);
|
|
31
|
-
} else {
|
|
32
|
-
console.error(
|
|
33
|
-
`[suipay-mcp] ${target} ${boundGateway} \xB7 not connected \xB7 run suipay_login \xB7 stdio ready`
|
|
34
|
-
);
|
|
35
|
-
}
|
|
36
|
-
} catch {
|
|
37
|
-
console.error(
|
|
38
|
-
`[suipay-mcp] ${target} ${cfg.gatewayUrl} \xB7 credentials unreadable: run suipay_login or suipay_logout force=true \xB7 stdio ready`
|
|
39
|
-
);
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export {
|
|
44
|
-
loadBootProfile,
|
|
45
|
-
main
|
|
46
|
-
};
|