@oobe-protocol-labs/sap-mcp-server 0.9.13 → 0.9.15

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.
Files changed (84) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/README.md +2 -2
  3. package/USER_DOCS/00_START_HERE.md +24 -2
  4. package/USER_DOCS/03_PAYMENTS_X402_PAYSH.md +6 -1
  5. package/USER_DOCS/04_CLIENT_CONFIGS.md +4 -3
  6. package/USER_DOCS/05_SKILLS_AND_TOOLS.md +9 -4
  7. package/USER_DOCS/06_DESKTOP_GUI_WIZARD.md +6 -6
  8. package/dist/config/mcp-client-injection.d.ts.map +1 -1
  9. package/dist/config/mcp-client-injection.js +4 -3
  10. package/dist/config/mcp-client-injection.js.map +1 -1
  11. package/dist/core/constants.d.ts +9 -1
  12. package/dist/core/constants.d.ts.map +1 -1
  13. package/dist/core/constants.js +13 -3
  14. package/dist/core/constants.js.map +1 -1
  15. package/dist/payments/hosted-tool-eligibility.d.ts.map +1 -1
  16. package/dist/payments/hosted-tool-eligibility.js +3 -0
  17. package/dist/payments/hosted-tool-eligibility.js.map +1 -1
  18. package/dist/payments/index.d.ts +2 -2
  19. package/dist/payments/index.d.ts.map +1 -1
  20. package/dist/payments/index.js +1 -1
  21. package/dist/payments/index.js.map +1 -1
  22. package/dist/payments/pricing.d.ts +29 -0
  23. package/dist/payments/pricing.d.ts.map +1 -1
  24. package/dist/payments/pricing.js +81 -0
  25. package/dist/payments/pricing.js.map +1 -1
  26. package/dist/prompts/context/sap-agent-context.prompt.js +2 -0
  27. package/dist/prompts/context/sap-agent-context.prompt.js.map +1 -1
  28. package/dist/prompts/context/sap-agent-start.prompt.js +2 -0
  29. package/dist/prompts/context/sap-agent-start.prompt.js.map +1 -1
  30. package/dist/prompts/registry/register-sap-agent.prompt.js +18 -18
  31. package/dist/prompts/registry/register-sap-agent.prompt.js.map +1 -1
  32. package/dist/remote/public-home/sections.d.ts.map +1 -1
  33. package/dist/remote/public-home/sections.js +4 -0
  34. package/dist/remote/public-home/sections.js.map +1 -1
  35. package/dist/remote/public-home/types.d.ts +1 -0
  36. package/dist/remote/public-home/types.d.ts.map +1 -1
  37. package/dist/remote/server.d.ts +1 -0
  38. package/dist/remote/server.d.ts.map +1 -1
  39. package/dist/remote/server.js +10 -2
  40. package/dist/remote/server.js.map +1 -1
  41. package/dist/security/tool-permissions.d.ts.map +1 -1
  42. package/dist/security/tool-permissions.js +9 -0
  43. package/dist/security/tool-permissions.js.map +1 -1
  44. package/dist/tools/agent-start-tool.d.ts.map +1 -1
  45. package/dist/tools/agent-start-tool.js +222 -1
  46. package/dist/tools/agent-start-tool.js.map +1 -1
  47. package/dist/tools/client-sdk-tools.js +3 -3
  48. package/dist/tools/client-sdk-tools.js.map +1 -1
  49. package/dist/tools/magicblock-tools.d.ts.map +1 -1
  50. package/dist/tools/magicblock-tools.js +43 -26
  51. package/dist/tools/magicblock-tools.js.map +1 -1
  52. package/dist/tools/sap-sdk-tools.d.ts +412 -1
  53. package/dist/tools/sap-sdk-tools.d.ts.map +1 -1
  54. package/dist/tools/sap-sdk-tools.js +1044 -30
  55. package/dist/tools/sap-sdk-tools.js.map +1 -1
  56. package/dist/tools/sap-sns-tools.d.ts.map +1 -1
  57. package/dist/tools/sap-sns-tools.js +6 -2
  58. package/dist/tools/sap-sns-tools.js.map +1 -1
  59. package/dist/tools/skills-tools.d.ts.map +1 -1
  60. package/dist/tools/skills-tools.js +2 -0
  61. package/dist/tools/skills-tools.js.map +1 -1
  62. package/dist/tools/tool-aliases.d.ts.map +1 -1
  63. package/dist/tools/tool-aliases.js +7 -0
  64. package/dist/tools/tool-aliases.js.map +1 -1
  65. package/dist/tools/x402-paid-call-tool.d.ts.map +1 -1
  66. package/dist/tools/x402-paid-call-tool.js +315 -15
  67. package/dist/tools/x402-paid-call-tool.js.map +1 -1
  68. package/docs/03_CONFIGURATION_AND_WIZARD.md +1 -0
  69. package/docs/06_PAYMENTS_X402_AND_PAYSH.md +38 -1
  70. package/docs/07_ENDPOINTS_AND_CLIENTS.md +2 -1
  71. package/docs/14_DESKTOP_WIZARD_RELEASE.md +5 -4
  72. package/docs/16_SAP_AGENT_IDENTITY_PIPELINE.md +298 -0
  73. package/docs/README.md +2 -0
  74. package/docs/magicblock-tools.md +26 -0
  75. package/package.json +1 -1
  76. package/server.json +3 -3
  77. package/skills/sap-agent-registry/SKILL.md +63 -5
  78. package/skills/sap-escrow-settlement/SKILL.md +22 -4
  79. package/skills/sap-mcp/SKILL.md +73 -22
  80. package/skills/sap-mcp/TOOL_REFERENCE.md +22 -1
  81. package/skills/sap-nft-metaplex/SKILL.md +22 -0
  82. package/skills/sap-operations/SKILL.md +17 -0
  83. package/skills/sap-payments-x402/SKILL.md +27 -0
  84. 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,121 @@ 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
