@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.
- package/CHANGELOG.md +34 -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 +29 -0
- package/dist/payments/pricing.d.ts.map +1 -1
- package/dist/payments/pricing.js +81 -0
- 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 +222 -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 +1044 -30
- 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 +38 -1
- package/docs/07_ENDPOINTS_AND_CLIENTS.md +2 -1
- 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 +1 -1
- package/server.json +3 -3
- package/skills/sap-agent-registry/SKILL.md +63 -5
- package/skills/sap-escrow-settlement/SKILL.md +22 -4
- package/skills/sap-mcp/SKILL.md +73 -22
- package/skills/sap-mcp/TOOL_REFERENCE.md +22 -1
- package/skills/sap-nft-metaplex/SKILL.md +22 -0
- package/skills/sap-operations/SKILL.md +17 -0
- package/skills/sap-payments-x402/SKILL.md +27 -0
- 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
|
|
712
|
-
tokenMint
|
|
713
|
-
tokenDecimals: optionalNumber(record, 'tokenDecimals') ??
|
|
714
|
-
settlementMode:
|
|
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:
|
|
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: '
|
|
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' },
|
|
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: '
|
|
1050
|
-
inputSchema:
|
|
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: '
|
|
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: '
|
|
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: '
|
|
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: '
|
|
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: '
|
|
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: '
|
|
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
|
},
|