@cmdoss/suipay-mcp 0.2.2 → 0.3.0-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.
- package/README.md +131 -11
- package/dist/bin/suipay.js +5 -4
- package/dist/chunk-FKDC22O6.js +13618 -0
- package/dist/chunk-W4RIN664.js +53 -0
- package/dist/http-BCFfQFIg.d.ts +4786 -0
- package/dist/http.d.ts +1 -1
- package/dist/http.js +1 -2
- package/dist/index.d.ts +474 -209
- package/dist/index.js +14 -3
- package/package.json +12 -9
- package/dist/chunk-26NKQ37M.js +0 -6177
- package/dist/chunk-A3JIIDYT.js +0 -161
- package/dist/chunk-EM7PKWVL.js +0 -39
- package/dist/http-BnIjiwcR.d.ts +0 -1149
- package/dist/personal-message-5PF4KVLF.js +0 -284
package/dist/index.d.ts
CHANGED
|
@@ -1,19 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export { A as AgentHeldResolver,
|
|
1
|
+
import { S as SpendAccountSigningRequest, P as PaidHttpRequest, D as DelegatedKeySigner, G as GetObjectJson, a as DelegatedPayerSession, b as DelegatedPayerConfig, c as DelegatedPayerDeps, d as SpendIntent, e as DelegatedPayResult, f as DelegationScope, C as CodeFor, N as NextAction, g as GrantPolicyBinding, h as PaymentAttemptLedger, i as DelegatedPayerLog, j as PreparedSettlementTerms, X as X402PayProgress, O as Offer, M as McpConfig, k as McpAuthContext, l as McpTraceHooks, m as SpendAccountPayDeps, n as createReader } from './http-BCFfQFIg.js';
|
|
2
|
+
export { A as AgentHeldResolver, o as AgentHeldSettlement, p as MCP_TARGET_PRESETS, q as McpEnvelope, r as McpEnvelopeEntry, s as McpServerOptions, t as McpTarget, u as McpTraceContext, v as McpTraceRuntime, w as PACKAGE_NAME, x as PreparedPayment, y as SpendAccountPrepareResult, z as SpendAccountPrepareSession, T as TOOLS, B as buildSpendAccountPayKind, E as canonicalMcpRequestId, F as createServer, H as handleSuipayMcpHttpRequest, I as inspectMcpEnvelope, J as loadMcpConfig, K as parseMcpTargetFlag, L as prepareSpendAccountPayment, Q as resetAgentHeldProcessLedger, R as resolveAgentHeldSettlement, U as resolveGrantTarget, V as resolveMcpTarget, W as sameMcpGateway, Y as traceMcpHttpRequest, Z as verifySpendAccountPayReconstructs } from './http-BCFfQFIg.js';
|
|
3
|
+
import { SuiGrpcClient } from '@mysten/sui/grpc';
|
|
4
|
+
import { Ed25519Keypair } from '@mysten/sui/keypairs/ed25519';
|
|
5
|
+
import { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
|
|
3
6
|
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
7
|
|
|
18
8
|
/** Holds a key and signs transaction bytes. Never leaves the payer. */
|
|
19
9
|
interface Wallet {
|
|
@@ -53,14 +43,9 @@ interface PaymentProfile {
|
|
|
53
43
|
/**
|
|
54
44
|
* Load a historical V1 allowance payment profile, if present.
|
|
55
45
|
* ADR-0010: not for new settlement. Callers that pay must refuse this profile
|
|
56
|
-
* and point operators at
|
|
46
|
+
* and point operators at delegate OAuth / delegated-signer flows.
|
|
57
47
|
*/
|
|
58
48
|
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
|
-
*/
|
|
64
49
|
declare function ensureLocalSigningKey(opts?: {
|
|
65
50
|
label?: string;
|
|
66
51
|
}): Promise<LocalSigningKeyIdentity>;
|
|
@@ -81,239 +66,519 @@ declare function loadLocalSigner(): {
|
|
|
81
66
|
} | null;
|
|
82
67
|
|
|
83
68
|
/**
|
|
84
|
-
*
|
|
69
|
+
* Client-side completion of an MCP prepared payment.
|
|
85
70
|
*
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
*
|
|
71
|
+
* Verifies the server-prepared kind (ADR-0016 §3) then settles through
|
|
72
|
+
* `payDelegatedSpendAccount` — the same path as direct signer mode. This module
|
|
73
|
+
* does not implement a second submitter.
|
|
74
|
+
*
|
|
75
|
+
* Two hops read a 402, and only one of them was verified. The prepare hop's kind
|
|
76
|
+
* is checked here; the payer then reads its own offer and builds the bytes it
|
|
77
|
+
* signs from that. So the verified terms are carried down as `preparedTerms` and
|
|
78
|
+
* re-asserted against the payer's offer before it signs. Without that carry, the
|
|
79
|
+
* two hops can price different recipients and nothing would notice.
|
|
90
80
|
*/
|
|
91
81
|
|
|
92
|
-
|
|
93
|
-
|
|
82
|
+
declare function settlePreparedMcpPayment(args: {
|
|
83
|
+
signingRequest: SpendAccountSigningRequest;
|
|
94
84
|
/**
|
|
95
|
-
*
|
|
96
|
-
*
|
|
97
|
-
*
|
|
85
|
+
* The same paid request that minted the prepare hop. Reconstructing from
|
|
86
|
+
* url+method alone drops a POST body and the settlement hop then prices
|
|
87
|
+
* (or 400s) a different request than the one the tool was called with.
|
|
98
88
|
*/
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
89
|
+
request: PaidHttpRequest;
|
|
90
|
+
/**
|
|
91
|
+
* What this payment is supposed to move, sourced from the caller's intent —
|
|
92
|
+
* never re-read from the object being verified. Comparing a prepared kind
|
|
93
|
+
* against fields copied out of the same prepare response proves only that the
|
|
94
|
+
* response agrees with itself.
|
|
95
|
+
*/
|
|
96
|
+
expected: {
|
|
97
|
+
amount: string;
|
|
98
|
+
recipient: string;
|
|
99
|
+
delegateAddress: string;
|
|
100
|
+
};
|
|
101
|
+
signer: DelegatedKeySigner;
|
|
102
|
+
getObjectJson: GetObjectJson;
|
|
103
|
+
session: DelegatedPayerSession;
|
|
104
|
+
cfg: DelegatedPayerConfig;
|
|
105
|
+
deps: DelegatedPayerDeps;
|
|
106
|
+
/**
|
|
107
|
+
* Agent-declared spend bound. Required when `deps.offerSecret` is absent
|
|
108
|
+
* (stdio local key): the HMAC is a gateway secret the agent must not need.
|
|
109
|
+
*/
|
|
110
|
+
intent?: SpendIntent;
|
|
111
|
+
}): Promise<DelegatedPayResult>;
|
|
106
112
|
|
|
107
113
|
/**
|
|
108
|
-
*
|
|
114
|
+
* The executable settlement a 402 carries so the agent needs no chain access.
|
|
109
115
|
*
|
|
110
|
-
*
|
|
111
|
-
*
|
|
112
|
-
*
|
|
113
|
-
*
|
|
114
|
-
*
|
|
115
|
-
*
|
|
116
|
-
* private key is never placed in an HTTP body, a bearer, or any header.
|
|
116
|
+
* ADR-0016 §2 says "the server prepares, the agent verifies, the agent signs".
|
|
117
|
+
* ADR-0015 shipped only the first half: the prepare path emitted a placeholder
|
|
118
|
+
* `poolInitialSharedVersion: '1'`, so its bytes described a payment instead of
|
|
119
|
+
* executing one, and an agent still had to make two full-node reads to build
|
|
120
|
+
* something submittable. These bytes are executable — the gateway resolves the
|
|
121
|
+
* real shared-object initial versions before it builds them.
|
|
117
122
|
*
|
|
118
|
-
*
|
|
119
|
-
*
|
|
120
|
-
*
|
|
121
|
-
*
|
|
122
|
-
*
|
|
123
|
+
* This travels ALONGSIDE the offer on the 402 body, never inside it, exactly as
|
|
124
|
+
* `requestSchema` does (`src/gateway/app.ts`). The offer digest is a
|
|
125
|
+
* consensus-relevant wire shape whose fields go on chain via `terms_hash`;
|
|
126
|
+
* adding to it is a protocol change. A prepared settlement authorizes nothing
|
|
127
|
+
* on its own — it is unsigned bytes, worthless until the delegate's key signs
|
|
128
|
+
* them — so it has no business inside a signed digest.
|
|
123
129
|
*
|
|
124
|
-
*
|
|
130
|
+
* Every field here exists to let the agent REBUILD the same transaction kind
|
|
131
|
+
* offline and compare it byte for byte. Nothing here is trusted: see
|
|
132
|
+
* `verifyPreparedPayment` in `src/agent-sdk/verify-prepared-settlement.ts`
|
|
133
|
+
* for what an agent must check before it signs, and why what it can check from
|
|
134
|
+
* the 402 alone is sufficient.
|
|
125
135
|
*/
|
|
126
136
|
|
|
137
|
+
/** Current prepared-settlement wire version. Bumped on any shape change. */
|
|
138
|
+
declare const PREPARED_SETTLEMENT_VERSION: 1;
|
|
127
139
|
/**
|
|
128
|
-
*
|
|
129
|
-
*
|
|
130
|
-
*
|
|
140
|
+
* An unsigned, executable settlement transaction plus everything needed to
|
|
141
|
+
* reconstruct it offline.
|
|
142
|
+
*
|
|
143
|
+
* `transactionBytes` is the full sponsored `TransactionData` — gas is already
|
|
144
|
+
* attached by the gateway's sponsor, so this is the ONE thing the agent signs.
|
|
145
|
+
* `transactionKindBytes` is the pre-sponsor programmable-transaction kind: the
|
|
146
|
+
* Move call and nothing else, which is what the agent rebuilds and compares.
|
|
131
147
|
*/
|
|
132
|
-
interface
|
|
133
|
-
|
|
134
|
-
/**
|
|
135
|
-
|
|
148
|
+
interface PreparedSettlement {
|
|
149
|
+
version: typeof PREPARED_SETTLEMENT_VERSION;
|
|
150
|
+
/** Base64 sponsored `TransactionData`. The agent signs exactly these bytes. */
|
|
151
|
+
transactionBytes: string;
|
|
152
|
+
/** Base64 pre-sponsor transaction kind (the Move call baseline). */
|
|
153
|
+
transactionKindBytes: string;
|
|
136
154
|
/**
|
|
137
|
-
*
|
|
138
|
-
*
|
|
139
|
-
*
|
|
140
|
-
* the prepare (on-chain `spend_account::settle_policy_payment` remains the final backstop for any
|
|
141
|
-
* spend the client later submits).
|
|
155
|
+
* The address that must sign. Move asserts `ctx.sender() == grant.delegate`,
|
|
156
|
+
* so an agent that checks this equals its own address has the chain enforce
|
|
157
|
+
* that the named grant is one it may draw on — no `getObject` required.
|
|
142
158
|
*/
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
buildPayKind?: (input: {
|
|
149
|
-
delegate: string;
|
|
150
|
-
coinType: string;
|
|
151
|
-
poolObjectId: string;
|
|
152
|
-
grantObjectId: string;
|
|
153
|
-
policyId: number | string;
|
|
154
|
-
targetHash: string;
|
|
155
|
-
amount: string;
|
|
156
|
-
paymentIdHash: Uint8Array;
|
|
157
|
-
termsHash: Uint8Array;
|
|
158
|
-
recipient?: string;
|
|
159
|
-
accessMode?: 'scoped' | 'open';
|
|
160
|
-
}) => Promise<{
|
|
161
|
-
kindBytes: string;
|
|
162
|
-
}>;
|
|
163
|
-
/**
|
|
164
|
-
* Live trace for this prepare. Purely observational: emission records what the
|
|
165
|
-
* canonical read decisions already returned — an event is written after a check
|
|
166
|
-
* passes and can never be the thing that lets the next step run.
|
|
167
|
-
*/
|
|
168
|
-
trace?: SpendAccountPayTrace;
|
|
169
|
-
}
|
|
170
|
-
interface SpendAccountPayTrace {
|
|
171
|
-
sink: TraceSink;
|
|
172
|
-
traceId: string;
|
|
173
|
-
requestId: string | null;
|
|
174
|
-
buyerAccountId: string;
|
|
175
|
-
connectionId: string;
|
|
176
|
-
/** HMAC secret for signed cross-request context. Absent disables signing. */
|
|
177
|
-
secret?: string | null;
|
|
178
|
-
/** Injected clock, for tests. */
|
|
179
|
-
now?: () => number;
|
|
180
|
-
}
|
|
181
|
-
/** The unsigned payment the client signs locally and submits. */
|
|
182
|
-
interface SpendAccountSigningRequest {
|
|
159
|
+
sender: string;
|
|
160
|
+
/** Deployed `suipay` package the call targets (normalized). */
|
|
161
|
+
packageId: string;
|
|
162
|
+
/** Fully-qualified `package::module::function` the call invokes. */
|
|
163
|
+
moveTarget: string;
|
|
183
164
|
/**
|
|
184
|
-
*
|
|
185
|
-
*
|
|
186
|
-
*
|
|
165
|
+
* Delegation scope: `open` pays an arbitrary recipient, `scoped` an
|
|
166
|
+
* allowlist. This is the SCOPE discriminator (which pay function) — distinct
|
|
167
|
+
* from the x402 eligibility (`SpendAccountGrantKind`) and the budget cap. The
|
|
168
|
+
* `'open'`/`'scoped'` value strings are a frozen wire shape (parsed by
|
|
169
|
+
* `asPreparedSettlement`); the type only names the concept.
|
|
187
170
|
*/
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
packageId: string;
|
|
171
|
+
accessMode: DelegationScope;
|
|
172
|
+
/** Shared `Account` the payment debits. */
|
|
191
173
|
poolObjectId: string;
|
|
174
|
+
/** Shared `Grant` naming the delegate. */
|
|
192
175
|
grantObjectId: string;
|
|
193
|
-
/**
|
|
194
|
-
|
|
176
|
+
/** Real initial shared versions — the gap ADR-0015's placeholder `'1'` left. */
|
|
177
|
+
poolInitialSharedVersion: string;
|
|
178
|
+
grantInitialSharedVersion: string;
|
|
179
|
+
clockInitialSharedVersion: string;
|
|
180
|
+
/** On-chain policy id; `'0'` for an open grant. Decimal string (u64). */
|
|
181
|
+
policyId: string;
|
|
182
|
+
/** Coin type the call spends. Must equal the offer's asset. */
|
|
195
183
|
coinType: string;
|
|
196
|
-
|
|
184
|
+
/** Atomic amount the call moves. Must equal the offer's amount. */
|
|
197
185
|
amount: string;
|
|
198
|
-
/**
|
|
199
|
-
paymentIdHashHex: string;
|
|
200
|
-
/** Hex (no 0x) of the terms hash bound into the transaction. */
|
|
201
|
-
termsHashHex: string;
|
|
202
|
-
challengeId: string;
|
|
203
|
-
/** The exact paid request to replay after the client submits. */
|
|
204
|
-
resource: {
|
|
205
|
-
url: string;
|
|
206
|
-
method: string;
|
|
207
|
-
};
|
|
208
|
-
dialect: Dialect;
|
|
209
|
-
/** Recipient the agent requested (offer.payTo). */
|
|
186
|
+
/** Recipient the call pays. Must equal the offer's payTo. */
|
|
210
187
|
recipient: string;
|
|
211
|
-
/** Where the client (holding the key) sponsors and submits. */
|
|
212
|
-
submit: {
|
|
213
|
-
sponsorUrl: string;
|
|
214
|
-
executeUrl: string;
|
|
215
|
-
moveCallTarget: string;
|
|
216
|
-
};
|
|
217
188
|
/**
|
|
218
|
-
*
|
|
219
|
-
*
|
|
220
|
-
*
|
|
189
|
+
* Fresh lineage (ADR-0043 Option B): settlement is MANDATORY-guarded, so the
|
|
190
|
+
* prepared call always consumes the offer's single-use exclusion marker before
|
|
191
|
+
* funds move. These four let the agent rebuild the SAME guarded kind offline
|
|
192
|
+
* and compare it byte for byte. The `ExclusionDirectory` the offer committed
|
|
193
|
+
* to (from its `registryIdentity`), read-only.
|
|
221
194
|
*/
|
|
222
|
-
|
|
223
|
-
/**
|
|
224
|
-
|
|
195
|
+
exclusionDirectoryId: string;
|
|
196
|
+
/** The per-recipient `RecipientRegistry` the marker is claimed in (mutable). */
|
|
197
|
+
recipientRegistryId: string;
|
|
198
|
+
/** Real initial shared version of the exclusion directory. */
|
|
199
|
+
exclusionDirectoryInitialSharedVersion: string;
|
|
200
|
+
/** Real initial shared version of the recipient registry. */
|
|
201
|
+
recipientRegistryInitialSharedVersion: string;
|
|
202
|
+
/** Signed expiry (epoch-ms, decimal string) bound into the guarded settle. */
|
|
203
|
+
expiresAtMs: string;
|
|
204
|
+
/** Absolute URL to POST `{challengeId, signature, body}` to. */
|
|
205
|
+
payUrl: string;
|
|
225
206
|
}
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Connection-manifest → SDK session bridge.
|
|
210
|
+
*
|
|
211
|
+
* The dashboard hands an agent a non-secret "Connection manifest" after it
|
|
212
|
+
* authorizes a delegate key. Historically that manifest could NOT be passed to
|
|
213
|
+
* the SDK: it named the signer `agentAddress` (not `delegateAddress`), the asset
|
|
214
|
+
* by symbol (`"USDC"`, not the full `coinType`), and its policies by off-chain
|
|
215
|
+
* UUID — while the payer needs the full coin type and each policy's NUMERIC
|
|
216
|
+
* on-chain id + target hashes (`GrantPolicyBinding`). This module closes that
|
|
217
|
+
* mismatch two ways:
|
|
218
|
+
*
|
|
219
|
+
* 1. {@link AgentConnectionManifest} — the SDK-consumable shape a corrected
|
|
220
|
+
* console emits directly (delegate address, full coin type, numeric policy
|
|
221
|
+
* ids + target hashes). {@link manifestToSession} turns it, plus an agent
|
|
222
|
+
* key, into a ready `DelegatedPayerSession` — no protocol reverse-engineering
|
|
223
|
+
* and no on-chain lookups.
|
|
224
|
+
*
|
|
225
|
+
* 2. {@link dashboardManifestToAgentManifest} — a bridge from the legacy
|
|
226
|
+
* console shape, resolving asset→coinType and (for scoped grants) UUID→
|
|
227
|
+
* numeric policy binding through injected resolvers.
|
|
228
|
+
*
|
|
229
|
+
* Every failure is a typed, caller-safe error (never a thrown internal): a
|
|
230
|
+
* malformed or legacy manifest that cannot be resolved fails loudly, not
|
|
231
|
+
* silently.
|
|
232
|
+
*/
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* A manifest or session that cannot be paid from. Nothing was signed, so the
|
|
236
|
+
* money state is always `not_charged`. Branch on `code`; `reason` names the
|
|
237
|
+
* internal check for logs.
|
|
238
|
+
*/
|
|
239
|
+
interface ManifestError {
|
|
240
|
+
code: CodeFor<'sdk'>;
|
|
241
|
+
detail: string;
|
|
242
|
+
paymentState: 'not_charged';
|
|
243
|
+
retryable: boolean;
|
|
244
|
+
nextAction: NextAction;
|
|
230
245
|
}
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
246
|
+
type ManifestResult<T> = {
|
|
247
|
+
ok: true;
|
|
248
|
+
value: T;
|
|
249
|
+
} | {
|
|
250
|
+
ok: false;
|
|
251
|
+
error: ManifestError;
|
|
252
|
+
};
|
|
253
|
+
/**
|
|
254
|
+
* A manifest policy binding. `onChainPolicyId` is always a validated u64 DECIMAL
|
|
255
|
+
* STRING — never a JS number — so `BigInt(onChainPolicyId)` on the money path
|
|
256
|
+
* (`spend_account::settle_policy`) is exact. A `u64` policy id can exceed `2^53`, past which
|
|
257
|
+
* a JS number loses precision and would bind the WRONG on-chain policy; carrying
|
|
258
|
+
* the string preserves every id exactly.
|
|
259
|
+
*/
|
|
260
|
+
type AgentPolicyBinding = Omit<GrantPolicyBinding, 'onChainPolicyId'> & {
|
|
261
|
+
onChainPolicyId: string;
|
|
262
|
+
};
|
|
263
|
+
/** One grant's SDK-consumable binding: everything the payer needs to settle. */
|
|
264
|
+
interface AgentSessionBinding {
|
|
265
|
+
/** Display asset symbol (informational; `coinType` is authoritative). */
|
|
266
|
+
asset: string;
|
|
267
|
+
/** Full coin struct type, e.g. `0x..::usdc::USDC`. Required for pay. */
|
|
237
268
|
coinType: string;
|
|
238
269
|
poolObjectId: string;
|
|
239
270
|
grantObjectId: string;
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
271
|
+
/** `open` = capped, no allowlist; `scoped` = policy-bound. */
|
|
272
|
+
accessMode: 'scoped' | 'open';
|
|
273
|
+
/** Grant-level session cap (atomic); null/undefined = unlimited. */
|
|
274
|
+
sessionCap?: string | null;
|
|
275
|
+
/** Grant-level per-payment ceiling (atomic); null/undefined = unlimited. */
|
|
276
|
+
maxPerPayment?: string | null;
|
|
277
|
+
/** On-chain policy ids (u64 strings) + target hashes. Empty for open grants. */
|
|
278
|
+
policies: AgentPolicyBinding[];
|
|
279
|
+
}
|
|
280
|
+
/** Which grant to drive when a manifest carries several. */
|
|
281
|
+
interface SessionSelector {
|
|
282
|
+
grantObjectId?: string;
|
|
283
|
+
asset?: string;
|
|
284
|
+
coinType?: string;
|
|
285
|
+
}
|
|
286
|
+
/** @deprecated Renamed to {@link AgentSessionBinding}. */
|
|
287
|
+
type AgentGrantBinding = AgentSessionBinding;
|
|
288
|
+
/** @deprecated Renamed to {@link SessionSelector}. */
|
|
289
|
+
type GrantSelector = SessionSelector;
|
|
290
|
+
|
|
248
291
|
/**
|
|
249
|
-
*
|
|
250
|
-
*
|
|
251
|
-
*
|
|
292
|
+
* Key + gateway URL → a ready delegated-pay session.
|
|
293
|
+
*
|
|
294
|
+
* This module is the point of `docs/decisions/ADR-0016-delegate-key-sole-agent-input.md`
|
|
295
|
+
* §1. Before it, an agent needed a connection manifest — network, package id,
|
|
296
|
+
* pool id, grant id, caps, expiry — copied out of the console at authorize time
|
|
297
|
+
* and liable to go stale the moment the owner tightened a cap or the operator
|
|
298
|
+
* rotated a package. After it, the agent is handed one secret and one URL, and
|
|
299
|
+
* everything else is resolved from the chain's own projection at startup.
|
|
300
|
+
*
|
|
301
|
+
* The order is forced by the protocol. A bootstrap signature binds `network` and
|
|
302
|
+
* `packageId`, which the gateway reconstructs from its own config, so the agent
|
|
303
|
+
* must know them before it can sign anything that will verify. It learns them
|
|
304
|
+
* from the gateway's public deployment constants — the same two values every 402
|
|
305
|
+
* offer already carries in the clear — and only then signs.
|
|
306
|
+
*
|
|
307
|
+
* Nothing here spends. It resolves authority and hands back a session; the
|
|
308
|
+
* payment is still built and signed later, on this machine, by
|
|
309
|
+
* `payDelegatedSpendAccount` with a key that never leaves the process.
|
|
252
310
|
*/
|
|
253
|
-
|
|
254
|
-
|
|
311
|
+
|
|
312
|
+
/** The gateway's public deployment constants. */
|
|
313
|
+
interface GatewayDeployment {
|
|
314
|
+
network: string;
|
|
255
315
|
packageId: string;
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
316
|
+
/**
|
|
317
|
+
* The canonical `ExclusionDirectory` object id, present only while the gateway
|
|
318
|
+
* mints guarded offers. Absent means every offer is unguarded and no registry
|
|
319
|
+
* ever needs resolving.
|
|
320
|
+
*
|
|
321
|
+
* It enables the resolver; it is never resolved against. A guarded settle
|
|
322
|
+
* resolves the directory the OFFER committed to under the HMAC, so a gateway
|
|
323
|
+
* cannot point a payer at a directory of its choosing by answering here.
|
|
324
|
+
*/
|
|
325
|
+
exclusionDirectoryObjectId?: string;
|
|
326
|
+
}
|
|
327
|
+
|
|
261
328
|
/**
|
|
262
|
-
*
|
|
263
|
-
*
|
|
264
|
-
*
|
|
265
|
-
*
|
|
266
|
-
*
|
|
329
|
+
* A delegate key and a gateway URL, and the agent can pay.
|
|
330
|
+
*
|
|
331
|
+
* `resolvePayerSession` already turned a key into a pay *session*
|
|
332
|
+
* (ADR-0016 §1). What it did not do was assemble the rest of what
|
|
333
|
+
* `payDelegatedSpendAccount` demands, so a real agent still had to supply four
|
|
334
|
+
* things its key does not carry: the gateway's own HMAC secret, a fullnode
|
|
335
|
+
* connection to confirm settlement, a second fullnode read for live
|
|
336
|
+
* shared-object versions, and a personal-message signer for the sponsor's
|
|
337
|
+
* personal-message challenge, separate from the transaction signer. This module is
|
|
338
|
+
* where those four go.
|
|
339
|
+
*
|
|
340
|
+
* Where each one went, and why that is safe:
|
|
341
|
+
*
|
|
342
|
+
* - **The gateway's HMAC secret is gone, not relocated.** It authenticated the
|
|
343
|
+
* offer's issuer to the agent — but the issuer is the same gateway the agent
|
|
344
|
+
* is paying, reached over TLS at an origin the payer pins, so a gateway that
|
|
345
|
+
* wanted to overcharge never needed to forge its own offer. What replaces it
|
|
346
|
+
* is a bound the gateway cannot see: the agent's own {@link SpendIntent}.
|
|
347
|
+
* - **Both fullnode reads stay with the AGENT**, against a public Sui fullnode
|
|
348
|
+
* derived from the network the gateway reports. They are not proxied through
|
|
349
|
+
* the gateway, because their whole value is that they are independent of it:
|
|
350
|
+
* an agent that asked the gateway whether the gateway's transaction settled
|
|
351
|
+
* would be verifying nothing. The gateway supplies no input here it could
|
|
352
|
+
* not already supply; what it never gains is the ability to answer for the
|
|
353
|
+
* chain.
|
|
354
|
+
* - **The personal-message challenge signer is the same key**, taken from the one
|
|
355
|
+
* keypair the agent already holds. It was never a second credential, only a
|
|
356
|
+
* second callback the caller had to wire.
|
|
357
|
+
*
|
|
358
|
+
* The key never leaves this process, and the gateway never gains the ability to
|
|
359
|
+
* spend: every payment is still built here, checked here against the chain's
|
|
360
|
+
* own view of the grant, and signed here.
|
|
267
361
|
*/
|
|
268
|
-
declare function prepareSpendAccountPayment(args: {
|
|
269
|
-
url: string;
|
|
270
|
-
/** The one normalized paid request, replayed byte for byte after signing. */
|
|
271
|
-
request?: PaidHttpRequest;
|
|
272
|
-
session: SpendAccountPrepareSession;
|
|
273
|
-
cfg: McpConfig;
|
|
274
|
-
deps?: SpendAccountPayDeps;
|
|
275
|
-
/** Preferred dialect when the 402 advertises both (default mpp). */
|
|
276
|
-
preferredDialect?: Dialect;
|
|
277
|
-
}): Promise<SpendAccountPrepareResult>;
|
|
278
362
|
|
|
363
|
+
interface PayerOptions {
|
|
364
|
+
/** Gateway origin, e.g. `https://gateway.suipay.dev`. */
|
|
365
|
+
gatewayUrl: string;
|
|
366
|
+
/**
|
|
367
|
+
* The delegate key. Used for the bootstrap signature, the sponsor
|
|
368
|
+
* personal-message challenge, and the payment signature — one key, three uses, none
|
|
369
|
+
* of which leave this process.
|
|
370
|
+
*/
|
|
371
|
+
keypair: Ed25519Keypair;
|
|
372
|
+
/** Which grant to drive when the delegate holds several. */
|
|
373
|
+
select?: GrantSelector;
|
|
374
|
+
/** Injectable fetch (tests / custom transports). Defaults to global `fetch`. */
|
|
375
|
+
fetchImpl?: typeof fetch;
|
|
376
|
+
/**
|
|
377
|
+
* Sui fullnode for the agent's own on-chain reads. Defaults to the public
|
|
378
|
+
* fullnode for the network the gateway reports. An operator running a private
|
|
379
|
+
* network sets it; an agent does not need to.
|
|
380
|
+
*/
|
|
381
|
+
fullnodeUrl?: string;
|
|
382
|
+
/**
|
|
383
|
+
* A chain transport to read through, for a caller that already has one (or a
|
|
384
|
+
* test). The seam is the CLIENT, not the individual reads: an option to
|
|
385
|
+
* supply `getSettledTx` directly would let a caller point settlement
|
|
386
|
+
* confirmation at something that is not the chain.
|
|
387
|
+
*/
|
|
388
|
+
client?: SuiGrpcClient;
|
|
389
|
+
/**
|
|
390
|
+
* Skip deployment discovery by supplying the constants directly. Same escape
|
|
391
|
+
* hatch `resolvePayerSession` offers.
|
|
392
|
+
*/
|
|
393
|
+
deployment?: GatewayDeployment;
|
|
394
|
+
/**
|
|
395
|
+
* Durable payment-attempt ledger. Defaults to an in-process one, which is
|
|
396
|
+
* exactly-once for the lifetime of THIS process. An agent that must survive a
|
|
397
|
+
* restart mid-payment supplies a durable one.
|
|
398
|
+
*/
|
|
399
|
+
paymentLedger?: PaymentAttemptLedger;
|
|
400
|
+
/**
|
|
401
|
+
* The gateway's HMAC secret, when the caller legitimately holds one (an
|
|
402
|
+
* operator running its own gateway). An external agent leaves it unset and
|
|
403
|
+
* relies on its spend intent; see {@link SpendIntent}.
|
|
404
|
+
*/
|
|
405
|
+
offerSecret?: string;
|
|
406
|
+
/**
|
|
407
|
+
* The canonical `ExclusionDirectory` object id, so this payer can build a
|
|
408
|
+
* GUARDED delegate settle for a guarded offer out of the box.
|
|
409
|
+
*
|
|
410
|
+
* Optional: `createPayer` otherwise takes it from `GET /v1/delegate/deployment`,
|
|
411
|
+
* which discovery already calls, so an agent holding only a key and a gateway
|
|
412
|
+
* URL needs no out-of-band value to pay a guarded offer. Pass it to pin the
|
|
413
|
+
* behaviour against a gateway that does not publish one.
|
|
414
|
+
*
|
|
415
|
+
* When present from either source, `createPayer` constructs the
|
|
416
|
+
* `resolveExclusionRegistry` dependency bound to this payer's own gRPC reader
|
|
417
|
+
* (the same client that reads the grant and confirms settlement), which
|
|
418
|
+
* resolves the per-recipient `RecipientRegistry` from the directory's
|
|
419
|
+
* dynamic-field binding at settle-build time. When absent from BOTH, a guarded
|
|
420
|
+
* offer FAILS CLOSED (BUILD_FAILED) — the payer never builds an unguarded
|
|
421
|
+
* settle, which would reopen the cross-rail double-settle hole and be rejected
|
|
422
|
+
* by the gateway's self-settled verifier anyway. An unguarded offer never
|
|
423
|
+
* resolves a registry, so it is unaffected either way.
|
|
424
|
+
*/
|
|
425
|
+
exclusionDirectoryObjectId?: string;
|
|
426
|
+
/** Server-side log port for internal failure detail. Never reaches a result. */
|
|
427
|
+
log?: DelegatedPayerLog;
|
|
428
|
+
}
|
|
429
|
+
/** Result of `payer.pay`. Same shape as the internal settlement result. */
|
|
430
|
+
type PayResult = DelegatedPayResult;
|
|
431
|
+
|
|
432
|
+
/** One paid request. `intent` is what this agent says it is buying. */
|
|
433
|
+
interface PayInput {
|
|
434
|
+
method?: string;
|
|
435
|
+
body?: string | Uint8Array;
|
|
436
|
+
contentType?: string;
|
|
437
|
+
/**
|
|
438
|
+
* Required. Without it nothing bounds what the offer may charge, and the
|
|
439
|
+
* payer refuses — the whole point of the key-only path is that the ceiling
|
|
440
|
+
* comes from the agent, not from a secret it should not hold.
|
|
441
|
+
*/
|
|
442
|
+
intent: SpendIntent;
|
|
443
|
+
/**
|
|
444
|
+
* Terms the caller has ALREADY verified, which the offer this payer reads
|
|
445
|
+
* must price exactly or nothing is signed.
|
|
446
|
+
*
|
|
447
|
+
* Distinct from {@link SpendIntent}, and not a replacement for it: `intent`
|
|
448
|
+
* is a ceiling — it refuses an offer that charges MORE — while this is an
|
|
449
|
+
* equality. A caller that showed a person a specific price, or checked a
|
|
450
|
+
* server-prepared kind, is not agreeing to "that or less from anyone in the
|
|
451
|
+
* catalogue"; it is agreeing to one exact payment. The payer fetches its own
|
|
452
|
+
* offer immediately before signing, so without this that final offer is only
|
|
453
|
+
* ceiling-bounded and could legitimately differ from the one the caller
|
|
454
|
+
* verified.
|
|
455
|
+
*
|
|
456
|
+
* Optional, so every existing caller keeps ceiling semantics unchanged.
|
|
457
|
+
*/
|
|
458
|
+
preparedTerms?: PreparedSettlementTerms;
|
|
459
|
+
/**
|
|
460
|
+
* Which dialect to pay in. Default `'mpp'`, unchanged: the sponsor executes
|
|
461
|
+
* the pay and the payer presents a finalized proof. `'x402'` signs the pay
|
|
462
|
+
* with the offer's pinned sponsor gas and presents it as `PAYMENT-SIGNATURE`;
|
|
463
|
+
* the gateway co-signs and submits (ADR-0045). Before reserving anything,
|
|
464
|
+
* x402 refuses an open-recipient grant and an offer without the pins.
|
|
465
|
+
*/
|
|
466
|
+
dialect?: 'mpp' | 'x402';
|
|
467
|
+
/**
|
|
468
|
+
* x402 only: each step as it happens (built, signed, then presented and
|
|
469
|
+
* answered per presentation). Called synchronously; a throw is logged and
|
|
470
|
+
* never affects the payment.
|
|
471
|
+
*/
|
|
472
|
+
onX402Progress?: (event: X402PayProgress) => void;
|
|
473
|
+
}
|
|
474
|
+
interface Payer {
|
|
475
|
+
/** The delegate's own address, derived from the key it holds. */
|
|
476
|
+
address: string;
|
|
477
|
+
/** Deployment constants the gateway published and the bootstrap bound. */
|
|
478
|
+
deployment: GatewayDeployment;
|
|
479
|
+
/** The grant this agent will pay from, as resolved and then verified on chain. */
|
|
480
|
+
grant: AgentGrantBinding;
|
|
481
|
+
/** The assembled session; exposed so a caller can inspect what it will sign with. */
|
|
482
|
+
session: DelegatedPayerSession;
|
|
483
|
+
/** Pay for one resource and return its response. */
|
|
484
|
+
pay(url: string, input: PayInput): Promise<PayResult>;
|
|
485
|
+
/**
|
|
486
|
+
* Verify a 402 prepared settlement against the grant this agent resolved.
|
|
487
|
+
* Always pins {@link Payer.grant} — a settlement drawn on another
|
|
488
|
+
* buyer's grant is refused. Callers with no resolved grant still use
|
|
489
|
+
* {@link verifyPreparedPayment} unpinned.
|
|
490
|
+
*/
|
|
491
|
+
verifySettlement(prepared: unknown, offer: Offer): Promise<PreparedSettlement>;
|
|
492
|
+
}
|
|
279
493
|
/**
|
|
280
|
-
*
|
|
494
|
+
* Create a payer from its key and a gateway URL.
|
|
281
495
|
*
|
|
282
|
-
*
|
|
283
|
-
* `
|
|
284
|
-
*
|
|
496
|
+
* The call sequence, all of it derivable from those two inputs:
|
|
497
|
+
* 1. `GET /v1/delegate/deployment` — network, package id, and the exclusion
|
|
498
|
+
* directory id when guarded minting is on. All in the clear.
|
|
499
|
+
* 2. `GET /v1/delegate/grants`, signed by the key — the grants naming it.
|
|
500
|
+
* 3. an unsigned `getObject` on the resolved grant, against a public
|
|
501
|
+
* fullnode — the agent's own check that the grant is its own.
|
|
285
502
|
*
|
|
286
|
-
*
|
|
287
|
-
*
|
|
288
|
-
* signs from that. So the verified terms are carried down as `preparedTerms` and
|
|
289
|
-
* re-asserted against the payer's offer before it signs. Without that carry, the
|
|
290
|
-
* two hops can price different recipients and nothing would notice.
|
|
503
|
+
* Typed failure, never a throw: an agent that cannot verify its own authority
|
|
504
|
+
* must refuse to sign, not proceed on the gateway's word.
|
|
291
505
|
*/
|
|
506
|
+
declare function createPayer(options: PayerOptions): Promise<ManifestResult<Payer>>;
|
|
292
507
|
|
|
293
|
-
|
|
294
|
-
|
|
508
|
+
interface StdioLoginOptions {
|
|
509
|
+
consoleUrl: string;
|
|
510
|
+
gatewayUrl: string;
|
|
511
|
+
identity: {
|
|
512
|
+
delegateAddress: string;
|
|
513
|
+
publicKeyHex: string;
|
|
514
|
+
};
|
|
515
|
+
label?: string;
|
|
516
|
+
timeoutMs?: number;
|
|
517
|
+
openBrowser?: boolean;
|
|
518
|
+
onUrl?: (connectUrl: string) => void;
|
|
519
|
+
}
|
|
520
|
+
interface StartedStdioLogin {
|
|
521
|
+
connectUrl: string;
|
|
522
|
+
port: number;
|
|
523
|
+
connectState: string;
|
|
524
|
+
done: Promise<void>;
|
|
525
|
+
close: () => void;
|
|
526
|
+
}
|
|
527
|
+
/**
|
|
528
|
+
* Bind the loopback listener and return the console URL immediately.
|
|
529
|
+
* The wallet callback is awaited on `done`, not on the MCP tool call.
|
|
530
|
+
*/
|
|
531
|
+
declare function startStdioLogin(opts: StdioLoginOptions): Promise<StartedStdioLogin>;
|
|
532
|
+
|
|
533
|
+
type ToolResult = CallToolResult;
|
|
534
|
+
|
|
535
|
+
interface ToolDeps {
|
|
536
|
+
fetchImpl?: typeof fetch;
|
|
537
|
+
profileLoader?: () => PaymentProfile | null;
|
|
538
|
+
/** Remote HTTP auth (grant + scope + optional shared-pool pay session). */
|
|
539
|
+
auth?: McpAuthContext;
|
|
540
|
+
/** Wire transport. Hosted HTTP never reads a buyer path. */
|
|
541
|
+
transport?: 'stdio' | 'http';
|
|
295
542
|
/**
|
|
296
|
-
*
|
|
297
|
-
*
|
|
298
|
-
* against fields copied out of the same prepare response proves only that the
|
|
299
|
-
* response agrees with itself.
|
|
543
|
+
* Proxy-resolved live trace for this MCP request. Observation only: it never
|
|
544
|
+
* changes which rail runs, what is paid, or whether a payment is allowed.
|
|
300
545
|
*/
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
546
|
+
trace?: McpTraceHooks;
|
|
547
|
+
/**
|
|
548
|
+
* Agent-held delegate key. Present only in the process that stores the
|
|
549
|
+
* key — never on the gateway. When set after a server-side prepare, `pay`
|
|
550
|
+
* verifies the prepared kind and settles through `payDelegatedSpendAccount`.
|
|
551
|
+
*/
|
|
552
|
+
agentHeld?: {
|
|
553
|
+
signer: DelegatedKeySigner;
|
|
554
|
+
getObjectJson: GetObjectJson;
|
|
555
|
+
payerDeps: DelegatedPayerDeps;
|
|
305
556
|
};
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
deps: DelegatedPayerDeps;
|
|
557
|
+
/** Override the prepare-time kind builder (tests / offline). */
|
|
558
|
+
buildPayKind?: SpendAccountPayDeps['buildPayKind'];
|
|
559
|
+
/** Override the exclusion-registry resolver (tests / offline). */
|
|
560
|
+
resolveExclusionRegistry?: SpendAccountPayDeps['resolveExclusionRegistry'];
|
|
311
561
|
/**
|
|
312
|
-
*
|
|
313
|
-
*
|
|
562
|
+
* Stdio login surfaces the console URL (MCP progress notification). The URL
|
|
563
|
+
* carries the delegate address only — never the seed.
|
|
314
564
|
*/
|
|
315
|
-
|
|
316
|
-
|
|
565
|
+
onUrl?: (url: string) => void;
|
|
566
|
+
createPayer?: typeof createPayer;
|
|
567
|
+
createReader?: typeof createReader;
|
|
568
|
+
startStdioLogin?: typeof startStdioLogin;
|
|
569
|
+
}
|
|
570
|
+
declare function pay(args: {
|
|
571
|
+
url: string;
|
|
572
|
+
method?: unknown;
|
|
573
|
+
json?: unknown;
|
|
574
|
+
file?: unknown;
|
|
575
|
+
fileField?: unknown;
|
|
576
|
+
recipient?: unknown;
|
|
577
|
+
maxAmount?: unknown;
|
|
578
|
+
}, cfg: McpConfig, deps?: ToolDeps): Promise<ToolResult>;
|
|
579
|
+
declare function receipts(args: {
|
|
580
|
+
challengeId?: string;
|
|
581
|
+
}, cfg: McpConfig, deps?: ToolDeps): Promise<ToolResult>;
|
|
317
582
|
|
|
318
583
|
declare function loadBootProfile(load?: typeof loadPaymentProfile): {
|
|
319
584
|
profile: ReturnType<typeof loadPaymentProfile>;
|
|
@@ -321,4 +586,4 @@ declare function loadBootProfile(load?: typeof loadPaymentProfile): {
|
|
|
321
586
|
};
|
|
322
587
|
declare function main(): Promise<void>;
|
|
323
588
|
|
|
324
|
-
export {
|
|
589
|
+
export { McpAuthContext, McpConfig, McpTraceHooks, SpendAccountSigningRequest, ensureLocalSigningKey, loadBootProfile, loadLocalSigner, main, pay, receipts, settlePreparedMcpPayment };
|