@oobe-protocol-labs/sap-mcp-server 0.9.13 → 0.9.16
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/CHANGELOG.md +62 -0
- package/README.md +2 -2
- package/USER_DOCS/00_START_HERE.md +24 -2
- package/USER_DOCS/03_PAYMENTS_X402_PAYSH.md +6 -1
- package/USER_DOCS/04_CLIENT_CONFIGS.md +4 -3
- package/USER_DOCS/05_SKILLS_AND_TOOLS.md +9 -4
- package/USER_DOCS/06_DESKTOP_GUI_WIZARD.md +6 -6
- package/dist/config/mcp-client-injection.d.ts.map +1 -1
- package/dist/config/mcp-client-injection.js +4 -3
- package/dist/config/mcp-client-injection.js.map +1 -1
- package/dist/core/constants.d.ts +9 -1
- package/dist/core/constants.d.ts.map +1 -1
- package/dist/core/constants.js +13 -3
- package/dist/core/constants.js.map +1 -1
- package/dist/payments/hosted-tool-eligibility.d.ts.map +1 -1
- package/dist/payments/hosted-tool-eligibility.js +3 -0
- package/dist/payments/hosted-tool-eligibility.js.map +1 -1
- package/dist/payments/index.d.ts +2 -2
- package/dist/payments/index.d.ts.map +1 -1
- package/dist/payments/index.js +1 -1
- package/dist/payments/index.js.map +1 -1
- package/dist/payments/pricing.d.ts +30 -0
- package/dist/payments/pricing.d.ts.map +1 -1
- package/dist/payments/pricing.js +142 -1
- package/dist/payments/pricing.js.map +1 -1
- package/dist/prompts/context/sap-agent-context.prompt.js +2 -0
- package/dist/prompts/context/sap-agent-context.prompt.js.map +1 -1
- package/dist/prompts/context/sap-agent-start.prompt.js +2 -0
- package/dist/prompts/context/sap-agent-start.prompt.js.map +1 -1
- package/dist/prompts/registry/register-sap-agent.prompt.js +18 -18
- package/dist/prompts/registry/register-sap-agent.prompt.js.map +1 -1
- package/dist/remote/public-home/sections.d.ts.map +1 -1
- package/dist/remote/public-home/sections.js +4 -0
- package/dist/remote/public-home/sections.js.map +1 -1
- package/dist/remote/public-home/types.d.ts +1 -0
- package/dist/remote/public-home/types.d.ts.map +1 -1
- package/dist/remote/server.d.ts +1 -0
- package/dist/remote/server.d.ts.map +1 -1
- package/dist/remote/server.js +10 -2
- package/dist/remote/server.js.map +1 -1
- package/dist/security/tool-permissions.d.ts.map +1 -1
- package/dist/security/tool-permissions.js +9 -0
- package/dist/security/tool-permissions.js.map +1 -1
- package/dist/tools/agent-start-tool.d.ts.map +1 -1
- package/dist/tools/agent-start-tool.js +411 -1
- package/dist/tools/agent-start-tool.js.map +1 -1
- package/dist/tools/client-sdk-tools.js +3 -3
- package/dist/tools/client-sdk-tools.js.map +1 -1
- package/dist/tools/magicblock-tools.d.ts.map +1 -1
- package/dist/tools/magicblock-tools.js +43 -26
- package/dist/tools/magicblock-tools.js.map +1 -1
- package/dist/tools/sap-sdk-tools.d.ts +412 -1
- package/dist/tools/sap-sdk-tools.d.ts.map +1 -1
- package/dist/tools/sap-sdk-tools.js +1222 -40
- package/dist/tools/sap-sdk-tools.js.map +1 -1
- package/dist/tools/sap-sns-tools.d.ts.map +1 -1
- package/dist/tools/sap-sns-tools.js +6 -2
- package/dist/tools/sap-sns-tools.js.map +1 -1
- package/dist/tools/skills-tools.d.ts.map +1 -1
- package/dist/tools/skills-tools.js +2 -0
- package/dist/tools/skills-tools.js.map +1 -1
- package/dist/tools/tool-aliases.d.ts.map +1 -1
- package/dist/tools/tool-aliases.js +7 -0
- package/dist/tools/tool-aliases.js.map +1 -1
- package/dist/tools/x402-paid-call-tool.d.ts.map +1 -1
- package/dist/tools/x402-paid-call-tool.js +315 -15
- package/dist/tools/x402-paid-call-tool.js.map +1 -1
- package/docs/03_CONFIGURATION_AND_WIZARD.md +1 -0
- package/docs/06_PAYMENTS_X402_AND_PAYSH.md +56 -3
- package/docs/07_ENDPOINTS_AND_CLIENTS.md +2 -1
- package/docs/09_TOOLS_SKILLS_AND_AGENT_GUIDE.md +16 -5
- package/docs/14_DESKTOP_WIZARD_RELEASE.md +5 -4
- package/docs/16_SAP_AGENT_IDENTITY_PIPELINE.md +298 -0
- package/docs/README.md +2 -0
- package/docs/magicblock-tools.md +26 -0
- package/package.json +4 -1
- package/server.json +3 -3
- package/skills/README.md +10 -2
- package/skills/sap-agent-registry/SKILL.md +81 -8
- package/skills/sap-discovery-indexing/SKILL.md +11 -0
- package/skills/sap-escrow-settlement/SKILL.md +22 -4
- package/skills/sap-mcp/SKILL.md +84 -23
- package/skills/sap-mcp/TOOL_REFERENCE.md +38 -7
- package/skills/sap-nft-metaplex/SKILL.md +22 -0
- package/skills/sap-operations/SKILL.md +28 -0
- package/skills/sap-payments-x402/SKILL.md +38 -3
- package/skills/sap-sns/SKILL.md +17 -0
|
@@ -5,18 +5,232 @@
|
|
|
5
5
|
* `@oobe-protocol-labs/synapse-sap-sdk@1.0.x`. It does not create local
|
|
6
6
|
* facades for missing SDK namespaces and it does not fabricate network data.
|
|
7
7
|
*/
|
|
8
|
-
import { PublicKey } from '@solana/web3.js';
|
|
8
|
+
import { PublicKey, SystemProgram, Transaction } from '@solana/web3.js';
|
|
9
9
|
import BN from 'bn.js';
|
|
10
|
-
import { TOOL_CATEGORY_VALUES } from '@oobe-protocol-labs/synapse-sap-sdk';
|
|
10
|
+
import { Pda, TOOL_CATEGORY_VALUES, USDC_MINT_DEVNET, USDC_MINT_MAINNET } from '@oobe-protocol-labs/synapse-sap-sdk';
|
|
11
11
|
import { SettlementMode, TokenType, } from '@oobe-protocol-labs/synapse-sap-sdk/types';
|
|
12
12
|
import { createTextResponse } from '../adapters/mcp/tool-response.js';
|
|
13
13
|
import { registerTool } from '../adapters/mcp/sdk-compat.js';
|
|
14
14
|
import { getSapClient, isSapClientInitialized } from '../sap/sap-client-manager.js';
|
|
15
15
|
import { logger } from '../core/logger.js';
|
|
16
|
+
import { DEFAULT_SAP_PROGRAM_ID, SAP_PROTOCOL_TREASURY, SAP_REGISTRATION_FEE_LAMPORTS, } from '../core/constants.js';
|
|
16
17
|
const ESCROW_AMOUNT_DESCRIPTION = 'Amount in the escrow token smallest unit: lamports for SOL, micro-USDC for USDC, or base units for the configured SPL token.';
|
|
17
18
|
const ESCROW_V2_COSIGNED_MODE = 1;
|
|
18
19
|
const ESCROW_V2_DISPUTE_WINDOW_MODE = 2;
|
|
19
20
|
const DEFAULT_ESCROW_V2_DISPUTE_WINDOW_SLOTS = new BN(2160);
|
|
21
|
+
const TOKEN_PROGRAM_ID = new PublicKey('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA');
|
|
22
|
+
const ASSOCIATED_TOKEN_PROGRAM_ID = new PublicKey('ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL');
|
|
23
|
+
export const SAP_AGENT_CAPABILITY_INPUT_SCHEMA = {
|
|
24
|
+
oneOf: [
|
|
25
|
+
{
|
|
26
|
+
type: 'string',
|
|
27
|
+
description: 'Capability id shorthand, for example jupiter:swap, pyth:price, metaplex:identity, sns:identity, or risk:management.',
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
type: 'object',
|
|
31
|
+
properties: {
|
|
32
|
+
id: {
|
|
33
|
+
type: 'string',
|
|
34
|
+
description: 'Required stable capability id. Use protocol:action naming, for example jupiter:swap, pyth:price, metaplex:identity, sns:identity, x402:payments, or risk:management.',
|
|
35
|
+
},
|
|
36
|
+
description: {
|
|
37
|
+
type: 'string',
|
|
38
|
+
description: 'Optional human-readable capability description for agents and explorers.',
|
|
39
|
+
},
|
|
40
|
+
protocolId: {
|
|
41
|
+
type: 'string',
|
|
42
|
+
description: 'Optional protocol namespace backing this capability, for example sap, mcp, jupiter, pyth, metaplex, sns, x402, or custom.',
|
|
43
|
+
},
|
|
44
|
+
version: {
|
|
45
|
+
type: 'string',
|
|
46
|
+
description: 'Optional capability version string. Use semver when the capability maps to an API contract.',
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
required: ['id'],
|
|
50
|
+
additionalProperties: false,
|
|
51
|
+
},
|
|
52
|
+
],
|
|
53
|
+
};
|
|
54
|
+
export const SAP_AGENT_PRICING_TIER_INPUT_SCHEMA = {
|
|
55
|
+
type: 'object',
|
|
56
|
+
properties: {
|
|
57
|
+
tierId: {
|
|
58
|
+
type: 'string',
|
|
59
|
+
description: 'Stable tier id, for example default, read, premium, value-action, or enterprise.',
|
|
60
|
+
},
|
|
61
|
+
pricePerCall: {
|
|
62
|
+
type: 'string',
|
|
63
|
+
description: 'Price per call in the smallest unit of tokenType: lamports for sol, micro-USDC for usdc, or base units for spl.',
|
|
64
|
+
},
|
|
65
|
+
minPricePerCall: {
|
|
66
|
+
type: 'string',
|
|
67
|
+
description: 'Optional floor price per call in the smallest unit of tokenType.',
|
|
68
|
+
},
|
|
69
|
+
maxPricePerCall: {
|
|
70
|
+
type: 'string',
|
|
71
|
+
description: 'Optional ceiling price per call in the smallest unit of tokenType.',
|
|
72
|
+
},
|
|
73
|
+
rateLimit: {
|
|
74
|
+
type: 'number',
|
|
75
|
+
description: 'Maximum calls per second allowed by this tier. Defaults to 60.',
|
|
76
|
+
},
|
|
77
|
+
maxCallsPerSession: {
|
|
78
|
+
type: 'number',
|
|
79
|
+
description: 'Maximum calls allowed per session for this tier. Defaults to 1000.',
|
|
80
|
+
},
|
|
81
|
+
burstLimit: {
|
|
82
|
+
type: 'number',
|
|
83
|
+
description: 'Optional short-window burst allowance for this tier.',
|
|
84
|
+
},
|
|
85
|
+
tokenType: {
|
|
86
|
+
type: 'string',
|
|
87
|
+
enum: ['sol', 'usdc', 'spl'],
|
|
88
|
+
description: 'Payment token type. Use usdc for x402/pay.sh agent commerce. Use spl only with tokenMint.',
|
|
89
|
+
},
|
|
90
|
+
tokenMint: {
|
|
91
|
+
type: 'string',
|
|
92
|
+
description: 'Optional SPL token mint public key. Required when tokenType is spl. For mainnet USDC use EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v.',
|
|
93
|
+
},
|
|
94
|
+
tokenDecimals: {
|
|
95
|
+
type: 'number',
|
|
96
|
+
description: 'Decimals for the payment token. Defaults to 6 for usdc and 9 for sol when omitted.',
|
|
97
|
+
},
|
|
98
|
+
settlementMode: {
|
|
99
|
+
type: 'string',
|
|
100
|
+
enum: ['instant', 'escrow', 'batched', 'x402'],
|
|
101
|
+
description: 'Settlement strategy. Use x402 for HTTP 402/pay.sh flows, escrow for prepaid SAP usage ledgers, instant for direct pay-per-call, and batched for periodic settlement.',
|
|
102
|
+
},
|
|
103
|
+
minEscrowDeposit: {
|
|
104
|
+
type: 'string',
|
|
105
|
+
description: 'Optional minimum escrow deposit in the smallest unit of tokenType.',
|
|
106
|
+
},
|
|
107
|
+
batchIntervalSec: {
|
|
108
|
+
type: 'number',
|
|
109
|
+
description: 'Optional settlement batch interval in seconds when settlementMode is batched.',
|
|
110
|
+
},
|
|
111
|
+
volumeCurve: {
|
|
112
|
+
type: 'array',
|
|
113
|
+
description: 'Optional volume discounts. Each item lowers pricePerCall after a call threshold.',
|
|
114
|
+
items: {
|
|
115
|
+
type: 'object',
|
|
116
|
+
properties: {
|
|
117
|
+
afterCalls: { type: 'number', description: 'Call count threshold where this price starts.' },
|
|
118
|
+
pricePerCall: { type: 'string', description: 'Discounted price per call in the smallest unit of tokenType.' },
|
|
119
|
+
},
|
|
120
|
+
required: ['afterCalls', 'pricePerCall'],
|
|
121
|
+
additionalProperties: false,
|
|
122
|
+
},
|
|
123
|
+
},
|
|
124
|
+
},
|
|
125
|
+
required: ['pricePerCall'],
|
|
126
|
+
additionalProperties: false,
|
|
127
|
+
};
|
|
128
|
+
export const SAP_AGENT_REGISTER_INPUT_SCHEMA = {
|
|
129
|
+
name: {
|
|
130
|
+
type: 'string',
|
|
131
|
+
description: 'Required public display name for the SAP agent. Keep it stable enough for explorers and humans.',
|
|
132
|
+
},
|
|
133
|
+
description: {
|
|
134
|
+
type: 'string',
|
|
135
|
+
description: 'Required public description of what the agent does, which protocols it can use, and its safety/trust boundaries.',
|
|
136
|
+
},
|
|
137
|
+
capabilities: {
|
|
138
|
+
type: 'array',
|
|
139
|
+
description: 'Required capability list. Prefer object form for production; strings are accepted as shorthand.',
|
|
140
|
+
items: SAP_AGENT_CAPABILITY_INPUT_SCHEMA,
|
|
141
|
+
},
|
|
142
|
+
pricing: {
|
|
143
|
+
type: 'array',
|
|
144
|
+
description: 'Optional pricing tiers advertised by the agent. Use tokenType usdc + settlementMode x402 for pay.sh/x402 agent commerce.',
|
|
145
|
+
items: SAP_AGENT_PRICING_TIER_INPUT_SCHEMA,
|
|
146
|
+
},
|
|
147
|
+
protocols: {
|
|
148
|
+
type: 'array',
|
|
149
|
+
items: { type: 'string' },
|
|
150
|
+
description: 'Required protocol tags the agent supports, for example sap, mcp, jupiter, pyth, metaplex, sns, x402, payments.',
|
|
151
|
+
},
|
|
152
|
+
agentId: {
|
|
153
|
+
type: 'string',
|
|
154
|
+
description: 'Optional stable lowercase agent id, for example solking. This is separate from the on-chain agent PDA.',
|
|
155
|
+
},
|
|
156
|
+
agentUri: {
|
|
157
|
+
type: 'string',
|
|
158
|
+
description: 'Optional public HTTPS/IPFS/Arweave/Kommodo URI for the agent profile metadata or profile page. Never use local desktop file paths.',
|
|
159
|
+
},
|
|
160
|
+
metadataUri: {
|
|
161
|
+
type: 'string',
|
|
162
|
+
description: 'Alias for agentUri. Prefer a public JSON metadata document containing name, description, image, external_url, attributes, sap, metaplex, sns, and x402 fields.',
|
|
163
|
+
},
|
|
164
|
+
x402Endpoint: {
|
|
165
|
+
type: 'string',
|
|
166
|
+
description: 'Optional public x402 discovery/payment endpoint, usually https://host/.well-known/x402 for external agent services.',
|
|
167
|
+
},
|
|
168
|
+
};
|
|
169
|
+
export const SAP_AGENT_UPDATE_INPUT_SCHEMA = {
|
|
170
|
+
name: {
|
|
171
|
+
type: 'string',
|
|
172
|
+
description: 'Optional replacement display name. Omit to keep the current name.',
|
|
173
|
+
},
|
|
174
|
+
description: {
|
|
175
|
+
type: 'string',
|
|
176
|
+
description: 'Optional replacement description. Omit to keep the current description.',
|
|
177
|
+
},
|
|
178
|
+
capabilities: {
|
|
179
|
+
type: 'array',
|
|
180
|
+
description: 'Optional full replacement capability list. Omit to keep current capabilities; do not send only the new item unless replacing the whole list is intended.',
|
|
181
|
+
items: SAP_AGENT_CAPABILITY_INPUT_SCHEMA,
|
|
182
|
+
},
|
|
183
|
+
pricing: {
|
|
184
|
+
type: 'array',
|
|
185
|
+
description: 'Optional full replacement pricing tier list. Omit to keep current pricing.',
|
|
186
|
+
items: SAP_AGENT_PRICING_TIER_INPUT_SCHEMA,
|
|
187
|
+
},
|
|
188
|
+
protocols: {
|
|
189
|
+
type: 'array',
|
|
190
|
+
items: { type: 'string' },
|
|
191
|
+
description: 'Optional full replacement protocol list. Omit to keep current protocols.',
|
|
192
|
+
},
|
|
193
|
+
agentId: {
|
|
194
|
+
type: 'string',
|
|
195
|
+
description: 'Optional replacement stable agent id. Omit to keep current agentId.',
|
|
196
|
+
},
|
|
197
|
+
agentUri: {
|
|
198
|
+
type: 'string',
|
|
199
|
+
description: 'Optional replacement public URI for agent metadata/profile. Use this to update pictures after uploading metadata to IPFS, Arweave, Kommodo, or HTTPS.',
|
|
200
|
+
},
|
|
201
|
+
metadataUri: {
|
|
202
|
+
type: 'string',
|
|
203
|
+
description: 'Alias for agentUri. Use a public metadata JSON URI; never use a desktop file path.',
|
|
204
|
+
},
|
|
205
|
+
x402Endpoint: {
|
|
206
|
+
type: 'string',
|
|
207
|
+
description: 'Optional replacement x402 discovery/payment endpoint. Omit to keep current endpoint.',
|
|
208
|
+
},
|
|
209
|
+
};
|
|
210
|
+
const SAP_AGENT_IDENTITY_PLAN_INPUT_SCHEMA = {
|
|
211
|
+
intendedAction: {
|
|
212
|
+
type: 'string',
|
|
213
|
+
enum: ['register', 'update', 'link-metaplex', 'link-sns', 'full-identity'],
|
|
214
|
+
description: 'Agent identity lifecycle step to plan. Use register for a new SAP agent, update for profile/image/capability changes, link-metaplex for NFT/MPL Core identity, link-sns for .sol identity, or full-identity for the recommended end-to-end flow.',
|
|
215
|
+
},
|
|
216
|
+
...SAP_AGENT_REGISTER_INPUT_SCHEMA,
|
|
217
|
+
snsDomain: {
|
|
218
|
+
type: 'string',
|
|
219
|
+
description: 'Optional .sol domain to link after ownership or registration is verified.',
|
|
220
|
+
},
|
|
221
|
+
imageUrl: {
|
|
222
|
+
type: 'string',
|
|
223
|
+
description: 'Optional public image URL. This must be HTTPS, IPFS, Arweave, or another public URI; desktop file paths are not valid.',
|
|
224
|
+
},
|
|
225
|
+
metaplexAsset: {
|
|
226
|
+
type: 'string',
|
|
227
|
+
description: 'Optional Metaplex NFT or MPL Core asset/collection address to reference from public agent metadata.',
|
|
228
|
+
},
|
|
229
|
+
ownerWallet: {
|
|
230
|
+
type: 'string',
|
|
231
|
+
description: 'Optional expected SAP owner wallet. Use this for verification planning only; signing still comes from the active local SAP profile.',
|
|
232
|
+
},
|
|
233
|
+
};
|
|
20
234
|
const escrowV2CreateInputSchema = {
|
|
21
235
|
agentWallet: {
|
|
22
236
|
type: 'string',
|
|
@@ -68,6 +282,124 @@ const escrowV2CreateInputSchema = {
|
|
|
68
282
|
description: 'Optional arbiter public key kept for IDL compatibility and dispute workflows.',
|
|
69
283
|
},
|
|
70
284
|
};
|
|
285
|
+
const escrowV2CreateBuilderInputSchema = {
|
|
286
|
+
depositorWallet: {
|
|
287
|
+
type: 'string',
|
|
288
|
+
description: 'Depositor wallet public key (base58). This wallet signs locally and funds the escrow; hosted SAP MCP never receives its private key.',
|
|
289
|
+
},
|
|
290
|
+
...escrowV2CreateInputSchema,
|
|
291
|
+
};
|
|
292
|
+
const escrowV2DepositBuilderInputSchema = {
|
|
293
|
+
depositorWallet: {
|
|
294
|
+
type: 'string',
|
|
295
|
+
description: 'Depositor wallet public key (base58). This wallet signs locally and provides the additional escrow funds.',
|
|
296
|
+
},
|
|
297
|
+
agentWallet: {
|
|
298
|
+
type: 'string',
|
|
299
|
+
description: 'Agent owner wallet public key (base58). The escrow PDA is derived from agentWallet, depositorWallet, and nonce.',
|
|
300
|
+
},
|
|
301
|
+
nonce: {
|
|
302
|
+
type: 'string',
|
|
303
|
+
description: 'Optional escrow nonce as a decimal string. Defaults to 0.',
|
|
304
|
+
},
|
|
305
|
+
amount: {
|
|
306
|
+
type: 'string',
|
|
307
|
+
description: `Deposit amount as a decimal string. ${ESCROW_AMOUNT_DESCRIPTION}`,
|
|
308
|
+
},
|
|
309
|
+
};
|
|
310
|
+
const escrowV2WithdrawBuilderInputSchema = {
|
|
311
|
+
depositorWallet: {
|
|
312
|
+
type: 'string',
|
|
313
|
+
description: 'Depositor wallet public key (base58). Only the depositor can locally sign a withdrawal from this escrow.',
|
|
314
|
+
},
|
|
315
|
+
agentWallet: {
|
|
316
|
+
type: 'string',
|
|
317
|
+
description: 'Agent owner wallet public key (base58). The escrow PDA is derived from agentWallet, depositorWallet, and nonce.',
|
|
318
|
+
},
|
|
319
|
+
nonce: {
|
|
320
|
+
type: 'string',
|
|
321
|
+
description: 'Optional escrow nonce as a decimal string. Defaults to 0.',
|
|
322
|
+
},
|
|
323
|
+
amount: {
|
|
324
|
+
type: 'string',
|
|
325
|
+
description: `Withdrawal amount as a decimal string. ${ESCROW_AMOUNT_DESCRIPTION}`,
|
|
326
|
+
},
|
|
327
|
+
};
|
|
328
|
+
const escrowV2SettleBuilderInputSchema = {
|
|
329
|
+
agentWallet: {
|
|
330
|
+
type: 'string',
|
|
331
|
+
description: 'Agent owner wallet public key (base58). This wallet signs locally because settlement releases funds to the serving agent.',
|
|
332
|
+
},
|
|
333
|
+
depositorWallet: {
|
|
334
|
+
type: 'string',
|
|
335
|
+
description: 'Depositor wallet public key (base58) for the escrow being settled.',
|
|
336
|
+
},
|
|
337
|
+
nonce: {
|
|
338
|
+
type: 'string',
|
|
339
|
+
description: 'Optional escrow nonce as a decimal string. Defaults to 0.',
|
|
340
|
+
},
|
|
341
|
+
callsToSettle: {
|
|
342
|
+
type: 'string',
|
|
343
|
+
description: 'Number of served calls to settle as a decimal string.',
|
|
344
|
+
},
|
|
345
|
+
serviceHash: {
|
|
346
|
+
oneOf: [
|
|
347
|
+
{
|
|
348
|
+
type: 'array',
|
|
349
|
+
items: { type: 'number' },
|
|
350
|
+
description: '32-byte service/audit hash as byte array.',
|
|
351
|
+
},
|
|
352
|
+
{
|
|
353
|
+
type: 'string',
|
|
354
|
+
description: '32-byte service/audit hash encoded as 64-char hex or base64 string.',
|
|
355
|
+
},
|
|
356
|
+
],
|
|
357
|
+
description: '32-byte service/audit hash. Use a stable hash of the fulfilled paid work.',
|
|
358
|
+
},
|
|
359
|
+
coSigner: {
|
|
360
|
+
type: 'string',
|
|
361
|
+
description: 'Optional co-signer public key for CoSigned escrows when the escrow account requires it.',
|
|
362
|
+
},
|
|
363
|
+
};
|
|
364
|
+
const escrowV2FinalizeBuilderInputSchema = {
|
|
365
|
+
payerWallet: {
|
|
366
|
+
type: 'string',
|
|
367
|
+
description: 'Wallet public key (base58) that signs and pays transaction fees to finalize the pending settlement.',
|
|
368
|
+
},
|
|
369
|
+
agentWallet: {
|
|
370
|
+
type: 'string',
|
|
371
|
+
description: 'Agent owner wallet public key (base58).',
|
|
372
|
+
},
|
|
373
|
+
depositorWallet: {
|
|
374
|
+
type: 'string',
|
|
375
|
+
description: 'Depositor wallet public key (base58).',
|
|
376
|
+
},
|
|
377
|
+
nonce: {
|
|
378
|
+
type: 'string',
|
|
379
|
+
description: 'Optional escrow nonce as a decimal string. Defaults to 0.',
|
|
380
|
+
},
|
|
381
|
+
settlementIndex: {
|
|
382
|
+
type: 'string',
|
|
383
|
+
description: 'Pending settlement index as a decimal string.',
|
|
384
|
+
},
|
|
385
|
+
};
|
|
386
|
+
const escrowV2CloseBuilderInputSchema = {
|
|
387
|
+
depositorWallet: {
|
|
388
|
+
type: 'string',
|
|
389
|
+
description: 'Depositor wallet public key (base58). The depositor signs locally to close the empty escrow.',
|
|
390
|
+
},
|
|
391
|
+
agentWallet: {
|
|
392
|
+
type: 'string',
|
|
393
|
+
description: 'Agent owner wallet public key (base58).',
|
|
394
|
+
},
|
|
395
|
+
nonce: {
|
|
396
|
+
type: 'string',
|
|
397
|
+
description: 'Optional escrow nonce as a decimal string. Defaults to 0.',
|
|
398
|
+
},
|
|
399
|
+
};
|
|
400
|
+
const DIRECTORY_CACHE_TTL_MS = 10_000;
|
|
401
|
+
const directoryPageCache = new Map();
|
|
402
|
+
const directoryPageInflight = new Map();
|
|
71
403
|
/**
|
|
72
404
|
* @name jsonReplacer
|
|
73
405
|
* @description Serializes SDK values such as PublicKey, BN, bigint, Buffer, and Uint8Array into JSON-safe output.
|
|
@@ -110,6 +442,22 @@ function asObjectPayload(payload) {
|
|
|
110
442
|
function getSapAnchorAccounts(client) {
|
|
111
443
|
return client.program.account;
|
|
112
444
|
}
|
|
445
|
+
function stableJson(value) {
|
|
446
|
+
if (value === undefined) {
|
|
447
|
+
return 'undefined';
|
|
448
|
+
}
|
|
449
|
+
if (value === null) {
|
|
450
|
+
return 'null';
|
|
451
|
+
}
|
|
452
|
+
if (typeof value !== 'object') {
|
|
453
|
+
return JSON.stringify(value) ?? String(value);
|
|
454
|
+
}
|
|
455
|
+
if (Array.isArray(value)) {
|
|
456
|
+
return `[${value.map(stableJson).join(',')}]`;
|
|
457
|
+
}
|
|
458
|
+
const record = value;
|
|
459
|
+
return `{${Object.keys(record).sort().map((key) => `${JSON.stringify(key)}:${stableJson(record[key])}`).join(',')}}`;
|
|
460
|
+
}
|
|
113
461
|
/**
|
|
114
462
|
* @name bnString
|
|
115
463
|
* @description Converts BN-like SDK counters into stable decimal strings.
|
|
@@ -362,6 +710,113 @@ async function buildAgentDirectoryPage(client, options) {
|
|
|
362
710
|
agentGuidance: directoryAgentGuidance({ hasMore, nextCursor, filters: options }),
|
|
363
711
|
};
|
|
364
712
|
}
|
|
713
|
+
async function getCachedAgentDirectoryPage(client, options) {
|
|
714
|
+
const cacheKey = stableJson(options);
|
|
715
|
+
const now = Date.now();
|
|
716
|
+
const cached = directoryPageCache.get(cacheKey);
|
|
717
|
+
if (cached && cached.expiresAt > now) {
|
|
718
|
+
return {
|
|
719
|
+
...cached.page,
|
|
720
|
+
cache: {
|
|
721
|
+
status: 'hit',
|
|
722
|
+
ttlMs: Math.max(0, cached.expiresAt - now),
|
|
723
|
+
key: cacheKey.slice(0, 24),
|
|
724
|
+
},
|
|
725
|
+
};
|
|
726
|
+
}
|
|
727
|
+
const inflight = directoryPageInflight.get(cacheKey);
|
|
728
|
+
if (inflight) {
|
|
729
|
+
const page = await inflight;
|
|
730
|
+
return {
|
|
731
|
+
...page,
|
|
732
|
+
cache: {
|
|
733
|
+
status: 'joined_inflight',
|
|
734
|
+
ttlMs: DIRECTORY_CACHE_TTL_MS,
|
|
735
|
+
key: cacheKey.slice(0, 24),
|
|
736
|
+
},
|
|
737
|
+
};
|
|
738
|
+
}
|
|
739
|
+
const load = buildAgentDirectoryPage(client, options)
|
|
740
|
+
.then((page) => {
|
|
741
|
+
directoryPageCache.set(cacheKey, { expiresAt: Date.now() + DIRECTORY_CACHE_TTL_MS, page });
|
|
742
|
+
return page;
|
|
743
|
+
})
|
|
744
|
+
.finally(() => {
|
|
745
|
+
directoryPageInflight.delete(cacheKey);
|
|
746
|
+
});
|
|
747
|
+
directoryPageInflight.set(cacheKey, load);
|
|
748
|
+
const page = await load;
|
|
749
|
+
return {
|
|
750
|
+
...page,
|
|
751
|
+
cache: {
|
|
752
|
+
status: 'miss',
|
|
753
|
+
ttlMs: DIRECTORY_CACHE_TTL_MS,
|
|
754
|
+
key: cacheKey.slice(0, 24),
|
|
755
|
+
},
|
|
756
|
+
};
|
|
757
|
+
}
|
|
758
|
+
async function buildSapAgentContext(input, client) {
|
|
759
|
+
const wallet = optionalString(input, 'wallet');
|
|
760
|
+
const agentPda = optionalString(input, 'agentPda');
|
|
761
|
+
const query = optionalString(input, 'query');
|
|
762
|
+
const limit = Math.max(1, Math.min(optionalNumber(input, 'limit') ?? 10, 20));
|
|
763
|
+
const exact = {};
|
|
764
|
+
if (wallet) {
|
|
765
|
+
const owner = new PublicKey(wallet);
|
|
766
|
+
const derivedAgentPda = client.agent.deriveAgent(owner)[0].toBase58();
|
|
767
|
+
const [agent, active, profile] = await Promise.all([
|
|
768
|
+
client.agent.fetchNullable(owner),
|
|
769
|
+
client.discovery.isAgentActive(owner).catch(() => false),
|
|
770
|
+
client.discovery.getAgentProfile(owner).catch((error) => ({
|
|
771
|
+
unavailable: true,
|
|
772
|
+
reason: error instanceof Error ? error.message : 'profile_fetch_failed',
|
|
773
|
+
})),
|
|
774
|
+
]);
|
|
775
|
+
exact.wallet = wallet;
|
|
776
|
+
exact.agentPda = derivedAgentPda;
|
|
777
|
+
exact.active = active;
|
|
778
|
+
exact.agent = agent;
|
|
779
|
+
exact.profile = profile;
|
|
780
|
+
}
|
|
781
|
+
const directory = wallet
|
|
782
|
+
? null
|
|
783
|
+
: await getCachedAgentDirectoryPage(client, {
|
|
784
|
+
includeInactive: false,
|
|
785
|
+
protocol: undefined,
|
|
786
|
+
capability: undefined,
|
|
787
|
+
capabilities: [],
|
|
788
|
+
capabilityMode: 'any',
|
|
789
|
+
query,
|
|
790
|
+
wallet: undefined,
|
|
791
|
+
agentPda,
|
|
792
|
+
hasX402Endpoint: undefined,
|
|
793
|
+
limit,
|
|
794
|
+
offset: 0,
|
|
795
|
+
view: 'compact',
|
|
796
|
+
includeProtocolIndexes: false,
|
|
797
|
+
});
|
|
798
|
+
return {
|
|
799
|
+
contextType: wallet ? 'exact-wallet' : agentPda ? 'exact-agent-pda' : query ? 'compact-query' : 'compact-orientation',
|
|
800
|
+
freeRead: true,
|
|
801
|
+
exact,
|
|
802
|
+
directory,
|
|
803
|
+
routing: {
|
|
804
|
+
firstReads: [
|
|
805
|
+
'Use sap_get_agent or sap_get_agent_profile when the owner wallet is known.',
|
|
806
|
+
'Use sap_list_agents with limit <= 20 and view: compact for free orientation.',
|
|
807
|
+
'Use sap_discover_agents or sap_list_all_agents only for paid search, enriched rows, large pages, or ecosystem-scale scans.',
|
|
808
|
+
],
|
|
809
|
+
paidHostedTools: 'Use sap_payments_call_paid_tool when a hosted tool returns x402 payment_required and the runtime cannot replay x402 natively.',
|
|
810
|
+
registryWrites: 'Use sap_payments_register_agent and sap_payments_update_agent for wallet-owned SAP registry writes. Do not retry hosted accountless writes after hosted_local_signer_required.',
|
|
811
|
+
unsignedTransactions: 'Use hosted builders when available, then sap_payments_finalize_transaction with submit:true after user confirmation.',
|
|
812
|
+
},
|
|
813
|
+
nextActions: [
|
|
814
|
+
wallet ? 'If the user wants details, use the exact profile already returned here before paid discovery.' : 'If the user selects an agent row, call sap_get_agent_profile with that row wallet.',
|
|
815
|
+
'For registration or image/profile updates, call sap_agent_identity_plan before any write.',
|
|
816
|
+
'For paid or write flows, call sap_payments_readiness first when the local bridge is visible.',
|
|
817
|
+
],
|
|
818
|
+
};
|
|
819
|
+
}
|
|
365
820
|
function parseCapabilityMode(input) {
|
|
366
821
|
const mode = optionalString(input, 'capabilityMode')?.trim().toLowerCase();
|
|
367
822
|
if (!mode) {
|
|
@@ -594,6 +1049,45 @@ function parseVolumeCurve(value) {
|
|
|
594
1049
|
};
|
|
595
1050
|
});
|
|
596
1051
|
}
|
|
1052
|
+
/**
|
|
1053
|
+
* @name optionalTokenType
|
|
1054
|
+
* @description Converts public tokenType strings into SDK Anchor enum variants.
|
|
1055
|
+
*/
|
|
1056
|
+
function optionalTokenType(input) {
|
|
1057
|
+
const raw = optionalString(input, 'tokenType') ?? 'sol';
|
|
1058
|
+
const normalized = raw.trim().toLowerCase();
|
|
1059
|
+
if (normalized === 'sol' || normalized === 'native' || normalized === 'lamports') {
|
|
1060
|
+
return { tokenType: TokenType.Sol, decimalsFallback: 9 };
|
|
1061
|
+
}
|
|
1062
|
+
if (normalized === 'usdc' || normalized === 'micro-usdc' || normalized === 'micro_usdc') {
|
|
1063
|
+
return { tokenType: TokenType.Usdc, decimalsFallback: 6 };
|
|
1064
|
+
}
|
|
1065
|
+
if (normalized === 'spl' || normalized === 'token') {
|
|
1066
|
+
return { tokenType: TokenType.Spl, decimalsFallback: 0 };
|
|
1067
|
+
}
|
|
1068
|
+
throw new Error('tokenType must be one of sol, usdc, or spl');
|
|
1069
|
+
}
|
|
1070
|
+
/**
|
|
1071
|
+
* @name optionalSettlementMode
|
|
1072
|
+
* @description Converts public settlementMode strings into SDK Anchor enum variants.
|
|
1073
|
+
*/
|
|
1074
|
+
function optionalSettlementMode(input) {
|
|
1075
|
+
const raw = optionalString(input, 'settlementMode') ?? 'escrow';
|
|
1076
|
+
const normalized = raw.trim().toLowerCase();
|
|
1077
|
+
if (normalized === 'instant') {
|
|
1078
|
+
return SettlementMode.Instant;
|
|
1079
|
+
}
|
|
1080
|
+
if (normalized === 'escrow') {
|
|
1081
|
+
return SettlementMode.Escrow;
|
|
1082
|
+
}
|
|
1083
|
+
if (normalized === 'batched' || normalized === 'batch') {
|
|
1084
|
+
return SettlementMode.Batched;
|
|
1085
|
+
}
|
|
1086
|
+
if (normalized === 'x402' || normalized === 'pay.sh' || normalized === 'paysh') {
|
|
1087
|
+
return SettlementMode.X402;
|
|
1088
|
+
}
|
|
1089
|
+
throw new Error('settlementMode must be one of instant, escrow, batched, or x402');
|
|
1090
|
+
}
|
|
597
1091
|
/**
|
|
598
1092
|
* @name requiredBytes
|
|
599
1093
|
* @description Reads byte-array fields used for hashes, nonces, and encrypted payloads.
|
|
@@ -700,6 +1194,12 @@ function parsePricingTiers(value) {
|
|
|
700
1194
|
return value.map((item) => {
|
|
701
1195
|
const record = asRecord(item);
|
|
702
1196
|
const pricePerCall = optionalBn(record, 'pricePerCall', new BN(0));
|
|
1197
|
+
const { tokenType, decimalsFallback } = optionalTokenType(record);
|
|
1198
|
+
const tokenMint = optionalPublicKey(record, 'tokenMint') ?? null;
|
|
1199
|
+
if (tokenType === TokenType.Spl && tokenMint === null) {
|
|
1200
|
+
throw new Error('pricing.tokenMint is required when tokenType is spl');
|
|
1201
|
+
}
|
|
1202
|
+
const volumeCurve = parseVolumeCurve(record.volumeCurve);
|
|
703
1203
|
return {
|
|
704
1204
|
tierId: optionalString(record, 'tierId') ?? 'default',
|
|
705
1205
|
pricePerCall,
|
|
@@ -708,13 +1208,13 @@ function parsePricingTiers(value) {
|
|
|
708
1208
|
rateLimit: optionalNumber(record, 'rateLimit') ?? 60,
|
|
709
1209
|
maxCallsPerSession: optionalNumber(record, 'maxCallsPerSession') ?? 1_000,
|
|
710
1210
|
burstLimit: optionalNumber(record, 'burstLimit') ?? null,
|
|
711
|
-
tokenType
|
|
712
|
-
tokenMint
|
|
713
|
-
tokenDecimals: optionalNumber(record, 'tokenDecimals') ??
|
|
714
|
-
settlementMode:
|
|
1211
|
+
tokenType,
|
|
1212
|
+
tokenMint,
|
|
1213
|
+
tokenDecimals: optionalNumber(record, 'tokenDecimals') ?? decimalsFallback,
|
|
1214
|
+
settlementMode: optionalSettlementMode(record),
|
|
715
1215
|
minEscrowDeposit: record.minEscrowDeposit === undefined ? null : requiredBn(record, 'minEscrowDeposit'),
|
|
716
1216
|
batchIntervalSec: optionalNumber(record, 'batchIntervalSec') ?? null,
|
|
717
|
-
volumeCurve: null,
|
|
1217
|
+
volumeCurve: volumeCurve.length > 0 ? volumeCurve : null,
|
|
718
1218
|
};
|
|
719
1219
|
});
|
|
720
1220
|
}
|
|
@@ -756,7 +1256,7 @@ export function parseRegisterAgentArgs(input) {
|
|
|
756
1256
|
* @name parseUpdateAgentArgs
|
|
757
1257
|
* @description Builds strongly typed `UpdateAgentArgs` from MCP JSON input.
|
|
758
1258
|
*/
|
|
759
|
-
function parseUpdateAgentArgs(input) {
|
|
1259
|
+
export function parseUpdateAgentArgs(input) {
|
|
760
1260
|
return {
|
|
761
1261
|
name: optionalString(input, 'name') ?? null,
|
|
762
1262
|
description: optionalString(input, 'description') ?? null,
|
|
@@ -768,6 +1268,168 @@ function parseUpdateAgentArgs(input) {
|
|
|
768
1268
|
x402Endpoint: optionalString(input, 'x402Endpoint') ?? null,
|
|
769
1269
|
};
|
|
770
1270
|
}
|
|
1271
|
+
function parseIdentityPlanAction(input) {
|
|
1272
|
+
const value = optionalString(input, 'intendedAction') ?? 'full-identity';
|
|
1273
|
+
if (['register', 'update', 'link-metaplex', 'link-sns', 'full-identity'].includes(value)) {
|
|
1274
|
+
return value;
|
|
1275
|
+
}
|
|
1276
|
+
throw new Error('intendedAction must be one of register, update, link-metaplex, link-sns, full-identity');
|
|
1277
|
+
}
|
|
1278
|
+
function optionalPublicMetadataUri(input) {
|
|
1279
|
+
return optionalString(input, 'metadataUri') ?? optionalString(input, 'agentUri') ?? null;
|
|
1280
|
+
}
|
|
1281
|
+
function buildSapAgentIdentityPlan(input) {
|
|
1282
|
+
const intendedAction = parseIdentityPlanAction(input);
|
|
1283
|
+
const metadataUri = optionalPublicMetadataUri(input);
|
|
1284
|
+
const imageUrl = optionalString(input, 'imageUrl') ?? null;
|
|
1285
|
+
const metaplexAsset = optionalString(input, 'metaplexAsset') ?? null;
|
|
1286
|
+
const snsDomain = optionalString(input, 'snsDomain') ?? null;
|
|
1287
|
+
const ownerWallet = optionalString(input, 'ownerWallet') ?? null;
|
|
1288
|
+
const missingRegisterFields = ['name', 'description', 'capabilities', 'protocols']
|
|
1289
|
+
.filter((field) => input[field] === undefined || input[field] === null);
|
|
1290
|
+
const hasCompleteRegisterFields = missingRegisterFields.length === 0;
|
|
1291
|
+
const registerArgs = hasCompleteRegisterFields
|
|
1292
|
+
? parseRegisterAgentArgs({
|
|
1293
|
+
...input,
|
|
1294
|
+
capabilities: input.capabilities ?? [],
|
|
1295
|
+
pricing: input.pricing ?? [],
|
|
1296
|
+
protocols: input.protocols ?? [],
|
|
1297
|
+
})
|
|
1298
|
+
: null;
|
|
1299
|
+
const updateArgs = parseUpdateAgentArgs(input);
|
|
1300
|
+
return {
|
|
1301
|
+
intendedAction,
|
|
1302
|
+
trustBoundary: {
|
|
1303
|
+
hostedSap: 'Use hosted sap for reads, paid hosted tools, and unsigned builders.',
|
|
1304
|
+
localSapPayments: 'Use local sap_payments for x402 payment signing, SAP registry writes, and transaction finalization.',
|
|
1305
|
+
keypairMaterial: 'Never read or export keypair JSON. Only local SAP MCP signer tools may sign.',
|
|
1306
|
+
},
|
|
1307
|
+
nextTools: {
|
|
1308
|
+
readiness: 'sap_payments_readiness',
|
|
1309
|
+
register: intendedAction === 'register' || intendedAction === 'full-identity' ? 'sap_payments_register_agent' : null,
|
|
1310
|
+
update: intendedAction !== 'register' ? 'sap_payments_update_agent' : null,
|
|
1311
|
+
metaplex: metaplexAsset ? 'metaplex-nft_* or DAS verification tools before SAP metadata update' : null,
|
|
1312
|
+
sns: snsDomain ? ['sap_sns_check_domain', 'sap_sns_check_ownership', 'sap_sns_build_manage_record_transaction when managing records'] : null,
|
|
1313
|
+
profileVerification: 'sap_get_agent_profile',
|
|
1314
|
+
},
|
|
1315
|
+
missingRegisterFields,
|
|
1316
|
+
normalizedRegistration: registerArgs
|
|
1317
|
+
? {
|
|
1318
|
+
name: registerArgs.name,
|
|
1319
|
+
description: registerArgs.description,
|
|
1320
|
+
agentId: registerArgs.agentId,
|
|
1321
|
+
agentUri: registerArgs.agentUri,
|
|
1322
|
+
x402Endpoint: registerArgs.x402Endpoint,
|
|
1323
|
+
protocols: registerArgs.protocols,
|
|
1324
|
+
capabilities: registerArgs.capabilities,
|
|
1325
|
+
pricing: registerArgs.pricing,
|
|
1326
|
+
confirm: true,
|
|
1327
|
+
}
|
|
1328
|
+
: null,
|
|
1329
|
+
normalizedUpdate: {
|
|
1330
|
+
name: updateArgs.name,
|
|
1331
|
+
description: updateArgs.description,
|
|
1332
|
+
agentId: updateArgs.agentId,
|
|
1333
|
+
agentUri: updateArgs.agentUri,
|
|
1334
|
+
x402Endpoint: updateArgs.x402Endpoint,
|
|
1335
|
+
protocols: updateArgs.protocols,
|
|
1336
|
+
capabilities: updateArgs.capabilities,
|
|
1337
|
+
pricing: updateArgs.pricing,
|
|
1338
|
+
confirm: true,
|
|
1339
|
+
},
|
|
1340
|
+
publicMetadataContract: {
|
|
1341
|
+
requiredForImages: true,
|
|
1342
|
+
metadataUri,
|
|
1343
|
+
imageUrl,
|
|
1344
|
+
metaplexAsset,
|
|
1345
|
+
snsDomain,
|
|
1346
|
+
recommendedShape: {
|
|
1347
|
+
name: input.name ?? '<agent name>',
|
|
1348
|
+
description: input.description ?? '<agent description>',
|
|
1349
|
+
image: imageUrl ?? '<public image URL>',
|
|
1350
|
+
external_url: ownerWallet ? `https://explorer.oobeprotocol.ai/agents/${ownerWallet}` : '<public agent page>',
|
|
1351
|
+
attributes: [
|
|
1352
|
+
{ trait_type: 'Protocol', value: 'SAP' },
|
|
1353
|
+
{ trait_type: 'Identity', value: [metaplexAsset ? 'Metaplex' : null, snsDomain ? 'SNS' : null].filter(Boolean).join(' + ') || 'SAP' },
|
|
1354
|
+
],
|
|
1355
|
+
sap: {
|
|
1356
|
+
ownerWallet: ownerWallet ?? '<owner wallet>',
|
|
1357
|
+
agentId: optionalString(input, 'agentId') ?? '<stable id>',
|
|
1358
|
+
capabilities: Array.isArray(input.capabilities) ? input.capabilities : [],
|
|
1359
|
+
},
|
|
1360
|
+
metaplex: metaplexAsset ? { asset: metaplexAsset } : null,
|
|
1361
|
+
sns: snsDomain ? { domain: snsDomain } : null,
|
|
1362
|
+
x402: optionalString(input, 'x402Endpoint') ? { endpoint: optionalString(input, 'x402Endpoint') } : null,
|
|
1363
|
+
},
|
|
1364
|
+
},
|
|
1365
|
+
verificationChecklist: [
|
|
1366
|
+
'Call sap_payments_readiness before paid/write actions.',
|
|
1367
|
+
'For registration, call sap_payments_register_agent with confirm:true; do not call hosted sap_register_agent after hosted_local_signer_required.',
|
|
1368
|
+
'Verify sap_payments_register_agent success, confirmationStatus, agentPda, and protocolFee.status.',
|
|
1369
|
+
'Fetch sap_get_agent_profile by owner wallet after registration or update.',
|
|
1370
|
+
'For update, verify every intended replacement field because arrays are full replacements.',
|
|
1371
|
+
'For Metaplex identity, verify the asset/collection metadata URI and update authority before linking it from SAP metadata.',
|
|
1372
|
+
'For SNS identity, verify domain ownership and records before claiming the .sol identity is linked.',
|
|
1373
|
+
],
|
|
1374
|
+
forbiddenActions: [
|
|
1375
|
+
'Do not create temporary signing scripts.',
|
|
1376
|
+
'Do not read keypair JSON.',
|
|
1377
|
+
'Do not reuse stale x402 payment signatures.',
|
|
1378
|
+
'Do not pay hosted x402 fees for hosted_local_signer_required registry writes.',
|
|
1379
|
+
'Do not announce lifecycle complete if protocolFee.status is missing_or_underpaid.',
|
|
1380
|
+
],
|
|
1381
|
+
};
|
|
1382
|
+
}
|
|
1383
|
+
function buildSapProtocolInvariants() {
|
|
1384
|
+
return {
|
|
1385
|
+
protocol: {
|
|
1386
|
+
name: 'Synapse Agent Protocol',
|
|
1387
|
+
programId: DEFAULT_SAP_PROGRAM_ID,
|
|
1388
|
+
network: 'mainnet-beta',
|
|
1389
|
+
custodyModel: 'non-custodial',
|
|
1390
|
+
},
|
|
1391
|
+
registrationFee: {
|
|
1392
|
+
sourceExpected: true,
|
|
1393
|
+
treasury: SAP_PROTOCOL_TREASURY,
|
|
1394
|
+
lamports: SAP_REGISTRATION_FEE_LAMPORTS.toString(10),
|
|
1395
|
+
sol: Number(SAP_REGISTRATION_FEE_LAMPORTS) / 1_000_000_000,
|
|
1396
|
+
verification: 'sap_payments_register_agent verifies the landed transaction pre/post balance delta for the treasury account because deployed program behavior can drift from the source-level invariant.',
|
|
1397
|
+
failureStatus: 'missing_or_underpaid',
|
|
1398
|
+
failureRule: 'If protocolFee.status is missing_or_underpaid, the agent account can still exist, but sap_payments_register_agent must return success:false, agentRegistered:true, and protocolComplete:false. Do not retry registration automatically; inspect the signature, deployed SAP program, and treasury delta first.',
|
|
1399
|
+
},
|
|
1400
|
+
hostedWritePolicy: {
|
|
1401
|
+
register: 'Hosted sap_register_agent is accountless and returns hosted_local_signer_required before x402 payment.',
|
|
1402
|
+
update: 'Hosted sap_update_agent is accountless and returns hosted_local_signer_required before x402 payment.',
|
|
1403
|
+
sns: 'Hosted direct SNS writes require a local signer or unsigned builder. If no builder exists, stop and route through local SAP MCP.',
|
|
1404
|
+
noChargeRule: 'hosted_local_signer_required is a routing guard, not a paid failure; no hosted x402 fee should be charged for that blocked write.',
|
|
1405
|
+
},
|
|
1406
|
+
localSignerRoutes: {
|
|
1407
|
+
readiness: 'sap_payments_readiness',
|
|
1408
|
+
registerAgent: 'sap_payments_register_agent',
|
|
1409
|
+
updateAgent: 'sap_payments_update_agent',
|
|
1410
|
+
finalizeUnsignedTransaction: 'sap_payments_finalize_transaction',
|
|
1411
|
+
paidHostedTool: 'sap_payments_call_paid_tool',
|
|
1412
|
+
externalX402Http: 'sap_payments_call_external_x402',
|
|
1413
|
+
},
|
|
1414
|
+
identityPipeline: [
|
|
1415
|
+
'Call sap_agent_identity_plan for register, update, Metaplex, SNS, or full-identity intent.',
|
|
1416
|
+
'Upload image and metadata to a public URL before writing agentUri or metadataUri.',
|
|
1417
|
+
'Use sap_payments_register_agent for local non-custodial registration.',
|
|
1418
|
+
'Verify success, agentRegistered, confirmationStatus, agentPda, protocolComplete, and protocolFee.status.',
|
|
1419
|
+
'Use Metaplex/MPL Core tools only after metadata authority and URI are clear.',
|
|
1420
|
+
'Use SNS tools only after domain availability/ownership and user confirmation are clear.',
|
|
1421
|
+
'Use sap_payments_update_agent for profile image, metadata, capabilities, pricing, protocols, or x402 endpoint updates.',
|
|
1422
|
+
'Fetch sap_get_agent_profile after every registry write and compare intended fields.',
|
|
1423
|
+
],
|
|
1424
|
+
forbiddenActions: [
|
|
1425
|
+
'Do not create temporary signing scripts.',
|
|
1426
|
+
'Do not read keypair JSON.',
|
|
1427
|
+
'Do not call hosted sap_sign_transaction for user-owned signatures.',
|
|
1428
|
+
'Do not call hosted sap_register_agent or sap_update_agent again after hosted_local_signer_required.',
|
|
1429
|
+
'Do not call an agent lifecycle complete when protocolFee.status is missing_or_underpaid, unavailable, or anything other than verified.',
|
|
1430
|
+
],
|
|
1431
|
+
};
|
|
1432
|
+
}
|
|
771
1433
|
/**
|
|
772
1434
|
* @name parseX402PreparePaymentOptions
|
|
773
1435
|
* @description Builds typed x402 payment preparation options from MCP JSON input.
|
|
@@ -885,6 +1547,433 @@ function parseEscrowV2Args(input) {
|
|
|
885
1547
|
arbiter: optionalPublicKey(input, 'arbiter') ?? null,
|
|
886
1548
|
};
|
|
887
1549
|
}
|
|
1550
|
+
function publicKeyOrNull(value) {
|
|
1551
|
+
return value instanceof PublicKey ? value : null;
|
|
1552
|
+
}
|
|
1553
|
+
function bnToBigInt(value) {
|
|
1554
|
+
if (typeof value === 'bigint') {
|
|
1555
|
+
return value;
|
|
1556
|
+
}
|
|
1557
|
+
if (typeof value === 'number') {
|
|
1558
|
+
return BigInt(value);
|
|
1559
|
+
}
|
|
1560
|
+
return BigInt(value.toString());
|
|
1561
|
+
}
|
|
1562
|
+
function bnToPdaSeed(value, field) {
|
|
1563
|
+
const asBigInt = BigInt(value.toString(10));
|
|
1564
|
+
if (asBigInt < 0n) {
|
|
1565
|
+
throw new Error(`${field} must be >= 0`);
|
|
1566
|
+
}
|
|
1567
|
+
return asBigInt;
|
|
1568
|
+
}
|
|
1569
|
+
function serializeAccountMetas(accounts) {
|
|
1570
|
+
return accounts.map((account) => ({
|
|
1571
|
+
pubkey: account.pubkey.toBase58(),
|
|
1572
|
+
isSigner: account.isSigner,
|
|
1573
|
+
isWritable: account.isWritable,
|
|
1574
|
+
}));
|
|
1575
|
+
}
|
|
1576
|
+
function validateEscrowPaymentMint(tokenMint) {
|
|
1577
|
+
if (!tokenMint) {
|
|
1578
|
+
return;
|
|
1579
|
+
}
|
|
1580
|
+
if (!tokenMint.equals(USDC_MINT_MAINNET) && !tokenMint.equals(USDC_MINT_DEVNET)) {
|
|
1581
|
+
throw new Error('Escrow V2 hosted builders only accept native SOL or USDC mints supported by the current SAP program.');
|
|
1582
|
+
}
|
|
1583
|
+
}
|
|
1584
|
+
function deriveAssociatedTokenAddress(tokenMint, owner) {
|
|
1585
|
+
const [address] = PublicKey.findProgramAddressSync([owner.toBuffer(), TOKEN_PROGRAM_ID.toBuffer(), tokenMint.toBuffer()], ASSOCIATED_TOKEN_PROGRAM_ID);
|
|
1586
|
+
return address;
|
|
1587
|
+
}
|
|
1588
|
+
function getEscrowPdas(client, agentWallet, depositorWallet, nonce) {
|
|
1589
|
+
const [agentPda] = Pda.deriveAgent(agentWallet, client.programId);
|
|
1590
|
+
const [escrowPda] = Pda.deriveEscrowV2(agentPda, depositorWallet, bnToPdaSeed(nonce, 'nonce'), client.programId);
|
|
1591
|
+
const [agentStake] = Pda.deriveStake(agentPda, client.programId);
|
|
1592
|
+
const [agentStats] = Pda.deriveAgentStats(agentPda, client.programId);
|
|
1593
|
+
const [pricingMenu] = Pda.derivePricingMenu(agentPda, client.programId);
|
|
1594
|
+
return { agentPda, escrowPda, agentStake, agentStats, pricingMenu };
|
|
1595
|
+
}
|
|
1596
|
+
function buildSplRemainingAccounts(tokenMint, sourceOwner, destinationOwner) {
|
|
1597
|
+
if (!tokenMint) {
|
|
1598
|
+
return [];
|
|
1599
|
+
}
|
|
1600
|
+
return [
|
|
1601
|
+
{
|
|
1602
|
+
pubkey: deriveAssociatedTokenAddress(tokenMint, sourceOwner),
|
|
1603
|
+
isSigner: false,
|
|
1604
|
+
isWritable: true,
|
|
1605
|
+
},
|
|
1606
|
+
{
|
|
1607
|
+
pubkey: deriveAssociatedTokenAddress(tokenMint, destinationOwner),
|
|
1608
|
+
isSigner: false,
|
|
1609
|
+
isWritable: true,
|
|
1610
|
+
},
|
|
1611
|
+
{ pubkey: tokenMint, isSigner: false, isWritable: false },
|
|
1612
|
+
{ pubkey: TOKEN_PROGRAM_ID, isSigner: false, isWritable: false },
|
|
1613
|
+
];
|
|
1614
|
+
}
|
|
1615
|
+
function buildEscrowSettlementRemainingAccounts(params) {
|
|
1616
|
+
const accounts = params.tokenMint
|
|
1617
|
+
? [
|
|
1618
|
+
...buildSplRemainingAccounts(params.tokenMint, params.escrowPda, params.agentWallet),
|
|
1619
|
+
{
|
|
1620
|
+
pubkey: deriveAssociatedTokenAddress(params.tokenMint, new PublicKey(SAP_PROTOCOL_TREASURY)),
|
|
1621
|
+
isSigner: false,
|
|
1622
|
+
isWritable: true,
|
|
1623
|
+
},
|
|
1624
|
+
]
|
|
1625
|
+
: [
|
|
1626
|
+
{
|
|
1627
|
+
pubkey: new PublicKey(SAP_PROTOCOL_TREASURY),
|
|
1628
|
+
isSigner: false,
|
|
1629
|
+
isWritable: true,
|
|
1630
|
+
},
|
|
1631
|
+
];
|
|
1632
|
+
if (params.pendingSettlementPda) {
|
|
1633
|
+
accounts.push({ pubkey: params.pendingSettlementPda, isSigner: false, isWritable: true });
|
|
1634
|
+
}
|
|
1635
|
+
if (params.coSigner) {
|
|
1636
|
+
accounts.push({ pubkey: params.coSigner, isSigner: true, isWritable: false });
|
|
1637
|
+
}
|
|
1638
|
+
return accounts;
|
|
1639
|
+
}
|
|
1640
|
+
async function serializeUnsignedTransaction(client, feePayer, instructions) {
|
|
1641
|
+
const latestBlockhash = await client.connection.getLatestBlockhash('confirmed');
|
|
1642
|
+
const tx = new Transaction();
|
|
1643
|
+
tx.feePayer = feePayer;
|
|
1644
|
+
tx.recentBlockhash = latestBlockhash.blockhash;
|
|
1645
|
+
for (const instruction of instructions) {
|
|
1646
|
+
tx.add(instruction);
|
|
1647
|
+
}
|
|
1648
|
+
return {
|
|
1649
|
+
transactionBase64: Buffer.from(tx.serialize({ requireAllSignatures: false })).toString('base64'),
|
|
1650
|
+
blockhash: latestBlockhash.blockhash,
|
|
1651
|
+
lastValidBlockHeight: latestBlockhash.lastValidBlockHeight,
|
|
1652
|
+
};
|
|
1653
|
+
}
|
|
1654
|
+
async function fetchEscrowV2Nullable(client, escrowPda) {
|
|
1655
|
+
const accounts = getSapAnchorAccounts(client);
|
|
1656
|
+
return accounts.escrowAccountV2.fetchNullable(escrowPda);
|
|
1657
|
+
}
|
|
1658
|
+
async function fetchPendingSettlementNullable(client, pendingPda) {
|
|
1659
|
+
const accounts = getSapAnchorAccounts(client);
|
|
1660
|
+
return accounts.pendingSettlement.fetchNullable(pendingPda);
|
|
1661
|
+
}
|
|
1662
|
+
function isDisputeWindowEscrow(escrow) {
|
|
1663
|
+
const security = escrow.settlementSecurity;
|
|
1664
|
+
return typeof security === 'object' && security !== null && 'disputeWindow' in security;
|
|
1665
|
+
}
|
|
1666
|
+
function isCoSignedEscrow(escrow) {
|
|
1667
|
+
const security = escrow.settlementSecurity;
|
|
1668
|
+
return typeof security === 'object' && security !== null && 'coSigned' in security;
|
|
1669
|
+
}
|
|
1670
|
+
function escrowBuilderResponse(params) {
|
|
1671
|
+
return {
|
|
1672
|
+
action: params.action,
|
|
1673
|
+
transactionBase64: params.transactionBase64,
|
|
1674
|
+
encoding: 'base64',
|
|
1675
|
+
requiredSigner: params.requiredSigner.toBase58(),
|
|
1676
|
+
requiredSignerRole: params.requiredSignerRole,
|
|
1677
|
+
submitWith: 'sap_payments_finalize_transaction',
|
|
1678
|
+
nextStep: 'Call sap_payments_finalize_transaction with transactionBase64, submit:true, confirm:true, and the same user-approved intent. Do not create temporary signing scripts and do not read keypair JSON.',
|
|
1679
|
+
accounts: {
|
|
1680
|
+
...params.accounts,
|
|
1681
|
+
blockhash: params.blockhash,
|
|
1682
|
+
lastValidBlockHeight: params.lastValidBlockHeight,
|
|
1683
|
+
},
|
|
1684
|
+
tokenMode: params.tokenMint ? 'SPL' : 'SOL',
|
|
1685
|
+
...(params.security ? { security: params.security } : {}),
|
|
1686
|
+
...(params.warnings && params.warnings.length > 0 ? { warnings: params.warnings } : {}),
|
|
1687
|
+
};
|
|
1688
|
+
}
|
|
1689
|
+
async function buildEscrowCreateTransaction(input, client) {
|
|
1690
|
+
const depositorWallet = requiredPublicKey(input, 'depositorWallet');
|
|
1691
|
+
const agentWallet = requiredPublicKey(input, 'agentWallet');
|
|
1692
|
+
const args = parseEscrowV2Args(input);
|
|
1693
|
+
validateEscrowPaymentMint(args.tokenMint);
|
|
1694
|
+
const pdas = getEscrowPdas(client, agentWallet, depositorWallet, args.escrowNonce);
|
|
1695
|
+
const remainingAccounts = buildSplRemainingAccounts(args.tokenMint, depositorWallet, pdas.escrowPda);
|
|
1696
|
+
const methods = client.program.methods;
|
|
1697
|
+
const instruction = await methods.createEscrowV2(args.escrowNonce, args.pricePerCall, args.maxCalls, args.initialDeposit, args.expiresAt, args.volumeCurve, args.tokenMint, args.tokenDecimals, args.settlementSecurity, args.disputeWindowSlots, args.coSigner, args.arbiter)
|
|
1698
|
+
.accounts({
|
|
1699
|
+
depositor: depositorWallet,
|
|
1700
|
+
agent: pdas.agentPda,
|
|
1701
|
+
agentStake: pdas.agentStake,
|
|
1702
|
+
agentStats: pdas.agentStats,
|
|
1703
|
+
pricingMenu: pdas.pricingMenu,
|
|
1704
|
+
escrow: pdas.escrowPda,
|
|
1705
|
+
systemProgram: SystemProgram.programId,
|
|
1706
|
+
})
|
|
1707
|
+
.remainingAccounts(remainingAccounts)
|
|
1708
|
+
.instruction();
|
|
1709
|
+
const tx = await serializeUnsignedTransaction(client, depositorWallet, [instruction]);
|
|
1710
|
+
return escrowBuilderResponse({
|
|
1711
|
+
action: 'create_escrow_v2',
|
|
1712
|
+
transactionBase64: tx.transactionBase64,
|
|
1713
|
+
requiredSigner: depositorWallet,
|
|
1714
|
+
requiredSignerRole: 'depositor',
|
|
1715
|
+
tokenMint: args.tokenMint,
|
|
1716
|
+
blockhash: tx.blockhash,
|
|
1717
|
+
lastValidBlockHeight: tx.lastValidBlockHeight,
|
|
1718
|
+
accounts: {
|
|
1719
|
+
agentWallet,
|
|
1720
|
+
depositorWallet,
|
|
1721
|
+
agentPda: pdas.agentPda,
|
|
1722
|
+
escrowPda: pdas.escrowPda,
|
|
1723
|
+
agentStake: pdas.agentStake,
|
|
1724
|
+
agentStats: pdas.agentStats,
|
|
1725
|
+
pricingMenu: pdas.pricingMenu,
|
|
1726
|
+
remainingAccounts: serializeAccountMetas(remainingAccounts),
|
|
1727
|
+
},
|
|
1728
|
+
security: {
|
|
1729
|
+
settlementSecurity: args.settlementSecurity,
|
|
1730
|
+
disputeWindowSlots: args.disputeWindowSlots,
|
|
1731
|
+
coSigner: args.coSigner,
|
|
1732
|
+
arbiter: args.arbiter,
|
|
1733
|
+
},
|
|
1734
|
+
});
|
|
1735
|
+
}
|
|
1736
|
+
async function buildEscrowDepositTransaction(input, client) {
|
|
1737
|
+
const depositorWallet = requiredPublicKey(input, 'depositorWallet');
|
|
1738
|
+
const agentWallet = requiredPublicKey(input, 'agentWallet');
|
|
1739
|
+
const nonce = optionalBn(input, 'nonce', new BN(0));
|
|
1740
|
+
const amount = requiredBn(input, 'amount');
|
|
1741
|
+
const pdas = getEscrowPdas(client, agentWallet, depositorWallet, nonce);
|
|
1742
|
+
const escrow = await fetchEscrowV2Nullable(client, pdas.escrowPda);
|
|
1743
|
+
if (!escrow) {
|
|
1744
|
+
throw new Error(`Escrow V2 PDA ${pdas.escrowPda.toBase58()} does not exist. Build and finalize sap_escrow_build_create_transaction first.`);
|
|
1745
|
+
}
|
|
1746
|
+
const tokenMint = publicKeyOrNull(escrow.tokenMint);
|
|
1747
|
+
const remainingAccounts = buildSplRemainingAccounts(tokenMint, depositorWallet, pdas.escrowPda);
|
|
1748
|
+
const methods = client.program.methods;
|
|
1749
|
+
const instruction = await methods.depositEscrowV2(nonce, amount)
|
|
1750
|
+
.accounts({
|
|
1751
|
+
depositor: depositorWallet,
|
|
1752
|
+
escrow: pdas.escrowPda,
|
|
1753
|
+
systemProgram: SystemProgram.programId,
|
|
1754
|
+
})
|
|
1755
|
+
.remainingAccounts(remainingAccounts)
|
|
1756
|
+
.instruction();
|
|
1757
|
+
const tx = await serializeUnsignedTransaction(client, depositorWallet, [instruction]);
|
|
1758
|
+
return escrowBuilderResponse({
|
|
1759
|
+
action: 'deposit_escrow_v2',
|
|
1760
|
+
transactionBase64: tx.transactionBase64,
|
|
1761
|
+
requiredSigner: depositorWallet,
|
|
1762
|
+
requiredSignerRole: 'depositor',
|
|
1763
|
+
tokenMint,
|
|
1764
|
+
blockhash: tx.blockhash,
|
|
1765
|
+
lastValidBlockHeight: tx.lastValidBlockHeight,
|
|
1766
|
+
accounts: {
|
|
1767
|
+
agentWallet,
|
|
1768
|
+
depositorWallet,
|
|
1769
|
+
escrowPda: pdas.escrowPda,
|
|
1770
|
+
amount,
|
|
1771
|
+
remainingAccounts: serializeAccountMetas(remainingAccounts),
|
|
1772
|
+
},
|
|
1773
|
+
});
|
|
1774
|
+
}
|
|
1775
|
+
async function buildEscrowWithdrawTransaction(input, client) {
|
|
1776
|
+
const depositorWallet = requiredPublicKey(input, 'depositorWallet');
|
|
1777
|
+
const agentWallet = requiredPublicKey(input, 'agentWallet');
|
|
1778
|
+
const nonce = optionalBn(input, 'nonce', new BN(0));
|
|
1779
|
+
const amount = requiredBn(input, 'amount');
|
|
1780
|
+
const pdas = getEscrowPdas(client, agentWallet, depositorWallet, nonce);
|
|
1781
|
+
const escrow = await fetchEscrowV2Nullable(client, pdas.escrowPda);
|
|
1782
|
+
if (!escrow) {
|
|
1783
|
+
throw new Error(`Escrow V2 PDA ${pdas.escrowPda.toBase58()} does not exist.`);
|
|
1784
|
+
}
|
|
1785
|
+
const balance = bnToBigInt(escrow.balance);
|
|
1786
|
+
const pendingAmount = bnToBigInt(escrow.pendingAmount);
|
|
1787
|
+
const withdrawable = balance > pendingAmount ? balance - pendingAmount : 0n;
|
|
1788
|
+
if (BigInt(amount.toString(10)) > withdrawable) {
|
|
1789
|
+
throw new Error(`Withdraw amount exceeds withdrawable escrow balance. requested=${amount.toString(10)}, withdrawable=${withdrawable.toString()}.`);
|
|
1790
|
+
}
|
|
1791
|
+
const tokenMint = publicKeyOrNull(escrow.tokenMint);
|
|
1792
|
+
const remainingAccounts = buildSplRemainingAccounts(tokenMint, pdas.escrowPda, depositorWallet);
|
|
1793
|
+
const methods = client.program.methods;
|
|
1794
|
+
const instruction = await methods.withdrawEscrowV2(amount)
|
|
1795
|
+
.accounts({
|
|
1796
|
+
depositor: depositorWallet,
|
|
1797
|
+
escrow: pdas.escrowPda,
|
|
1798
|
+
})
|
|
1799
|
+
.remainingAccounts(remainingAccounts)
|
|
1800
|
+
.instruction();
|
|
1801
|
+
const tx = await serializeUnsignedTransaction(client, depositorWallet, [instruction]);
|
|
1802
|
+
return escrowBuilderResponse({
|
|
1803
|
+
action: 'withdraw_escrow_v2',
|
|
1804
|
+
transactionBase64: tx.transactionBase64,
|
|
1805
|
+
requiredSigner: depositorWallet,
|
|
1806
|
+
requiredSignerRole: 'depositor',
|
|
1807
|
+
tokenMint,
|
|
1808
|
+
blockhash: tx.blockhash,
|
|
1809
|
+
lastValidBlockHeight: tx.lastValidBlockHeight,
|
|
1810
|
+
accounts: {
|
|
1811
|
+
agentWallet,
|
|
1812
|
+
depositorWallet,
|
|
1813
|
+
escrowPda: pdas.escrowPda,
|
|
1814
|
+
amount,
|
|
1815
|
+
withdrawable: withdrawable.toString(),
|
|
1816
|
+
remainingAccounts: serializeAccountMetas(remainingAccounts),
|
|
1817
|
+
},
|
|
1818
|
+
});
|
|
1819
|
+
}
|
|
1820
|
+
async function buildEscrowSettleTransaction(input, client) {
|
|
1821
|
+
const agentWallet = requiredPublicKey(input, 'agentWallet');
|
|
1822
|
+
const depositorWallet = requiredPublicKey(input, 'depositorWallet');
|
|
1823
|
+
const nonce = optionalBn(input, 'nonce', new BN(0));
|
|
1824
|
+
const callsToSettle = requiredBn(input, 'callsToSettle');
|
|
1825
|
+
const serviceHash = requiredBytes(input, 'serviceHash', 32);
|
|
1826
|
+
const pdas = getEscrowPdas(client, agentWallet, depositorWallet, nonce);
|
|
1827
|
+
const escrow = await fetchEscrowV2Nullable(client, pdas.escrowPda);
|
|
1828
|
+
if (!escrow) {
|
|
1829
|
+
throw new Error(`Escrow V2 PDA ${pdas.escrowPda.toBase58()} does not exist.`);
|
|
1830
|
+
}
|
|
1831
|
+
const tokenMint = publicKeyOrNull(escrow.tokenMint);
|
|
1832
|
+
const pendingSettlementIndex = bnToBigInt(escrow.settlementIndex);
|
|
1833
|
+
let pendingSettlementPda;
|
|
1834
|
+
if (isDisputeWindowEscrow(escrow)) {
|
|
1835
|
+
[pendingSettlementPda] = Pda.derivePendingSettlement(pdas.escrowPda, pendingSettlementIndex, client.programId);
|
|
1836
|
+
const existing = await fetchPendingSettlementNullable(client, pendingSettlementPda);
|
|
1837
|
+
if (existing) {
|
|
1838
|
+
throw new Error(`Pending settlement ${pendingSettlementPda.toBase58()} already exists for settlementIndex=${pendingSettlementIndex.toString()}. Finalize or quarantine it before building another DisputeWindow settlement.`);
|
|
1839
|
+
}
|
|
1840
|
+
}
|
|
1841
|
+
const coSigner = isCoSignedEscrow(escrow) ? publicKeyOrNull(escrow.coSigner) : optionalPublicKey(input, 'coSigner') ?? null;
|
|
1842
|
+
if (isCoSignedEscrow(escrow) && !coSigner) {
|
|
1843
|
+
throw new Error('CoSigned escrow requires coSigner in the escrow account.');
|
|
1844
|
+
}
|
|
1845
|
+
const remainingAccounts = buildEscrowSettlementRemainingAccounts({
|
|
1846
|
+
tokenMint,
|
|
1847
|
+
escrowPda: pdas.escrowPda,
|
|
1848
|
+
agentWallet,
|
|
1849
|
+
pendingSettlementPda,
|
|
1850
|
+
coSigner,
|
|
1851
|
+
});
|
|
1852
|
+
const methods = client.program.methods;
|
|
1853
|
+
const instruction = await methods.settleCallsV2(nonce, callsToSettle, serviceHash)
|
|
1854
|
+
.accountsPartial({
|
|
1855
|
+
wallet: agentWallet,
|
|
1856
|
+
agent: pdas.agentPda,
|
|
1857
|
+
agentStats: pdas.agentStats,
|
|
1858
|
+
escrow: pdas.escrowPda,
|
|
1859
|
+
systemProgram: SystemProgram.programId,
|
|
1860
|
+
})
|
|
1861
|
+
.remainingAccounts(remainingAccounts)
|
|
1862
|
+
.instruction();
|
|
1863
|
+
const tx = await serializeUnsignedTransaction(client, agentWallet, [instruction]);
|
|
1864
|
+
return escrowBuilderResponse({
|
|
1865
|
+
action: 'settle_calls_v2',
|
|
1866
|
+
transactionBase64: tx.transactionBase64,
|
|
1867
|
+
requiredSigner: agentWallet,
|
|
1868
|
+
requiredSignerRole: 'agentWallet',
|
|
1869
|
+
tokenMint,
|
|
1870
|
+
blockhash: tx.blockhash,
|
|
1871
|
+
lastValidBlockHeight: tx.lastValidBlockHeight,
|
|
1872
|
+
accounts: {
|
|
1873
|
+
agentWallet,
|
|
1874
|
+
depositorWallet,
|
|
1875
|
+
agentPda: pdas.agentPda,
|
|
1876
|
+
agentStats: pdas.agentStats,
|
|
1877
|
+
escrowPda: pdas.escrowPda,
|
|
1878
|
+
pendingSettlementPda: pendingSettlementPda ?? null,
|
|
1879
|
+
pendingSettlementIndex: pendingSettlementIndex.toString(),
|
|
1880
|
+
callsToSettle,
|
|
1881
|
+
serviceHash,
|
|
1882
|
+
remainingAccounts: serializeAccountMetas(remainingAccounts),
|
|
1883
|
+
},
|
|
1884
|
+
security: {
|
|
1885
|
+
settlementSecurity: isDisputeWindowEscrow(escrow) ? ESCROW_V2_DISPUTE_WINDOW_MODE : ESCROW_V2_COSIGNED_MODE,
|
|
1886
|
+
coSigner,
|
|
1887
|
+
},
|
|
1888
|
+
});
|
|
1889
|
+
}
|
|
1890
|
+
async function buildEscrowFinalizeTransaction(input, client) {
|
|
1891
|
+
const payerWallet = requiredPublicKey(input, 'payerWallet');
|
|
1892
|
+
const agentWallet = requiredPublicKey(input, 'agentWallet');
|
|
1893
|
+
const depositorWallet = requiredPublicKey(input, 'depositorWallet');
|
|
1894
|
+
const nonce = optionalBn(input, 'nonce', new BN(0));
|
|
1895
|
+
const settlementIndex = requiredBn(input, 'settlementIndex');
|
|
1896
|
+
const pdas = getEscrowPdas(client, agentWallet, depositorWallet, nonce);
|
|
1897
|
+
const [pendingSettlementPda] = Pda.derivePendingSettlement(pdas.escrowPda, bnToPdaSeed(settlementIndex, 'settlementIndex'), client.programId);
|
|
1898
|
+
const escrow = await fetchEscrowV2Nullable(client, pdas.escrowPda);
|
|
1899
|
+
if (!escrow) {
|
|
1900
|
+
throw new Error(`Escrow V2 PDA ${pdas.escrowPda.toBase58()} does not exist.`);
|
|
1901
|
+
}
|
|
1902
|
+
const pending = await fetchPendingSettlementNullable(client, pendingSettlementPda);
|
|
1903
|
+
if (!pending) {
|
|
1904
|
+
throw new Error(`Pending settlement ${pendingSettlementPda.toBase58()} does not exist.`);
|
|
1905
|
+
}
|
|
1906
|
+
const tokenMint = publicKeyOrNull(escrow.tokenMint);
|
|
1907
|
+
const remainingAccounts = buildSplRemainingAccounts(tokenMint, pdas.escrowPda, agentWallet);
|
|
1908
|
+
const methods = client.program.methods;
|
|
1909
|
+
const instruction = await methods.finalizeSettlement()
|
|
1910
|
+
.accounts({
|
|
1911
|
+
payer: payerWallet,
|
|
1912
|
+
agentWallet,
|
|
1913
|
+
escrow: pdas.escrowPda,
|
|
1914
|
+
pendingSettlement: pendingSettlementPda,
|
|
1915
|
+
agentStats: pdas.agentStats,
|
|
1916
|
+
})
|
|
1917
|
+
.remainingAccounts(remainingAccounts)
|
|
1918
|
+
.instruction();
|
|
1919
|
+
const tx = await serializeUnsignedTransaction(client, payerWallet, [instruction]);
|
|
1920
|
+
return escrowBuilderResponse({
|
|
1921
|
+
action: 'finalize_settlement',
|
|
1922
|
+
transactionBase64: tx.transactionBase64,
|
|
1923
|
+
requiredSigner: payerWallet,
|
|
1924
|
+
requiredSignerRole: 'payer',
|
|
1925
|
+
tokenMint,
|
|
1926
|
+
blockhash: tx.blockhash,
|
|
1927
|
+
lastValidBlockHeight: tx.lastValidBlockHeight,
|
|
1928
|
+
accounts: {
|
|
1929
|
+
payerWallet,
|
|
1930
|
+
agentWallet,
|
|
1931
|
+
depositorWallet,
|
|
1932
|
+
escrowPda: pdas.escrowPda,
|
|
1933
|
+
pendingSettlementPda,
|
|
1934
|
+
settlementIndex,
|
|
1935
|
+
releaseSlot: pending.releaseSlot,
|
|
1936
|
+
remainingAccounts: serializeAccountMetas(remainingAccounts),
|
|
1937
|
+
},
|
|
1938
|
+
});
|
|
1939
|
+
}
|
|
1940
|
+
async function buildEscrowCloseTransaction(input, client) {
|
|
1941
|
+
const depositorWallet = requiredPublicKey(input, 'depositorWallet');
|
|
1942
|
+
const agentWallet = requiredPublicKey(input, 'agentWallet');
|
|
1943
|
+
const nonce = optionalBn(input, 'nonce', new BN(0));
|
|
1944
|
+
const pdas = getEscrowPdas(client, agentWallet, depositorWallet, nonce);
|
|
1945
|
+
const escrow = await fetchEscrowV2Nullable(client, pdas.escrowPda);
|
|
1946
|
+
if (!escrow) {
|
|
1947
|
+
throw new Error(`Escrow V2 PDA ${pdas.escrowPda.toBase58()} does not exist.`);
|
|
1948
|
+
}
|
|
1949
|
+
if (bnToBigInt(escrow.balance) !== 0n || bnToBigInt(escrow.pendingAmount) !== 0n) {
|
|
1950
|
+
throw new Error('Escrow cannot close until balance and pendingAmount are both zero.');
|
|
1951
|
+
}
|
|
1952
|
+
const methods = client.program.methods;
|
|
1953
|
+
const instruction = await methods.closeEscrowV2()
|
|
1954
|
+
.accounts({
|
|
1955
|
+
depositor: depositorWallet,
|
|
1956
|
+
escrow: pdas.escrowPda,
|
|
1957
|
+
agentStats: pdas.agentStats,
|
|
1958
|
+
})
|
|
1959
|
+
.instruction();
|
|
1960
|
+
const tx = await serializeUnsignedTransaction(client, depositorWallet, [instruction]);
|
|
1961
|
+
return escrowBuilderResponse({
|
|
1962
|
+
action: 'close_escrow_v2',
|
|
1963
|
+
transactionBase64: tx.transactionBase64,
|
|
1964
|
+
requiredSigner: depositorWallet,
|
|
1965
|
+
requiredSignerRole: 'depositor',
|
|
1966
|
+
tokenMint: publicKeyOrNull(escrow.tokenMint),
|
|
1967
|
+
blockhash: tx.blockhash,
|
|
1968
|
+
lastValidBlockHeight: tx.lastValidBlockHeight,
|
|
1969
|
+
accounts: {
|
|
1970
|
+
agentWallet,
|
|
1971
|
+
depositorWallet,
|
|
1972
|
+
escrowPda: pdas.escrowPda,
|
|
1973
|
+
agentStats: pdas.agentStats,
|
|
1974
|
+
},
|
|
1975
|
+
});
|
|
1976
|
+
}
|
|
888
1977
|
/**
|
|
889
1978
|
* @name parseFeedbackArgs
|
|
890
1979
|
* @description Builds typed feedback args from MCP JSON input.
|
|
@@ -1000,7 +2089,7 @@ function buildSapSdkToolDescription(definition) {
|
|
|
1000
2089
|
}
|
|
1001
2090
|
function getSapSdkToolContext(name) {
|
|
1002
2091
|
if (name === 'sap_register_agent') {
|
|
1003
|
-
return 'SAP MCP context:
|
|
2092
|
+
return 'SAP MCP context: Do not use this raw SDK wrapper for production registration. The canonical registration path is sap_payments_register_agent, because it confirms the account, audits the protocol treasury fee, and fails closed when protocolComplete is false. Hosted accountless SAP MCP rejects direct registration before x402 payment because OOBE never custodies user wallet keys. Use sap_agent_identity_plan first, then sap_payments_register_agent with confirm:true.';
|
|
1004
2093
|
}
|
|
1005
2094
|
if (name === 'sap_update_agent') {
|
|
1006
2095
|
return 'SAP MCP context: Use this after sap_register_agent to refresh name, description, capabilities, pricing, supported protocols, x402 endpoint, or metadataUri. For NFT-backed identity changes, update the Metaplex asset first when needed, then point the SAP agent metadataUri at the current metadata document.';
|
|
@@ -1008,6 +2097,9 @@ function getSapSdkToolContext(name) {
|
|
|
1008
2097
|
if (name.startsWith('sap_publish_tool') || name.startsWith('sap_update_tool')) {
|
|
1009
2098
|
return 'SAP MCP context: Use tool registry writes to advertise concrete capabilities that this MCP can serve, including AgentKit bridge tools such as bridging_bridgeWormhole and Metaplex tools such as metaplex-nft_mintNFT. Publish only schemas and descriptions that match the actual MCP tool surface.';
|
|
1010
2099
|
}
|
|
2100
|
+
if (name.startsWith('sap_escrow_build_')) {
|
|
2101
|
+
return 'SAP MCP context: Hosted-safe unsigned Escrow V2 builder. The output is not submitted and is not signed by hosted SAP MCP. Preview it, then call local sap_payments_finalize_transaction with submit:true and confirm:true. Never create temporary signing scripts or read keypair JSON.';
|
|
2102
|
+
}
|
|
1011
2103
|
if (name.startsWith('sap_discover') || name.startsWith('sap_list') || name.startsWith('sap_find') || name.startsWith('sap_fetch') || name.startsWith('sap_get') || name.startsWith('sap_is')) {
|
|
1012
2104
|
return 'SAP MCP context: Read-only SAP SDK wrapper against the configured Solana RPC and SAP program. Use these reads to inspect current chain state before mutating registry, payment, reputation, memory, or tool accounts.';
|
|
1013
2105
|
}
|
|
@@ -1026,28 +2118,34 @@ function getSapSdkToolContext(name) {
|
|
|
1026
2118
|
return 'SAP MCP context: Direct synapse-sap-sdk wrapper served by this MCP. Read tools return on-chain state; write tools require signer policy, configured RPC, and the active SAP profile.';
|
|
1027
2119
|
}
|
|
1028
2120
|
const agentTools = [
|
|
2121
|
+
{
|
|
2122
|
+
name: 'sap_protocol_invariants',
|
|
2123
|
+
title: 'Get SAP Protocol Invariants',
|
|
2124
|
+
description: 'Free read-only protocol invariant card for agents. Returns the current SAP program id, protocol treasury, source-level expected 0.1 SOL registration fee invariant, hosted write routing rules, local sap_payments routes, identity pipeline, and forbidden actions. Use this before SAP registry writes and whenever fee/treasury behavior is unclear.',
|
|
2125
|
+
inputSchema: {},
|
|
2126
|
+
handler: async () => buildSapProtocolInvariants(),
|
|
2127
|
+
},
|
|
2128
|
+
{
|
|
2129
|
+
name: 'sap_agent_identity_plan',
|
|
2130
|
+
title: 'Plan SAP Agent Identity',
|
|
2131
|
+
description: 'Free read-only planner for SAP agent registration, profile/image updates, Metaplex/MPL Core identity linking, SNS linking, x402 pricing metadata, and post-write verification. Use this before sap_payments_register_agent or sap_payments_update_agent so agents know the exact local-signer route, metadata contract, forbidden actions, and protocol fee verification checklist. It does not touch chain and never signs.',
|
|
2132
|
+
inputSchema: SAP_AGENT_IDENTITY_PLAN_INPUT_SCHEMA,
|
|
2133
|
+
handler: async (input) => buildSapAgentIdentityPlan(input),
|
|
2134
|
+
},
|
|
1029
2135
|
{
|
|
1030
2136
|
name: 'sap_register_agent',
|
|
1031
|
-
title: 'Register SAP Agent',
|
|
1032
|
-
description: '
|
|
1033
|
-
inputSchema:
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
capabilities: { type: 'array', description: 'Array of capability objects or strings identifying what the agent can do (e.g. "jupiter:swap")' },
|
|
1037
|
-
pricing: { type: 'array', description: 'Array of pricing tier objects defining per-call costs, rate limits, and settlement terms' },
|
|
1038
|
-
protocols: { type: 'array', items: { type: 'string' }, description: 'Array of protocol identifiers the agent supports (e.g. "jupiter", "drift")' },
|
|
1039
|
-
agentId: { type: 'string', description: 'Optional unique agent identifier string' },
|
|
1040
|
-
agentUri: { type: 'string', description: 'Optional URI to the agent\'s off-chain metadata or service endpoint' },
|
|
1041
|
-
metadataUri: { type: 'string', description: 'Alias for agentUri — URI to off-chain agent metadata JSON' },
|
|
1042
|
-
x402Endpoint: { type: 'string', description: 'Optional x402 payment endpoint URL for HTTP-based agent payments' },
|
|
2137
|
+
title: 'Register SAP Agent (Raw SDK Deprecated)',
|
|
2138
|
+
description: 'Deprecated raw SDK wrapper. Production registration must use sap_payments_register_agent so the local signer path confirms the account, verifies the 0.1 SOL protocol treasury fee invariant, and returns protocolComplete. Hosted accountless SAP MCP rejects this direct write before x402 payment.',
|
|
2139
|
+
inputSchema: SAP_AGENT_REGISTER_INPUT_SCHEMA,
|
|
2140
|
+
handler: async () => {
|
|
2141
|
+
throw new Error('sap_register_agent raw SDK path is disabled for production safety. Call sap_agent_identity_plan, then sap_payments_register_agent with the same registration fields and confirm: true. success is true only when the agent account exists and protocolFee.status is verified.');
|
|
1043
2142
|
},
|
|
1044
|
-
handler: async (input, client) => ({ signature: await client.agent.register(parseRegisterAgentArgs(input)) }),
|
|
1045
2143
|
},
|
|
1046
2144
|
{
|
|
1047
2145
|
name: 'sap_update_agent',
|
|
1048
2146
|
title: 'Update SAP Agent',
|
|
1049
|
-
description: '
|
|
1050
|
-
inputSchema:
|
|
2147
|
+
description: 'Local-signer-only: update the connected wallet SAP agent using SDK AgentModule.update. Hosted accountless SAP MCP rejects this direct write before x402 payment; hosted users should call sap_payments_update_agent from the local sap_payments bridge.',
|
|
2148
|
+
inputSchema: SAP_AGENT_UPDATE_INPUT_SCHEMA,
|
|
1051
2149
|
handler: async (input, client) => ({ signature: await client.agent.update(parseUpdateAgentArgs(input)) }),
|
|
1052
2150
|
},
|
|
1053
2151
|
{
|
|
@@ -1090,7 +2188,7 @@ const agentTools = [
|
|
|
1090
2188
|
{
|
|
1091
2189
|
name: 'sap_get_agent',
|
|
1092
2190
|
title: 'Get SAP Agent',
|
|
1093
|
-
description: '
|
|
2191
|
+
description: 'Free exact SAP agent identity read by owner wallet. If omitted, fetches the connected wallet agent. Use this before paid discovery when the wallet is known.',
|
|
1094
2192
|
inputSchema: { wallet: { type: 'string', description: 'Solana public key of the agent owner wallet (base58). If omitted, uses the connected wallet.' } },
|
|
1095
2193
|
handler: async (input, client) => {
|
|
1096
2194
|
const wallet = optionalPublicKey(input, 'wallet');
|
|
@@ -1102,7 +2200,7 @@ const agentTools = [
|
|
|
1102
2200
|
{
|
|
1103
2201
|
name: 'sap_get_agent_stats',
|
|
1104
2202
|
title: 'Get SAP Agent Stats',
|
|
1105
|
-
description: '
|
|
2203
|
+
description: 'Free exact SAP agent stats read by agent PDA. Use after sap_get_agent or sap_get_agent_profile when stats are needed.',
|
|
1106
2204
|
inputSchema: { agentPda: { type: 'string', description: 'Agent PDA (base58) to fetch stats for' } },
|
|
1107
2205
|
handler: async (input, client) => ({
|
|
1108
2206
|
stats: await client.agent.fetchStatsNullable(requiredPublicKey(input, 'agentPda')),
|
|
@@ -1111,7 +2209,7 @@ const agentTools = [
|
|
|
1111
2209
|
{
|
|
1112
2210
|
name: 'sap_get_global_state',
|
|
1113
2211
|
title: 'Get SAP Global State',
|
|
1114
|
-
description: '
|
|
2212
|
+
description: 'Free compact global SAP registry state read. Use for initial orientation before paid network analytics.',
|
|
1115
2213
|
inputSchema: {},
|
|
1116
2214
|
handler: async (_input, client) => ({ state: await client.agent.fetchGlobalRegistry() }),
|
|
1117
2215
|
},
|
|
@@ -1124,17 +2222,41 @@ const discoveryTools = [
|
|
|
1124
2222
|
inputSchema: {},
|
|
1125
2223
|
handler: async (_input, client) => ({ overview: await client.discovery.getNetworkOverview() }),
|
|
1126
2224
|
},
|
|
2225
|
+
{
|
|
2226
|
+
name: 'sap_agent_context',
|
|
2227
|
+
title: 'Get SAP Agent Context',
|
|
2228
|
+
description: 'Free one-shot SAP orientation context for agents. Use this when the user asks whether SAP MCP is connected, wants to understand an agent/wallet, or needs the next safe paid/write route. It combines exact reads or a compact directory page with routing guidance without triggering x402.',
|
|
2229
|
+
inputSchema: {
|
|
2230
|
+
wallet: {
|
|
2231
|
+
type: 'string',
|
|
2232
|
+
description: 'Optional exact owner wallet public key (base58). When supplied, returns the matching agent identity, PDA, active state, and hydrated profile when available.',
|
|
2233
|
+
},
|
|
2234
|
+
agentPda: {
|
|
2235
|
+
type: 'string',
|
|
2236
|
+
description: 'Optional exact SAP agent PDA (base58) for compact context lookup when the owner wallet is not known.',
|
|
2237
|
+
},
|
|
2238
|
+
query: {
|
|
2239
|
+
type: 'string',
|
|
2240
|
+
description: 'Optional text query for a free compact orientation search. Keep this narrow, for example "XONA" or "Solking".',
|
|
2241
|
+
},
|
|
2242
|
+
limit: {
|
|
2243
|
+
type: 'number',
|
|
2244
|
+
description: 'Maximum compact orientation rows to include when wallet is not supplied. Defaults to 10 and is capped at 20 to keep this tool free.',
|
|
2245
|
+
},
|
|
2246
|
+
},
|
|
2247
|
+
handler: async (input, client) => buildSapAgentContext(input, client),
|
|
2248
|
+
},
|
|
1127
2249
|
{
|
|
1128
2250
|
name: 'sap_get_agent_profile',
|
|
1129
2251
|
title: 'Get SAP Agent Profile',
|
|
1130
|
-
description: '
|
|
2252
|
+
description: 'Free exact SAP agent profile read by owner wallet. Use this for a known agent before paid discovery or enrichment.',
|
|
1131
2253
|
inputSchema: { wallet: { type: 'string', description: 'Solana public key of the agent owner wallet (base58)' } },
|
|
1132
2254
|
handler: async (input, client) => ({ profile: await client.discovery.getAgentProfile(requiredPublicKey(input, 'wallet')) }),
|
|
1133
2255
|
},
|
|
1134
2256
|
{
|
|
1135
2257
|
name: 'sap_is_agent_active',
|
|
1136
2258
|
title: 'Check SAP Agent Active',
|
|
1137
|
-
description: '
|
|
2259
|
+
description: 'Free exact activity check for an owner wallet. Use this before paid discovery when the wallet is known.',
|
|
1138
2260
|
inputSchema: { wallet: { type: 'string', description: 'Solana public key of the wallet to check for an active SAP agent (base58)' } },
|
|
1139
2261
|
handler: async (input, client) => ({ active: await client.discovery.isAgentActive(requiredPublicKey(input, 'wallet')) }),
|
|
1140
2262
|
},
|
|
@@ -1147,7 +2269,7 @@ const discoveryTools = [
|
|
|
1147
2269
|
const limit = Math.max(1, Math.min(optionalNumber(input, 'limit') ?? 50, 500));
|
|
1148
2270
|
const capability = optionalString(input, 'capability');
|
|
1149
2271
|
const capabilities = parseOptionalStringArray(input, 'capabilities');
|
|
1150
|
-
return
|
|
2272
|
+
return getCachedAgentDirectoryPage(client, {
|
|
1151
2273
|
includeInactive: input.includeInactive === true,
|
|
1152
2274
|
protocol: optionalString(input, 'protocol'),
|
|
1153
2275
|
capability,
|
|
@@ -1167,9 +2289,27 @@ const discoveryTools = [
|
|
|
1167
2289
|
{
|
|
1168
2290
|
name: 'sap_list_agents',
|
|
1169
2291
|
title: 'List SAP Agents',
|
|
1170
|
-
description: '
|
|
1171
|
-
inputSchema: makeAgentDirectoryInputSchema(
|
|
1172
|
-
handler: async (input, client) =>
|
|
2292
|
+
description: 'Compact SAP agent orientation list. Free only when limit <= 20, view is compact, hydrate is false, and includeProtocolIndexes is false; larger or enriched pages are paid read-premium.',
|
|
2293
|
+
inputSchema: makeAgentDirectoryInputSchema(20),
|
|
2294
|
+
handler: async (input, client) => {
|
|
2295
|
+
const limit = Math.max(1, Math.min(optionalNumber(input, 'limit') ?? 20, 500));
|
|
2296
|
+
const capability = optionalString(input, 'capability');
|
|
2297
|
+
return getCachedAgentDirectoryPage(client, {
|
|
2298
|
+
includeInactive: input.includeInactive === true,
|
|
2299
|
+
protocol: optionalString(input, 'protocol'),
|
|
2300
|
+
capability,
|
|
2301
|
+
capabilities: parseOptionalStringArray(input, 'capabilities'),
|
|
2302
|
+
capabilityMode: parseCapabilityMode(input),
|
|
2303
|
+
query: optionalString(input, 'query'),
|
|
2304
|
+
wallet: optionalString(input, 'wallet'),
|
|
2305
|
+
agentPda: optionalString(input, 'agentPda'),
|
|
2306
|
+
hasX402Endpoint: parseHasX402Endpoint(input),
|
|
2307
|
+
limit,
|
|
2308
|
+
offset: parseDirectoryOffset(input),
|
|
2309
|
+
view: parseDirectoryView(input),
|
|
2310
|
+
includeProtocolIndexes: input.includeProtocolIndexes === true,
|
|
2311
|
+
});
|
|
2312
|
+
},
|
|
1173
2313
|
},
|
|
1174
2314
|
{
|
|
1175
2315
|
name: 'sap_list_all_agents',
|
|
@@ -1179,7 +2319,7 @@ const discoveryTools = [
|
|
|
1179
2319
|
handler: async (input, client) => {
|
|
1180
2320
|
const limit = Math.max(1, Math.min(optionalNumber(input, 'limit') ?? 100, 500));
|
|
1181
2321
|
const capability = optionalString(input, 'capability');
|
|
1182
|
-
return
|
|
2322
|
+
return getCachedAgentDirectoryPage(client, {
|
|
1183
2323
|
includeInactive: input.includeInactive === true,
|
|
1184
2324
|
protocol: optionalString(input, 'protocol'),
|
|
1185
2325
|
capability,
|
|
@@ -1457,24 +2597,66 @@ const paymentAndEscrowTools = [
|
|
|
1457
2597
|
},
|
|
1458
2598
|
];
|
|
1459
2599
|
const escrowV2Tools = [
|
|
2600
|
+
{
|
|
2601
|
+
name: 'sap_escrow_build_create_transaction',
|
|
2602
|
+
title: 'Build SAP Escrow V2 Create Transaction',
|
|
2603
|
+
description: 'Hosted-safe unsigned builder for create_escrow_v2. Use this from hosted SAP MCP, preview the result, then call local sap_payments_finalize_transaction with submit:true. The depositorWallet signs locally; keypair bytes never leave the user machine. Defaults to DisputeWindow settlementSecurity=2 and rejects SelfReport/0.',
|
|
2604
|
+
inputSchema: escrowV2CreateBuilderInputSchema,
|
|
2605
|
+
handler: buildEscrowCreateTransaction,
|
|
2606
|
+
},
|
|
2607
|
+
{
|
|
2608
|
+
name: 'sap_escrow_build_deposit_transaction',
|
|
2609
|
+
title: 'Build SAP Escrow V2 Deposit Transaction',
|
|
2610
|
+
description: 'Hosted-safe unsigned builder for deposit_escrow_v2. Use this when a hosted workflow needs to add funds to an existing V2 escrow, then finalize locally with sap_payments_finalize_transaction.',
|
|
2611
|
+
inputSchema: escrowV2DepositBuilderInputSchema,
|
|
2612
|
+
handler: buildEscrowDepositTransaction,
|
|
2613
|
+
},
|
|
2614
|
+
{
|
|
2615
|
+
name: 'sap_escrow_build_settle_transaction',
|
|
2616
|
+
title: 'Build SAP Escrow V2 Settlement Transaction',
|
|
2617
|
+
description: 'Hosted-safe unsigned builder for settle_calls_v2. The agent owner wallet signs locally. DisputeWindow escrows create a pending settlement PDA; CoSigned escrows require the coSigner account when configured.',
|
|
2618
|
+
inputSchema: escrowV2SettleBuilderInputSchema,
|
|
2619
|
+
handler: buildEscrowSettleTransaction,
|
|
2620
|
+
},
|
|
2621
|
+
{
|
|
2622
|
+
name: 'sap_escrow_build_finalize_transaction',
|
|
2623
|
+
title: 'Build SAP Escrow V2 Finalize Transaction',
|
|
2624
|
+
description: 'Hosted-safe unsigned builder for finalize_settlement. Use after the dispute window has elapsed and a pending settlement exists; any payerWallet can locally sign to crank finalization.',
|
|
2625
|
+
inputSchema: escrowV2FinalizeBuilderInputSchema,
|
|
2626
|
+
handler: buildEscrowFinalizeTransaction,
|
|
2627
|
+
},
|
|
2628
|
+
{
|
|
2629
|
+
name: 'sap_escrow_build_withdraw_transaction',
|
|
2630
|
+
title: 'Build SAP Escrow V2 Withdraw Transaction',
|
|
2631
|
+
description: 'Hosted-safe unsigned builder for withdraw_escrow_v2. The depositor signs locally and can only withdraw unlocked balance after pending amounts are excluded.',
|
|
2632
|
+
inputSchema: escrowV2WithdrawBuilderInputSchema,
|
|
2633
|
+
handler: buildEscrowWithdrawTransaction,
|
|
2634
|
+
},
|
|
2635
|
+
{
|
|
2636
|
+
name: 'sap_escrow_build_close_transaction',
|
|
2637
|
+
title: 'Build SAP Escrow V2 Close Transaction',
|
|
2638
|
+
description: 'Hosted-safe unsigned builder for close_escrow_v2. The depositor signs locally; the builder refuses to close until balance and pendingAmount are both zero.',
|
|
2639
|
+
inputSchema: escrowV2CloseBuilderInputSchema,
|
|
2640
|
+
handler: buildEscrowCloseTransaction,
|
|
2641
|
+
},
|
|
1460
2642
|
{
|
|
1461
2643
|
name: 'sap_create_escrow_v2',
|
|
1462
2644
|
title: 'Create SAP Escrow V2',
|
|
1463
|
-
description: '
|
|
2645
|
+
description: 'Local-signer-only direct V2 escrow creation using SDK EscrowV2Module.create. Defaults to DisputeWindow settlementSecurity=2; SelfReport/0 is rejected. Hosted accountless SAP MCP rejects this before x402 payment; hosted users should call sap_escrow_build_create_transaction and finalize locally.',
|
|
1464
2646
|
inputSchema: escrowV2CreateInputSchema,
|
|
1465
2647
|
handler: async (input, client) => ({ signature: await client.escrowV2.create(requiredPublicKey(input, 'agentWallet'), parseEscrowV2Args(input)) }),
|
|
1466
2648
|
},
|
|
1467
2649
|
{
|
|
1468
2650
|
name: 'sap_deposit_escrow_v2',
|
|
1469
2651
|
title: 'Deposit SAP Escrow V2',
|
|
1470
|
-
description: '
|
|
2652
|
+
description: 'Local-signer-only direct deposit into a V2 escrow. Hosted users should call sap_escrow_build_deposit_transaction and finalize locally.',
|
|
1471
2653
|
inputSchema: { agentWallet: { type: 'string', description: 'Agent wallet public key (base58)' }, nonce: { type: 'string', description: 'Escrow nonce (as a decimal string, default: 0)' }, amount: { type: 'string', description: `Deposit amount as a decimal string. ${ESCROW_AMOUNT_DESCRIPTION}` } },
|
|
1472
2654
|
handler: async (input, client) => ({ signature: await client.escrowV2.deposit(requiredPublicKey(input, 'agentWallet'), optionalBn(input, 'nonce', new BN(0)), requiredBn(input, 'amount')) }),
|
|
1473
2655
|
},
|
|
1474
2656
|
{
|
|
1475
2657
|
name: 'sap_settle_escrow_v2',
|
|
1476
2658
|
title: 'Settle SAP Escrow V2',
|
|
1477
|
-
description: '
|
|
2659
|
+
description: 'Local-signer-only direct settlement against a V2 escrow. Hosted users should call sap_escrow_build_settle_transaction and finalize locally.',
|
|
1478
2660
|
inputSchema: { depositorWallet: { type: 'string', description: 'Depositor wallet public key (base58)' }, nonce: { type: 'string', description: 'Escrow nonce (as a decimal string, default: 0)' }, callsToSettle: { type: 'string', description: 'Number of calls to settle (as a decimal string)' }, serviceHash: { type: 'array', description: '32-byte service hash as a byte array, hex string, or base64 string' } },
|
|
1479
2661
|
handler: async (input, client) => ({
|
|
1480
2662
|
signature: await client.escrowV2.settle(requiredPublicKey(input, 'depositorWallet'), optionalBn(input, 'nonce', new BN(0)), requiredBn(input, 'callsToSettle'), requiredBytes(input, 'serviceHash', 32)),
|
|
@@ -1492,7 +2674,7 @@ const escrowV2Tools = [
|
|
|
1492
2674
|
{
|
|
1493
2675
|
name: 'sap_finalize_settlement_v2',
|
|
1494
2676
|
title: 'Finalize SAP Escrow V2 Settlement',
|
|
1495
|
-
description: '
|
|
2677
|
+
description: 'Local-signer-only direct finalization of a V2 pending settlement. Hosted users should call sap_escrow_build_finalize_transaction and finalize locally.',
|
|
1496
2678
|
inputSchema: { agentWallet: { type: 'string', description: 'Agent wallet public key (base58)' }, depositorWallet: { type: 'string', description: 'Depositor wallet public key (base58)' }, nonce: { type: 'string', description: 'Escrow nonce (as a decimal string, default: 0)' }, settlementIndex: { type: 'string', description: 'Settlement index to finalize (as a decimal string)' } },
|
|
1497
2679
|
handler: async (input, client) => ({
|
|
1498
2680
|
signature: await client.escrowV2.finalizeSettlement(requiredPublicKey(input, 'agentWallet'), requiredPublicKey(input, 'depositorWallet'), optionalBn(input, 'nonce', new BN(0)), requiredBn(input, 'settlementIndex')),
|
|
@@ -1510,14 +2692,14 @@ const escrowV2Tools = [
|
|
|
1510
2692
|
{
|
|
1511
2693
|
name: 'sap_withdraw_escrow_v2',
|
|
1512
2694
|
title: 'Withdraw SAP Escrow V2',
|
|
1513
|
-
description: '
|
|
2695
|
+
description: 'Local-signer-only direct withdrawal from a V2 escrow. Hosted users should call sap_escrow_build_withdraw_transaction and finalize locally.',
|
|
1514
2696
|
inputSchema: { agentWallet: { type: 'string', description: 'Agent wallet public key (base58)' }, nonce: { type: 'string', description: 'Escrow nonce (as a decimal string, default: 0)' }, amount: { type: 'string', description: `Withdrawal amount as a decimal string. ${ESCROW_AMOUNT_DESCRIPTION}` } },
|
|
1515
2697
|
handler: async (input, client) => ({ signature: await client.escrowV2.withdraw(requiredPublicKey(input, 'agentWallet'), optionalBn(input, 'nonce', new BN(0)), requiredBn(input, 'amount')) }),
|
|
1516
2698
|
},
|
|
1517
2699
|
{
|
|
1518
2700
|
name: 'sap_close_escrow_v2',
|
|
1519
2701
|
title: 'Close SAP Escrow V2',
|
|
1520
|
-
description: '
|
|
2702
|
+
description: 'Local-signer-only direct close for an empty V2 escrow. Hosted users should call sap_escrow_build_close_transaction and finalize locally.',
|
|
1521
2703
|
inputSchema: { agentWallet: { type: 'string', description: 'Agent wallet public key (base58)' }, nonce: { type: 'string', description: 'Escrow nonce (as a decimal string, default: 0)' } },
|
|
1522
2704
|
handler: async (input, client) => ({ signature: await client.escrowV2.close(requiredPublicKey(input, 'agentWallet'), optionalBn(input, 'nonce', new BN(0))) }),
|
|
1523
2705
|
},
|