+ };
71
400
  /**
72
401
  * @name jsonReplacer
73
402
  * @description Serializes SDK values such as PublicKey, BN, bigint, Buffer, and Uint8Array into JSON-safe output.
@@ -594,6 +923,45 @@ function parseVolumeCurve(value) {
594
923
  };
595
924
  });
596
925
  }
926
+ /**
927
+ * @name optionalTokenType
928
+ * @description Converts public tokenType strings into SDK Anchor enum variants.
929
+ */
930
+ function optionalTokenType(input) {
931
+ const raw = optionalString(input, 'tokenType') ?? 'sol';
932
+ const normalized = raw.trim().toLowerCase();
933
+ if (normalized === 'sol' || normalized === 'native' || normalized === 'lamports') {
934
+ return { tokenType: TokenType.Sol, decimalsFallback: 9 };
935
+ }
936
+ if (normalized === 'usdc' || normalized === 'micro-usdc' || normalized === 'micro_usdc') {
937
+ return { tokenType: TokenType.Usdc, decimalsFallback: 6 };
938
+ }
939
+ if (normalized === 'spl' || normalized === 'token') {
940
+ return { tokenType: TokenType.Spl, decimalsFallback: 0 };
941
+ }
942
+ throw new Error('tokenType must be one of sol, usdc, or spl');
943
+ }
944
+ /**
945
+ * @name optionalSettlementMode
946
+ * @description Converts public settlementMode strings into SDK Anchor enum variants.
947
+ */
948
+ function optionalSettlementMode(input) {
949
+ const raw = optionalString(input, 'settlementMode') ?? 'escrow';
950
+ const normalized = raw.trim().toLowerCase();
951
+ if (normalized === 'instant') {
952
+ return SettlementMode.Instant;
953
+ }
954
+ if (normalized === 'escrow') {
955
+ return SettlementMode.Escrow;
956
+ }
957
+ if (normalized === 'batched' || normalized === 'batch') {
958
+ return SettlementMode.Batched;
959
+ }
960
+ if (normalized === 'x402' || normalized === 'pay.sh' || normalized === 'paysh') {
961
+ return SettlementMode.X402;
962
+ }
963
+ throw new Error('settlementMode must be one of instant, escrow, batched, or x402');
964
+ }
597
965
  /**
598
966
  * @name requiredBytes
599
967
  * @description Reads byte-array fields used for hashes, nonces, and encrypted payloads.
@@ -700,6 +1068,12 @@ function parsePricingTiers(value) {
700
1068
  return value.map((item) => {
701
1069
  const record = asRecord(item);
702
1070
  const pricePerCall = optionalBn(record, 'pricePerCall', new BN(0));
1071
+ const { tokenType, decimalsFallback } = optionalTokenType(record);
1072
+ const tokenMint = optionalPublicKey(record, 'tokenMint') ?? null;
1073
+ if (tokenType === TokenType.Spl && tokenMint === null) {
1074
+ throw new Error('pricing.tokenMint is required when tokenType is spl');
1075
+ }
1076
+ const volumeCurve = parseVolumeCurve(record.volumeCurve);
703
1077
  return {
704
1078
  tierId: optionalString(record, 'tierId') ?? 'default',
705
1079
  pricePerCall,
@@ -708,13 +1082,13 @@ function parsePricingTiers(value) {
708
1082
  rateLimit: optionalNumber(record, 'rateLimit') ?? 60,
709
1083
  maxCallsPerSession: optionalNumber(record, 'maxCallsPerSession') ?? 1_000,
710
1084
  burstLimit: optionalNumber(record, 'burstLimit') ?? null,
711
- tokenType: TokenType.Sol,
712
- tokenMint: optionalPublicKey(record, 'tokenMint') ?? null,
713
- tokenDecimals: optionalNumber(record, 'tokenDecimals') ?? null,
714
- settlementMode: SettlementMode.Escrow,
1085
+ tokenType,
1086
+ tokenMint,
1087
+ tokenDecimals: optionalNumber(record, 'tokenDecimals') ?? decimalsFallback,
1088
+ settlementMode: optionalSettlementMode(record),
715
1089
  minEscrowDeposit: record.minEscrowDeposit === undefined ? null : requiredBn(record, 'minEscrowDeposit'),
716
1090
  batchIntervalSec: optionalNumber(record, 'batchIntervalSec') ?? null,
717
- volumeCurve: null,
1091
+ volumeCurve: volumeCurve.length > 0 ? volumeCurve : null,
718
1092
  };
719
1093
  });
720
1094
  }
@@ -756,7 +1130,7 @@ export function parseRegisterAgentArgs(input) {
756
1130
  * @name parseUpdateAgentArgs
757
1131
  * @description Builds strongly typed `UpdateAgentArgs` from MCP JSON input.
758
1132
  */
