@cmdoss/suipay-mcp 0.2.1 → 0.2.2-dev.1

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.
@@ -1,284 +0,0 @@
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
- };