759
- function parseUpdateAgentArgs(input) {
1133
+ export function parseUpdateAgentArgs(input) {
760
1134
  return {
761
1135
  name: optionalString(input, 'name') ?? null,
762
1136
  description: optionalString(input, 'description') ?? null,
@@ -768,6 +1142,168 @@ function parseUpdateAgentArgs(input) {
768
1142
  x402Endpoint: optionalString(input, 'x402Endpoint') ?? null,
769
1143
  };
770
1144
  }
1145
+ function parseIdentityPlanAction(input) {
1146
+ const value = optionalString(input, 'intendedAction') ?? 'full-identity';
1147
+ if (['register', 'update', 'link-metaplex', 'link-sns', 'full-identity'].includes(value)) {
1148
+ return value;
1149
+ }
1150
+ throw new Error('intendedAction must be one of register, update, link-metaplex, link-sns, full-identity');
1151
+ }
1152
+ function optionalPublicMetadataUri(input) {
1153
+ return optionalString(input, 'metadataUri') ?? optionalString(input, 'agentUri') ?? null;
1154
+ }
1155
+ function buildSapAgentIdentityPlan(input) {
1156
+ const intendedAction = parseIdentityPlanAction(input);
1157
+ const metadataUri = optionalPublicMetadataUri(input);
1158
+ const imageUrl = optionalString(input, 'imageUrl') ?? null;
1159
+ const metaplexAsset = optionalString(input, 'metaplexAsset') ?? null;
1160
+ const snsDomain = optionalString(input, 'snsDomain') ?? null;
1161
+ const ownerWallet = optionalString(input, 'ownerWallet') ?? null;
1162
+ const missingRegisterFields = ['name', 'description', 'capabilities', 'protocols']
1163
+ .filter((field) => input[field] === undefined || input[field] === null);
1164
+ const hasCompleteRegisterFields = missingRegisterFields.length === 0;
1165
+ const registerArgs = hasCompleteRegisterFields
1166
+ ? parseRegisterAgentArgs({
1167
+ ...input,
1168
+ capabilities: input.capabilities ?? [],
1169
+ pricing: input.pricing ?? [],
1170
+ protocols: input.protocols ?? [],
1171
+ })
1172
+ : null;
1173
+ const updateArgs = parseUpdateAgentArgs(input);
1174
+ return {
1175
+ intendedAction,
1176
+ trustBoundary: {
1177
+ hostedSap: 'Use hosted sap for reads, paid hosted tools, and unsigned builders.',
1178
+ localSapPayments: 'Use local sap_payments for x402 payment signing, SAP registry writes, and transaction finalization.',
1179
+ keypairMaterial: 'Never read or export keypair JSON. Only local SAP MCP signer tools may sign.',
1180
+ },
1181
+ nextTools: {
1182
+ readiness: 'sap_payments_readiness',
1183
+ register: intendedAction === 'register' || intendedAction === 'full-identity' ? 'sap_payments_register_agent' : null,
1184
+ update: intendedAction !== 'register' ? 'sap_payments_update_agent' : null,
1185
+ metaplex: metaplexAsset ? 'metaplex-nft_* or DAS verification tools before SAP metadata update' : null,
1186
+ sns: snsDomain ? ['sap_sns_check_domain', 'sap_sns_check_ownership', 'sap_sns_build_manage_record_transaction when managing records'] : null,
1187
+ profileVerification: 'sap_get_agent_profile',
1188
+ },
1189
+ missingRegisterFields,
1190
+ normalizedRegistration: registerArgs
1191
+ ? {
1192
+ name: registerArgs.name,
1193
+ description: registerArgs.description,
1194
+ agentId: registerArgs.agentId,
1195
+ agentUri: registerArgs.agentUri,
1196
+ x402Endpoint: registerArgs.x402Endpoint,
1197
+ protocols: registerArgs.protocols,
1198
+ capabilities: registerArgs.capabilities,
1199
+ pricing: registerArgs.pricing,
1200
+ confirm: true,
1201
+ }
1202
+ : null,
1203
+ normalizedUpdate: {
1204
+ name: updateArgs.name,
1205
+ description: updateArgs.description,
1206
+ agentId: updateArgs.agentId,
1207
+ agentUri: updateArgs.agentUri,
1208
+ x402Endpoint: updateArgs.x402Endpoint,
1209
+ protocols: updateArgs.protocols,
1210
+ capabilities: updateArgs.capabilities,
1211
+ pricing: updateArgs.pricing,
1212
+ confirm: true,
1213
+ },
1214
+ publicMetadataContract: {
1215
+ requiredForImages: true,
1216
+ metadataUri,
1217
+ imageUrl,
1218
+ metaplexAsset,
1219
+ snsDomain,
1220
+ recommendedShape: {
1221
+ name: input.name ?? '<agent name>',
1222
+ description: input.description ?? '<agent description>',
1223
+ image: imageUrl ?? '<public image URL>',
1224
+ external_url: ownerWallet ? `https://explorer.oobeprotocol.ai/agents/${ownerWallet}` : '<public agent page>',
1225
+ attributes: [
1226
+ { trait_type: 'Protocol', value: 'SAP' },
1227
+ { trait_type: 'Identity', value: [metaplexAsset ? 'Metaplex' : null, snsDomain ? 'SNS' : null].filter(Boolean).join(' + ') || 'SAP' },
1228
+ ],
1229
+ sap: {
1230
+ ownerWallet: ownerWallet ?? '<owner wallet>',
1231
+ agentId: optionalString(input, 'agentId') ?? '<stable id>',
1232
+ capabilities: Array.isArray(input.capabilities) ? input.capabilities : [],
1233
+ },
1234
+ metaplex: metaplexAsset ? { asset: metaplexAsset } : null,
1235
+ sns: snsDomain ? { domain: snsDomain } : null,
1236
+ x402: optionalString(input, 'x402Endpoint') ? { endpoint: optionalString(input, 'x402Endpoint') } : null,
1237
+ },
1238
+ },
1239
+ verificationChecklist: [
1240
+ 'Call sap_payments_readiness before paid/write actions.',
1241
+ 'For registration, call sap_payments_register_agent with confirm:true; do not call hosted sap_register_agent after hosted_local_signer_required.',
1242
+ 'Verify sap_payments_register_agent success, confirmationStatus, agentPda, and protocolFee.status.',
1243
+ 'Fetch sap_get_agent_profile by owner wallet after registration or update.',
1244
+ 'For update, verify every intended replacement field because arrays are full replacements.',
1245
+ 'For Metaplex identity, verify the asset/collection metadata URI and update authority before linking it from SAP metadata.',
1246
+ 'For SNS identity, verify domain ownership and records before claiming the .sol identity is linked.',
1247
+ ],
1248
+ forbiddenActions: [
1249
+ 'Do not create temporary signing scripts.',
1250
+ 'Do not read keypair JSON.',
1251
+ 'Do not reuse stale x402 payment signatures.',
1252
+ 'Do not pay hosted x402 fees for hosted_local_signer_required registry writes.',
1253
+ 'Do not announce lifecycle complete if protocolFee.status is missing_or_underpaid.',
1254
+ ],
1255
+ };
1256
+ }
1257
+ function buildSapProtocolInvariants() {
1258
+ return {
1259
+ protocol: {
1260
+ name: 'Synapse Agent Protocol',
1261
+ programId: DEFAULT_SAP_PROGRAM_ID,
1262
+ network: 'mainnet-beta',
1263
+ custodyModel: 'non-custodial',
1264
+ },
1265
+ registrationFee: {
1266
+ sourceExpected: true,
1267
+ treasury: SAP_PROTOCOL_TREASURY,
1268
+ lamports: SAP_REGISTRATION_FEE_LAMPORTS.toString(10),
1269
+ sol: Number(SAP_REGISTRATION_FEE_LAMPORTS) / 1_000_000_000,
1270
+ 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.',
1271
+ failureStatus: 'missing_or_underpaid',
1272
+ 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.',
1273
+ },
1274
+ hostedWritePolicy: {
1275
+ register: 'Hosted sap_register_agent is accountless and returns hosted_local_signer_required before x402 payment.',
1276
+ update: 'Hosted sap_update_agent is accountless and returns hosted_local_signer_required before x402 payment.',
1277
+ sns: 'Hosted direct SNS writes require a local signer or unsigned builder. If no builder exists, stop and route through local SAP MCP.',
1278
+ noChargeRule: 'hosted_local_signer_required is a routing guard, not a paid failure; no hosted x402 fee should be charged for that blocked write.',
1279
+ },
1280
+ localSignerRoutes: {
1281
+ readiness: 'sap_payments_readiness',
1282
+ registerAgent: 'sap_payments_register_agent',
1283
+ updateAgent: 'sap_payments_update_agent',
1284
+ finalizeUnsignedTransaction: 'sap_payments_finalize_transaction',
1285
+ paidHostedTool: 'sap_payments_call_paid_tool',
1286
+ externalX402Http: 'sap_payments_call_external_x402',
1287
+ },
1288
+ identityPipeline: [
1289
+ 'Call sap_agent_identity_plan for register, update, Metaplex, SNS, or full-identity intent.',
1290
+ 'Upload image and metadata to a public URL before writing agentUri or metadataUri.',
1291
+ 'Use sap_payments_register_agent for local non-custodial registration.',
1292
+ 'Verify success, agentRegistered, confirmationStatus, agentPda, protocolComplete, and protocolFee.status.',
1293
+ 'Use Metaplex/MPL Core tools only after metadata authority and URI are clear.',
1294
+ 'Use SNS tools only after domain availability/ownership and user confirmation are clear.',
1295
+ 'Use sap_payments_update_agent for profile image, metadata, capabilities, pricing, protocols, or x402 endpoint updates.',
1296
+ 'Fetch sap_get_agent_profile after every registry write and compare intended fields.',
1297
+ ],
1298
+ forbiddenActions: [
1299
+ 'Do not create temporary signing scripts.',
1300
+ 'Do not read keypair JSON.',
1301
+ 'Do not call hosted sap_sign_transaction for user-owned signatures.',
1302
+ 'Do not call hosted sap_register_agent or sap_update_agent again after hosted_local_signer_required.',
1303
+ 'Do not call an agent lifecycle complete when protocolFee.status is missing_or_underpaid, unavailable, or anything other than verified.',
1304
+ ],
1305
+ };
1306
+ }
771
1307
  /**
772
1308
  * @name parseX402PreparePaymentOptions
773
1309
  * @description Builds typed x402 payment preparation options from MCP JSON input.
@@ -885,6 +1421,433 @@ function parseEscrowV2Args(input) {
885
1421
  arbiter: optionalPublicKey(input, 'arbiter') ?? null,
886
1422
  };
887
1423
  }
1424
+ function publicKeyOrNull(value) {
1425
+ return value instanceof PublicKey ? value : null;
1426
+ }
1427
+ function bnToBigInt(value) {
1428
+ if (typeof value === 'bigint') {
1429
+ return value;
1430
+ }
1431
+ if (typeof value === 'number') {
1432
+ return BigInt(value);
1433
+ }
1434
+ return BigInt(value.toString());
1435
+ }
1436
+ function bnToPdaSeed(value, field) {
1437
+ const asBigInt = BigInt(value.toString(10));
1438
+ if (asBigInt < 0n) {
1439
+ throw new Error(`${field} must be >= 0`);
1440
+ }
1441
+ return asBigInt;
1442
+ }
1443
+ function serializeAccountMetas(accounts) {
1444
+ return accounts.map((account) => ({
1445
+ pubkey: account.pubkey.toBase58(),
1446
+ isSigner: account.isSigner,
1447
+ isWritable: account.isWritable,
1448
+ }));
1449
+ }
1450
+ function validateEscrowPaymentMint(tokenMint) {
1451
+ if (!tokenMint) {
1452
+ return;
1453
+ }
1454
+ if (!tokenMint.equals(USDC_MINT_MAINNET) && !tokenMint.equals(USDC_MINT_DEVNET)) {
1455
+ throw new Error('Escrow V2 hosted builders only accept native SOL or USDC mints supported by the current SAP program.');
1456
+ }
1457
+ }
1458
+ function deriveAssociatedTokenAddress(tokenMint, owner) {
1459
+ const [address] = PublicKey.findProgramAddressSync([owner.toBuffer(), TOKEN_PROGRAM_ID.toBuffer(), tokenMint.toBuffer()], ASSOCIATED_TOKEN_PROGRAM_ID);
1460
+ return address;
1461
+ }
1462
+ function getEscrowPdas(client, agentWallet, depositorWallet, nonce) {
1463
+ const [agentPda] = Pda.deriveAgent(agentWallet, client.programId);
1464
+ const [escrowPda] = Pda.deriveEscrowV2(agentPda, depositorWallet, bnToPdaSeed(nonce, 'nonce'), client.programId);
1465
+ const [agentStake] = Pda.deriveStake(agentPda, client.programId);
1466
+ const [agentStats] = Pda.deriveAgentStats(agentPda, client.programId);
1467
+ const [pricingMenu] = Pda.derivePricingMenu(agentPda, client.programId);
1468
+ return { agentPda, escrowPda, agentStake, agentStats, pricingMenu };
1469
+ }
1470
+ function buildSplRemainingAccounts(tokenMint, sourceOwner, destinationOwner) {
1471
+ if (!tokenMint) {
1472
+ return [];
1473
+ }
1474
+ return [
1475
+ {
1476
+ pubkey: deriveAssociatedTokenAddress(tokenMint, sourceOwner),
1477
+ isSigner: false,
1478
+ isWritable: true,
1479
+ },
1480
+ {
1481
+ pubkey: deriveAssociatedTokenAddress(tokenMint, destinationOwner),
1482
+ isSigner: false,
1483
+ isWritable: true,
1484
+ },
1485
+ { pubkey: tokenMint, isSigner: false, isWritable: false },
1486
+ { pubkey: TOKEN_PROGRAM_ID, isSigner: false, isWritable: false },
1487
+ ];
1488
+ }
1489
+ function buildEscrowSettlementRemainingAccounts(params) {
1490
+ const accounts = params.tokenMint
1491
+ ? [
1492
+ ...buildSplRemainingAccounts(params.tokenMint, params.escrowPda, params.agentWallet),
1493
+ {
1494
+ pubkey: deriveAssociatedTokenAddress(params.tokenMint, new PublicKey(SAP_PROTOCOL_TREASURY)),
1495
+ isSigner: false,
1496
+ isWritable: true,
1497
+ },
1498
+ ]
1499
+ : [
1500
+ {
1501
+ pubkey: new PublicKey(SAP_PROTOCOL_TREASURY),
1502
+ isSigner: false,
1503
+ isWritable: true,
1504
+ },
1505
+ ];
1506
+ if (params.pendingSettlementPda) {
1507
+ accounts.push({ pubkey: params.pendingSettlementPda, isSigner: false, isWritable: true });
1508
+ }
1509
+ if (params.coSigner) {
1510
+ accounts.push({ pubkey: params.coSigner, isSigner: true, isWritable: false });
1511
+ }
1512
+ return accounts;
1513
+ }
1514
+ async function serializeUnsignedTransaction(client, feePayer, instructions) {
1515
+ const latestBlockhash = await client.connection.getLatestBlockhash('confirmed');
1516
+ const tx = new Transaction();
1517
+ tx.feePayer = feePayer;
1518
+ tx.recentBlockhash = latestBlockhash.blockhash;
1519
+ for (const instruction of instructions) {
1520
+ tx.add(instruction);
1521
+ }
1522
+ return {
1523
+ transactionBase64: Buffer.from(tx.serialize({ requireAllSignatures: false })).toString('base64'),
1524
+ blockhash: latestBlockhash.blockhash,
1525
+ lastValidBlockHeight: latestBlockhash.lastValidBlockHeight,
1526
+ };
1527
+ }
1528
+ async function fetchEscrowV2Nullable(client, escrowPda) {
1529
+ const accounts = getSapAnchorAccounts(client);
1530
+ return accounts.escrowAccountV2.fetchNullable(escrowPda);
1531
+ }
1532
+ async function fetchPendingSettlementNullable(client, pendingPda) {
1533
+ const accounts = getSapAnchorAccounts(client);
1534
+ return accounts.pendingSettlement.fetchNullable(pendingPda);
1535
+ }
1536
+ function isDisputeWindowEscrow(escrow) {
1537
+ const security = escrow.settlementSecurity;
1538
+ return typeof security === 'object' && security !== null && 'disputeWindow' in security;
1539
+ }
1540
+ function isCoSignedEscrow(escrow) {
1541
+ const security = escrow.settlementSecurity;
1542
+ return typeof security === 'object' && security !== null && 'coSigned' in security;
1543
+ }
1544
+ function escrowBuilderResponse(params) {
1545
+ return {
1546
+ action: params.action,
1547
+ transactionBase64: params.transactionBase64,
1548
+ encoding: 'base64',
1549
+ requiredSigner: params.requiredSigner.toBase58(),
1550
+ requiredSignerRole: params.requiredSignerRole,
1551
+ submitWith: 'sap_payments_finalize_transaction',
1552
+ 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.',
1553
+ accounts: {
1554
+ ...params.accounts,
1555
+ blockhash: params.blockhash,
1556
+ lastValidBlockHeight: params.lastValidBlockHeight,
1557
+ },
1558
+ tokenMode: params.tokenMint ? 'SPL' : 'SOL',
1559
+ ...(params.security ? { security: params.security } : {}),
1560
+ ...(params.warnings && params.warnings.length > 0 ? { warnings: params.warnings } : {}),
1561
+ };
1562
+ }
1563
+ async function buildEscrowCreateTransaction(input, client) {
1564
+ const depositorWallet = requiredPublicKey(input, 'depositorWallet');
1565
+ const agentWallet = requiredPublicKey(input, 'agentWallet');
1566
+ const args = parseEscrowV2Args(input);
1567
+ validateEscrowPaymentMint(args.tokenMint);
1568
+ const pdas = getEscrowPdas(client, agentWallet, depositorWallet, args.escrowNonce);
1569
+ const remainingAccounts = buildSplRemainingAccounts(args.tokenMint, depositorWallet, pdas.escrowPda);
1570
+ const methods = client.program.methods;
1571
+ 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)
1572
+ .accounts({
1573
+ depositor: depositorWallet,
1574
+ agent: pdas.agentPda,
1575
+ agentStake: pdas.agentStake,
1576
+ agentStats: pdas.agentStats,
1577
+ pricingMenu: pdas.pricingMenu,
1578
+ escrow: pdas.escrowPda,
1579
+ systemProgram: SystemProgram.programId,
1580
+ })
1581
+ .remainingAccounts(remainingAccounts)
1582
+ .instruction();
1583
+ const tx = await serializeUnsignedTransaction(client, depositorWallet, [instruction]);
1584
+ return escrowBuilderResponse({
1585
+ action: 'create_escrow_v2',
1586
+ transactionBase64: tx.transactionBase64,
1587
+ requiredSigner: depositorWallet,
1588
+ requiredSignerRole: 'depositor',
1589
+ tokenMint: args.tokenMint,
1590
+ blockhash: tx.blockhash,
1591
+ lastValidBlockHeight: tx.lastValidBlockHeight,
1592
+ accounts: {
1593
+ agentWallet,
1594
+ depositorWallet,
1595
+ agentPda: pdas.agentPda,
1596
+ escrowPda: pdas.escrowPda,
1597
+ agentStake: pdas.agentStake,
1598
+ agentStats: pdas.agentStats,
1599
+ pricingMenu: pdas.pricingMenu,
1600
+ remainingAccounts: serializeAccountMetas(remainingAccounts),
1601
+ },
1602
+ security: {
1603
+ settlementSecurity: args.settlementSecurity,
1604
+ disputeWindowSlots: args.disputeWindowSlots,
1605
+ coSigner: args.coSigner,
1606
+ arbiter: args.arbiter,
1607
+ },
1608
+ });
1609
+ }
1610
+ async function buildEscrowDepositTransaction(input, client) {
1611
+ const depositorWallet = requiredPublicKey(input, 'depositorWallet');
1612
+ const agentWallet = requiredPublicKey(input, 'agentWallet');
1613
+ const nonce = optionalBn(input, 'nonce', new BN(0));
1614
+ const amount = requiredBn(input, 'amount');
1615
+ const pdas = getEscrowPdas(client, agentWallet, depositorWallet, nonce);
1616
+ const escrow = await fetchEscrowV2Nullable(client, pdas.escrowPda);
1617
+ if (!escrow) {
1618
+ throw new Error(`Escrow V2 PDA ${pdas.escrowPda.toBase58()} does not exist. Build and finalize sap_escrow_build_create_transaction first.`);
1619
+ }
1620
+ const tokenMint = publicKeyOrNull(escrow.tokenMint);
1621
+ const remainingAccounts = buildSplRemainingAccounts(tokenMint, depositorWallet, pdas.escrowPda);
1622
+ const methods = client.program.methods;
1623
+ const instruction = await methods.depositEscrowV2(nonce, amount)
1624
+ .accounts({
1625
+ depositor: depositorWallet,
1626
+ escrow: pdas.escrowPda,
1627
+ systemProgram: SystemProgram.programId,
1628
+ })
1629
+ .remainingAccounts(remainingAccounts)
1630
+ .instruction();
1631
+ const tx = await serializeUnsignedTransaction(client, depositorWallet, [instruction]);
1632
+ return escrowBuilderResponse({
1633
+ action: 'deposit_escrow_v2',
1634
+ transactionBase64: tx.transactionBase64,
1635
+ requiredSigner: depositorWallet,
1636
+ requiredSignerRole: 'depositor',
1637
+ tokenMint,
1638
+ blockhash: tx.blockhash,
1639
+ lastValidBlockHeight: tx.lastValidBlockHeight,
1640
+ accounts: {
1641
+ agentWallet,
1642
+ depositorWallet,
1643
+ escrowPda: pdas.escrowPda,
1644
+ amount,
1645
+ remainingAccounts: serializeAccountMetas(remainingAccounts),
1646
+ },
1647
+ });
1648
+ }
1649
+ async function buildEscrowWithdrawTransaction(input, client) {
1650
+ const depositorWallet = requiredPublicKey(input, 'depositorWallet');
1651
+ const agentWallet = requiredPublicKey(input, 'agentWallet');
1652
+ const nonce = optionalBn(input, 'nonce', new BN(0));
1653
+ const amount = requiredBn(input, 'amount');
1654
+ const pdas = getEscrowPdas(client, agentWallet, depositorWallet, nonce);
1655
+ const escrow = await fetchEscrowV2Nullable(client, pdas.escrowPda);
1656
+ if (!escrow) {
1657
+ throw new Error(`Escrow V2 PDA ${pdas.escrowPda.toBase58()} does not exist.`);
1658
+ }
1659
+ const balance = bnToBigInt(escrow.balance);
1660
+ const pendingAmount = bnToBigInt(escrow.pendingAmount);
1661
+ const withdrawable = balance > pendingAmount ? balance - pendingAmount : 0n;
1662
+ if (BigInt(amount.toString(10)) > withdrawable) {
1663
+ throw new Error(`Withdraw amount exceeds withdrawable escrow balance. requested=${amount.toString(10)}, withdrawable=${withdrawable.toString()}.`);
1664
+ }
1665
+ const tokenMint = publicKeyOrNull(escrow.tokenMint);
1666
+ const remainingAccounts = buildSplRemainingAccounts(tokenMint, pdas.escrowPda, depositorWallet);
1667
+ const methods = client.program.methods;
1668
+ const instruction = await methods.withdrawEscrowV2(amount)
1669
+ .accounts({
1670
+ depositor: depositorWallet,
1671
+ escrow: pdas.escrowPda,
1672
+ })
1673
+ .remainingAccounts(remainingAccounts)
1674
+ .instruction();
1675
+ const tx = await serializeUnsignedTransaction(client, depositorWallet, [instruction]);
1676
+ return escrowBuilderResponse({
1677
+ action: 'withdraw_escrow_v2',
1678
+ transactionBase64: tx.transactionBase64,
1679
+ requiredSigner: depositorWallet,
1680
+ requiredSignerRole: 'depositor',
1681
+ tokenMint,
1682
+ blockhash: tx.blockhash,
1683
+ lastValidBlockHeight: tx.lastValidBlockHeight,
1684
+ accounts: {
1685
+ agentWallet,
1686
+ depositorWallet,
1687
+ escrowPda: pdas.escrowPda,
1688
+ amount,
1689
+ withdrawable: withdrawable.toString(),
1690
+ remainingAccounts: serializeAccountMetas(remainingAccounts),
1691
+ },
1692
+ });
1693
+ }
1694
+ async function buildEscrowSettleTransaction(input, client) {
1695
+ const agentWallet = requiredPublicKey(input, 'agentWallet');
1696
+ const depositorWallet = requiredPublicKey(input, 'depositorWallet');
1697
+ const nonce = optionalBn(input, 'nonce', new BN(0));
1698
+ const callsToSettle = requiredBn(input, 'callsToSettle');
1699
+ const serviceHash = requiredBytes(input, 'serviceHash', 32);
1700
+ const pdas = getEscrowPdas(client, agentWallet, depositorWallet, nonce);
1701
+ const escrow = await fetchEscrowV2Nullable(client, pdas.escrowPda);
1702
+ if (!escrow) {
1703
+ throw new Error(`Escrow V2 PDA ${pdas.escrowPda.toBase58()} does not exist.`);
1704
+ }
1705
+ const tokenMint = publicKeyOrNull(escrow.tokenMint);
1706
+ const pendingSettlementIndex = bnToBigInt(escrow.settlementIndex);
1707
+ let pendingSettlementPda;
1708
+ if (isDisputeWindowEscrow(escrow)) {
1709
+ [pendingSettlementPda] = Pda.derivePendingSettlement(pdas.escrowPda, pendingSettlementIndex, client.programId);
1710
+ const existing = await fetchPendingSettlementNullable(client, pendingSettlementPda);
1711
+ if (existing) {
1712
+ throw new Error(`Pending settlement ${pendingSettlementPda.toBase58()} already exists for settlementIndex=${pendingSettlementIndex.toString()}. Finalize or quarantine it before building another DisputeWindow settlement.`);
1713
+ }
1714
+ }
1715
+ const coSigner = isCoSignedEscrow(escrow) ? publicKeyOrNull(escrow.coSigner) : optionalPublicKey(input, 'coSigner') ?? null;
1716
+ if (isCoSignedEscrow(escrow) && !coSigner) {
1717
+ throw new Error('CoSigned escrow requires coSigner in the escrow account.');
1718
+ }
1719
+ const remainingAccounts = buildEscrowSettlementRemainingAccounts({
1720
+ tokenMint,
1721
+ escrowPda: pdas.escrowPda,
1722
+ agentWallet,
1723
+ pendingSettlementPda,
1724
+ coSigner,
1725
+ });
1726
+ const methods = client.program.methods;
1727
+ const instruction = await methods.settleCallsV2(nonce, callsToSettle, serviceHash)
1728
+ .accountsPartial({
1729
+ wallet: agentWallet,
1730
+ agent: pdas.agentPda,
1731
+ agentStats: pdas.agentStats,
1732
+ escrow: pdas.escrowPda,
1733
+ systemProgram: SystemProgram.programId,
1734
+ })
1735
+ .remainingAccounts(remainingAccounts)
1736
+ .instruction();
1737
+ const tx = await serializeUnsignedTransaction(client, agentWallet, [instruction]);
1738
+ return escrowBuilderResponse({
1739
+ action: 'settle_calls_v2',
1740
+ transactionBase64: tx.transactionBase64,
1741
+ requiredSigner: agentWallet,
1742
+ requiredSignerRole: 'agentWallet',
1743
+ tokenMint,
1744
+ blockhash: tx.blockhash,
1745
+ lastValidBlockHeight: tx.lastValidBlockHeight,
1746
+ accounts: {
1747
+ agentWallet,
1748
+ depositorWallet,
1749
+ agentPda: pdas.agentPda,
1750
+ agentStats: pdas.agentStats,
1751
+ escrowPda: pdas.escrowPda,
1752
+ pendingSettlementPda: pendingSettlementPda ?? null,
1753
+ pendingSettlementIndex: pendingSettlementIndex.toString(),
1754
+ callsToSettle,
1755
+ serviceHash,
1756
+ remainingAccounts: serializeAccountMetas(remainingAccounts),
1757
+ },
1758
+ security: {
1759
+ settlementSecurity: isDisputeWindowEscrow(escrow) ? ESCROW_V2_DISPUTE_WINDOW_MODE : ESCROW_V2_COSIGNED_MODE,
1760
+ coSigner,
1761
+ },
1762
+ });
1763
+ }
1764
+ async function buildEscrowFinalizeTransaction(input, client) {
1765
+ const payerWallet = requiredPublicKey(input, 'payerWallet');
1766
+ const agentWallet = requiredPublicKey(input, 'agentWallet');
1767
+ const depositorWallet = requiredPublicKey(input, 'depositorWallet');
1768
+ const nonce = optionalBn(input, 'nonce', new BN(0));
1769
+ const settlementIndex = requiredBn(input, 'settlementIndex');
1770
+ const pdas = getEscrowPdas(client, agentWallet, depositorWallet, nonce);
1771
+ const [pendingSettlementPda] = Pda.derivePendingSettlement(pdas.escrowPda, bnToPdaSeed(settlementIndex, 'settlementIndex'), client.programId);
1772
+ const escrow = await fetchEscrowV2Nullable(client, pdas.escrowPda);
1773
+ if (!escrow) {
1774
+ throw new Error(`Escrow V2 PDA ${pdas.escrowPda.toBase58()} does not exist.`);
1775
+ }
1776
+ const pending = await fetchPendingSettlementNullable(client, pendingSettlementPda);
1777
+ if (!pending) {
1778
+ throw new Error(`Pending settlement ${pendingSettlementPda.toBase58()} does not exist.`);
1779
+ }
1780
+ const tokenMint = publicKeyOrNull(escrow.tokenMint);
1781
+ const remainingAccounts = buildSplRemainingAccounts(tokenMint, pdas.escrowPda, agentWallet);
1782
+ const methods = client.program.methods;
1783
+ const instruction = await methods.finalizeSettlement()
1784
+ .accounts({
1785
+ payer: payerWallet,
1786
+ agentWallet,
1787
+ escrow: pdas.escrowPda,
1788
+ pendingSettlement: pendingSettlementPda,
1789
+ agentStats: pdas.agentStats,
1790
+ })
1791
+ .remainingAccounts(remainingAccounts)
1792
+ .instruction();
1793
+ const tx = await serializeUnsignedTransaction(client, payerWallet, [instruction]);
1794
+ return escrowBuilderResponse({
1795
+ action: 'finalize_settlement',
1796
+ transactionBase64: tx.transactionBase64,
1797
+ requiredSigner: payerWallet,
1798
+ requiredSignerRole: 'payer',
1799
+ tokenMint,
1800
+ blockhash: tx.blockhash,
1801
+ lastValidBlockHeight: tx.lastValidBlockHeight,
1802
+ accounts: {
1803
+ payerWallet,
1804
+ agentWallet,
1805
+ depositorWallet,
1806
+ escrowPda: pdas.escrowPda,
1807
+ pendingSettlementPda,
1808
+ settlementIndex,
1809
+ releaseSlot: pending.releaseSlot,
1810
+ remainingAccounts: serializeAccountMetas(remainingAccounts),
1811
+ },
1812
+ });
1813
+ }
1814
+ async function buildEscrowCloseTransaction(input, client) {
1815
+ const depositorWallet = requiredPublicKey(input, 'depositorWallet');
1816
+ const agentWallet = requiredPublicKey(input, 'agentWallet');
1817
+ const nonce = optionalBn(input, 'nonce', new BN(0));
1818
+ const pdas = getEscrowPdas(client, agentWallet, depositorWallet, nonce);
1819
+ const escrow = await fetchEscrowV2Nullable(client, pdas.escrowPda);
1820
+ if (!escrow) {
1821
+ throw new Error(`Escrow V2 PDA ${pdas.escrowPda.toBase58()} does not exist.`);
1822
+ }
1823
+ if (bnToBigInt(escrow.balance) !== 0n || bnToBigInt(escrow.pendingAmount) !== 0n) {
1824
+ throw new Error('Escrow cannot close until balance and pendingAmount are both zero.');
1825
+ }
1826
+ const methods = client.program.methods;
1827
+ const instruction = await methods.closeEscrowV2()
1828
+ .accounts({
1829
+ depositor: depositorWallet,
1830
+ escrow: pdas.escrowPda,
1831
+ agentStats: pdas.agentStats,
1832
+ })
1833
+ .instruction();
1834
+ const tx = await serializeUnsignedTransaction(client, depositorWallet, [instruction]);
1835
+ return escrowBuilderResponse({
1836
+ action: 'close_escrow_v2',
1837
+ transactionBase64: tx.transactionBase64,
1838
+ requiredSigner: depositorWallet,
1839
+ requiredSignerRole: 'depositor',
1840
+ tokenMint: publicKeyOrNull(escrow.tokenMint),
1841
+ blockhash: tx.blockhash,
1842
+ lastValidBlockHeight: tx.lastValidBlockHeight,
1843
+ accounts: {
1844
+ agentWallet,
1845
+ depositorWallet,
1846
+ escrowPda: pdas.escrowPda,
1847
+ agentStats: pdas.agentStats,
1848
+ },
1849
+ });
1850
+ }
888
1851
  /**
889
1852
  * @name parseFeedbackArgs
890
1853
  * @description Builds typed feedback args from MCP JSON input.
@@ -1000,7 +1963,7 @@ function buildSapSdkToolDescription(definition) {
1000
1963
  }
1001
1964
  function getSapSdkToolContext(name) {
1002
1965
  if (name === 'sap_register_agent') {
1003
- return 'SAP MCP context: This is the primary on-chain SAP agent registration tool for a local profile signer or external signer. Hosted accountless SAP MCP rejects direct registration before x402 payment because OOBE never custodies user wallet keys. Use agentUri or metadataUri for off-chain metadata, including a Metaplex or DAS-backed identity document when the agent also has NFT/collection metadata. After registration, use sap_publish_tool_by_name for advertised MCP capabilities and AgentKit metaplex-nft_* tools for NFT collection, badge, or metadata workflows.';
1966
+ 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
1967
  }
1005
1968
  if (name === 'sap_update_agent') {
1006
1969
  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 +1971,9 @@ function getSapSdkToolContext(name) {
1008
1971
  if (name.startsWith('sap_publish_tool') || name.startsWith('sap_update_tool')) {
1009
1972
  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
1973
  }
1974
+ if (name.startsWith('sap_escrow_build_')) {
1975
+ 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.';
1976
+ }
1011
1977
  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
1978
  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
1979
  }
@@ -1026,28 +1992,34 @@ function getSapSdkToolContext(name) {
1026
1992
  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
1993
  }
1028
1994
  const agentTools = [
1995
+ {
1996
+ name: 'sap_protocol_invariants',
1997
+ title: 'Get SAP Protocol Invariants',
1998
+ 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.',
1999
+ inputSchema: {},
2000
+ handler: async () => buildSapProtocolInvariants(),
2001
+ },
2002
+ {
2003
+ name: 'sap_agent_identity_plan',
2004
+ title: 'Plan SAP Agent Identity',
2005
+ 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.',
2006
+ inputSchema: SAP_AGENT_IDENTITY_PLAN_INPUT_SCHEMA,
2007
+ handler: async (input) => buildSapAgentIdentityPlan(input),
2008
+ },
1029
2009
  {
1030
2010
  name: 'sap_register_agent',
1031
- title: 'Register SAP Agent',
1032
- description: 'Local-signer-only: register the connected wallet as a SAP agent using SDK AgentModule.register. Hosted accountless SAP MCP rejects this direct write before x402 payment; run it through a local SAP MCP profile or external signer.',
1033
- inputSchema: {
1034
- name: { type: 'string', description: 'Human-readable name for the SAP agent' },
1035
- description: { type: 'string', description: 'Detailed description of the agent\'s purpose and capabilities' },
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' },
2011
+ title: 'Register SAP Agent (Raw SDK Deprecated)',
2012
+ 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.',
2013
+ inputSchema: SAP_AGENT_REGISTER_INPUT_SCHEMA,
2014
+ handler: async () => {
2015
+ 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
2016
  },
1044
- handler: async (input, client) => ({ signature: await client.agent.register(parseRegisterAgentArgs(input)) }),
1045
2017
  },
1046
2018
  {
1047
2019
  name: 'sap_update_agent',
1048
2020
  title: 'Update SAP Agent',
1049
- description: 'Update the connected wallet SAP agent using SDK AgentModule.update.',
1050
- inputSchema: { type: 'object', additionalProperties: true, description: 'Object containing any combination of agent fields to update (name, description, capabilities, pricing, protocols, agentId, agentUri, x402Endpoint)' },
2021
+ 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.',
2022
+ inputSchema: SAP_AGENT_UPDATE_INPUT_SCHEMA,
1051
2023
  handler: async (input, client) => ({ signature: await client.agent.update(parseUpdateAgentArgs(input)) }),
1052
2024
  },
1053
2025
  {
@@ -1457,24 +2429,66 @@ const paymentAndEscrowTools = [
1457
2429
  },
1458
2430
  ];
1459
2431
  const escrowV2Tools = [
2432
+ {
2433
+ name: 'sap_escrow_build_create_transaction',
2434
+ title: 'Build SAP Escrow V2 Create Transaction',
2435
+ 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.',
2436
+ inputSchema: escrowV2CreateBuilderInputSchema,
2437
+ handler: buildEscrowCreateTransaction,
2438
+ },
2439
+ {
2440
+ name: 'sap_escrow_build_deposit_transaction',
2441
+ title: 'Build SAP Escrow V2 Deposit Transaction',
2442
+ 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.',
2443
+ inputSchema: escrowV2DepositBuilderInputSchema,
2444
+ handler: buildEscrowDepositTransaction,
2445
+ },
2446
+ {
2447
+ name: 'sap_escrow_build_settle_transaction',
2448
+ title: 'Build SAP Escrow V2 Settlement Transaction',
2449
+ 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.',
2450
+ inputSchema: escrowV2SettleBuilderInputSchema,
2451
+ handler: buildEscrowSettleTransaction,
2452
+ },
2453
+ {
2454
+ name: 'sap_escrow_build_finalize_transaction',
2455
+ title: 'Build SAP Escrow V2 Finalize Transaction',
2456
+ 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.',
2457
+ inputSchema: escrowV2FinalizeBuilderInputSchema,
2458
+ handler: buildEscrowFinalizeTransaction,
2459
+ },
2460
+ {
2461
+ name: 'sap_escrow_build_withdraw_transaction',
2462
+ title: 'Build SAP Escrow V2 Withdraw Transaction',
2463
+ description: 'Hosted-safe unsigned builder for withdraw_escrow_v2. The depositor signs locally and can only withdraw unlocked balance after pending amounts are excluded.',
2464
+ inputSchema: escrowV2WithdrawBuilderInputSchema,
2465
+ handler: buildEscrowWithdrawTransaction,
2466
+ },
2467
+ {
2468
+ name: 'sap_escrow_build_close_transaction',
2469
+ title: 'Build SAP Escrow V2 Close Transaction',
2470
+ 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.',
2471
+ inputSchema: escrowV2CloseBuilderInputSchema,
2472
+ handler: buildEscrowCloseTransaction,
2473
+ },
1460
2474
  {
1461
2475
  name: 'sap_create_escrow_v2',
1462
2476
  title: 'Create SAP Escrow V2',
1463
- description: 'Create a V2 escrow using SDK EscrowV2Module.create. Defaults to DisputeWindow settlementSecurity=2; SelfReport/0 is rejected.',
2477
+ 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
2478
  inputSchema: escrowV2CreateInputSchema,
1465
2479
  handler: async (input, client) => ({ signature: await client.escrowV2.create(requiredPublicKey(input, 'agentWallet'), parseEscrowV2Args(input)) }),
1466
2480
  },
1467
2481
  {
1468
2482
  name: 'sap_deposit_escrow_v2',
1469
2483
  title: 'Deposit SAP Escrow V2',
1470
- description: 'Deposit funds into a V2 escrow.',
2484
+ description: 'Local-signer-only direct deposit into a V2 escrow. Hosted users should call sap_escrow_build_deposit_transaction and finalize locally.',
1471
2485
  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
2486
  handler: async (input, client) => ({ signature: await client.escrowV2.deposit(requiredPublicKey(input, 'agentWallet'), optionalBn(input, 'nonce', new BN(0)), requiredBn(input, 'amount')) }),
1473
2487
  },
1474
2488
  {
1475
2489
  name: 'sap_settle_escrow_v2',
1476
2490
  title: 'Settle SAP Escrow V2',
1477
- description: 'Settle calls against a V2 escrow.',
2491
+ description: 'Local-signer-only direct settlement against a V2 escrow. Hosted users should call sap_escrow_build_settle_transaction and finalize locally.',
1478
2492
  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
2493
  handler: async (input, client) => ({
1480
2494
  signature: await client.escrowV2.settle(requiredPublicKey(input, 'depositorWallet'), optionalBn(input, 'nonce', new BN(0)), requiredBn(input, 'callsToSettle'), requiredBytes(input, 'serviceHash', 32)),
@@ -1492,7 +2506,7 @@ const escrowV2Tools = [
1492
2506
  {
1493
2507
  name: 'sap_finalize_settlement_v2',
1494
2508
  title: 'Finalize SAP Escrow V2 Settlement',
1495
- description: 'Finalize a V2 pending settlement.',
2509
+ description: 'Local-signer-only direct finalization of a V2 pending settlement. Hosted users should call sap_escrow_build_finalize_transaction and finalize locally.',
1496
2510
  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
2511
  handler: async (input, client) => ({
1498
2512
  signature: await client.escrowV2.finalizeSettlement(requiredPublicKey(input, 'agentWallet'), requiredPublicKey(input, 'depositorWallet'), optionalBn(input, 'nonce', new BN(0)), requiredBn(input, 'settlementIndex')),
@@ -1510,14 +2524,14 @@ const escrowV2Tools = [
1510
2524
  {
1511
2525
  name: 'sap_withdraw_escrow_v2',
1512
2526
  title: 'Withdraw SAP Escrow V2',
1513
- description: 'Withdraw funds from a V2 escrow.',
2527
+ description: 'Local-signer-only direct withdrawal from a V2 escrow. Hosted users should call sap_escrow_build_withdraw_transaction and finalize locally.',
1514
2528
  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
2529
  handler: async (input, client) => ({ signature: await client.escrowV2.withdraw(requiredPublicKey(input, 'agentWallet'), optionalBn(input, 'nonce', new BN(0)), requiredBn(input, 'amount')) }),
1516
2530
  },
1517
2531
  {
1518
2532
  name: 'sap_close_escrow_v2',
1519
2533
  title: 'Close SAP Escrow V2',
1520
- description: 'Close a V2 escrow.',
2534
+ description: 'Local-signer-only direct close for an empty V2 escrow. Hosted users should call sap_escrow_build_close_transaction and finalize locally.',
1521
2535
  inputSchema: { agentWallet: { type: 'string', description: 'Agent wallet public key (base58)' }, nonce: { type: 'string', description: 'Escrow nonce (as a decimal string, default: 0)' } },
1522
2536
  handler: async (input, client) => ({ signature: await client.escrowV2.close(requiredPublicKey(input, 'agentWallet'), optionalBn(input, 'nonce', new BN(0))) }),
1523
2537
  },