@oobe-protocol-labs/synapse-sap-sdk 0.6.3 → 0.8.0

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 (132) hide show
  1. package/dist/cjs/constants/seeds.js +9 -0
  2. package/dist/cjs/constants/seeds.js.map +1 -1
  3. package/dist/cjs/core/client.js +55 -0
  4. package/dist/cjs/core/client.js.map +1 -1
  5. package/dist/cjs/idl/synapse_agent_sap.json +8571 -4106
  6. package/dist/cjs/index.js +29 -3
  7. package/dist/cjs/index.js.map +1 -1
  8. package/dist/cjs/modules/escrow-v2.js +220 -0
  9. package/dist/cjs/modules/escrow-v2.js.map +1 -0
  10. package/dist/cjs/modules/escrow.js +4 -0
  11. package/dist/cjs/modules/escrow.js.map +1 -1
  12. package/dist/cjs/modules/index.js +9 -1
  13. package/dist/cjs/modules/index.js.map +1 -1
  14. package/dist/cjs/modules/receipt.js +144 -0
  15. package/dist/cjs/modules/receipt.js.map +1 -0
  16. package/dist/cjs/modules/staking.js +94 -0
  17. package/dist/cjs/modules/staking.js.map +1 -0
  18. package/dist/cjs/modules/subscription.js +96 -0
  19. package/dist/cjs/modules/subscription.js.map +1 -0
  20. package/dist/cjs/pda/index.js +166 -1
  21. package/dist/cjs/pda/index.js.map +1 -1
  22. package/dist/cjs/registries/x402.js +88 -51
  23. package/dist/cjs/registries/x402.js.map +1 -1
  24. package/dist/cjs/types/enums.js +96 -1
  25. package/dist/cjs/types/enums.js.map +1 -1
  26. package/dist/cjs/types/index.js +6 -1
  27. package/dist/cjs/types/index.js.map +1 -1
  28. package/dist/cjs/types/instructions.js.map +1 -1
  29. package/dist/cjs/utils/escrow-validation.js +219 -0
  30. package/dist/cjs/utils/escrow-validation.js.map +1 -0
  31. package/dist/cjs/utils/index.js +12 -1
  32. package/dist/cjs/utils/index.js.map +1 -1
  33. package/dist/cjs/utils/merchant-validator.js +246 -0
  34. package/dist/cjs/utils/merchant-validator.js.map +1 -0
  35. package/dist/cjs/utils/x402-direct.js +231 -0
  36. package/dist/cjs/utils/x402-direct.js.map +1 -0
  37. package/dist/esm/constants/seeds.js +9 -0
  38. package/dist/esm/constants/seeds.js.map +1 -1
  39. package/dist/esm/core/client.js +55 -0
  40. package/dist/esm/core/client.js.map +1 -1
  41. package/dist/esm/idl/synapse_agent_sap.json +8571 -4106
  42. package/dist/esm/index.js +5 -3
  43. package/dist/esm/index.js.map +1 -1
  44. package/dist/esm/modules/escrow-v2.js +216 -0
  45. package/dist/esm/modules/escrow-v2.js.map +1 -0
  46. package/dist/esm/modules/escrow.js +4 -0
  47. package/dist/esm/modules/escrow.js.map +1 -1
  48. package/dist/esm/modules/index.js +4 -0
  49. package/dist/esm/modules/index.js.map +1 -1
  50. package/dist/esm/modules/receipt.js +140 -0
  51. package/dist/esm/modules/receipt.js.map +1 -0
  52. package/dist/esm/modules/staking.js +90 -0
  53. package/dist/esm/modules/staking.js.map +1 -0
  54. package/dist/esm/modules/subscription.js +92 -0
  55. package/dist/esm/modules/subscription.js.map +1 -0
  56. package/dist/esm/pda/index.js +157 -0
  57. package/dist/esm/pda/index.js.map +1 -1
  58. package/dist/esm/registries/x402.js +89 -52
  59. package/dist/esm/registries/x402.js.map +1 -1
  60. package/dist/esm/types/enums.js +95 -0
  61. package/dist/esm/types/enums.js.map +1 -1
  62. package/dist/esm/types/index.js +1 -1
  63. package/dist/esm/types/index.js.map +1 -1
  64. package/dist/esm/types/instructions.js.map +1 -1
  65. package/dist/esm/utils/escrow-validation.js +212 -0
  66. package/dist/esm/utils/escrow-validation.js.map +1 -0
  67. package/dist/esm/utils/index.js +4 -0
  68. package/dist/esm/utils/index.js.map +1 -1
  69. package/dist/esm/utils/merchant-validator.js +241 -0
  70. package/dist/esm/utils/merchant-validator.js.map +1 -0
  71. package/dist/esm/utils/x402-direct.js +228 -0
  72. package/dist/esm/utils/x402-direct.js.map +1 -0
  73. package/dist/types/constants/seeds.d.ts +9 -0
  74. package/dist/types/constants/seeds.d.ts.map +1 -1
  75. package/dist/types/core/client.d.ts +43 -0
  76. package/dist/types/core/client.d.ts.map +1 -1
  77. package/dist/types/index.d.ts +6 -4
  78. package/dist/types/index.d.ts.map +1 -1
  79. package/dist/types/modules/escrow-v2.d.ts +58 -0
  80. package/dist/types/modules/escrow-v2.d.ts.map +1 -0
  81. package/dist/types/modules/escrow.d.ts +4 -0
  82. package/dist/types/modules/escrow.d.ts.map +1 -1
  83. package/dist/types/modules/index.d.ts +4 -0
  84. package/dist/types/modules/index.d.ts.map +1 -1
  85. package/dist/types/modules/receipt.d.ts +77 -0
  86. package/dist/types/modules/receipt.d.ts.map +1 -0
  87. package/dist/types/modules/staking.d.ts +32 -0
  88. package/dist/types/modules/staking.d.ts.map +1 -0
  89. package/dist/types/modules/subscription.d.ts +33 -0
  90. package/dist/types/modules/subscription.d.ts.map +1 -0
  91. package/dist/types/pda/index.d.ts +114 -0
  92. package/dist/types/pda/index.d.ts.map +1 -1
  93. package/dist/types/plugin/schemas.d.ts +2 -2
  94. package/dist/types/registries/x402.d.ts +14 -12
  95. package/dist/types/registries/x402.d.ts.map +1 -1
  96. package/dist/types/types/accounts.d.ts +201 -1
  97. package/dist/types/types/accounts.d.ts.map +1 -1
  98. package/dist/types/types/enums.d.ts +115 -0
  99. package/dist/types/types/enums.d.ts.map +1 -1
  100. package/dist/types/types/index.d.ts +4 -4
  101. package/dist/types/types/index.d.ts.map +1 -1
  102. package/dist/types/types/instructions.d.ts +34 -0
  103. package/dist/types/types/instructions.d.ts.map +1 -1
  104. package/dist/types/utils/escrow-validation.d.ts +145 -0
  105. package/dist/types/utils/escrow-validation.d.ts.map +1 -0
  106. package/dist/types/utils/index.d.ts +6 -0
  107. package/dist/types/utils/index.d.ts.map +1 -1
  108. package/dist/types/utils/merchant-validator.d.ts +176 -0
  109. package/dist/types/utils/merchant-validator.d.ts.map +1 -0
  110. package/dist/types/utils/x402-direct.d.ts +114 -0
  111. package/dist/types/utils/x402-direct.d.ts.map +1 -0
  112. package/package.json +2 -2
  113. package/src/constants/seeds.ts +9 -0
  114. package/src/core/client.ts +59 -0
  115. package/src/idl/synapse_agent_sap.json +8571 -4106
  116. package/src/index.ts +54 -0
  117. package/src/modules/escrow-v2.ts +375 -0
  118. package/src/modules/escrow.ts +4 -0
  119. package/src/modules/index.ts +4 -0
  120. package/src/modules/receipt.ts +207 -0
  121. package/src/modules/staking.ts +122 -0
  122. package/src/modules/subscription.ts +147 -0
  123. package/src/pda/index.ts +228 -0
  124. package/src/registries/x402.ts +108 -69
  125. package/src/types/accounts.ts +241 -1
  126. package/src/types/enums.ts +119 -0
  127. package/src/types/index.ts +20 -0
  128. package/src/types/instructions.ts +40 -0
  129. package/src/utils/escrow-validation.ts +301 -0
  130. package/src/utils/index.ts +28 -0
  131. package/src/utils/merchant-validator.ts +359 -0
  132. package/src/utils/x402-direct.ts +370 -0
@@ -212,3 +212,122 @@ export const ToolCategory = {
212
212
  */
213
213
  export type ToolCategoryKind =
214
214
  (typeof ToolCategory)[keyof typeof ToolCategory];
215
+
216
+ // ═══════════════════════════════════════════════════════════════════
217
+ // Settlement Security (V2.1)
218
+ // ═══════════════════════════════════════════════════════════════════
219
+
220
+ /**
221
+ * @name SettlementSecurity
222
+ * @description Anchor-compatible enum variants for V2 escrow settlement security levels.
223
+ *
224
+ * - `SelfReport` — **DEPRECATED in v0.7** — Agent settles unilaterally (abuse vector).
225
+ * - `CoSigned` — Agent + client must co-sign every settlement.
226
+ * - `DisputeWindow` — Settlement enters pending state, depositor can dispute.
227
+ *
228
+ * @category Types
229
+ * @since v0.5.0
230
+ */
231
+ export const SettlementSecurity = {
232
+ /** @deprecated Removed in v0.7 — returns SelfReportDeprecated error */
233
+ SelfReport: { selfReport: {} },
234
+ CoSigned: { coSigned: {} },
235
+ DisputeWindow: { disputeWindow: {} },
236
+ } as const;
237
+
238
+ export type SettlementSecurityKind =
239
+ (typeof SettlementSecurity)[keyof typeof SettlementSecurity];
240
+
241
+ // ═══════════════════════════════════════════════════════════════════
242
+ // Dispute Outcome (V2.1)
243
+ // ═══════════════════════════════════════════════════════════════════
244
+
245
+ /**
246
+ * @name DisputeOutcome
247
+ * @description Anchor-compatible enum variants for dispute resolution outcomes.
248
+ *
249
+ * @category Types
250
+ * @since v0.5.0
251
+ */
252
+ export const DisputeOutcome = {
253
+ Pending: { pending: {} },
254
+ AutoReleased: { autoReleased: {} },
255
+ DepositorWins: { depositorWins: {} },
256
+ AgentWins: { agentWins: {} },
257
+ /** @since v0.7.0 — Proportional refund based on proven vs claimed calls */
258
+ PartialRefund: { partialRefund: {} },
259
+ /** @since v0.7.0 — 50/50 split for irresolvable quality disputes */
260
+ Split: { split: {} },
261
+ } as const;
262
+
263
+ export type DisputeOutcomeKind =
264
+ (typeof DisputeOutcome)[keyof typeof DisputeOutcome];
265
+
266
+ // ═══════════════════════════════════════════════════════════════════
267
+ // Dispute Type (v0.7)
268
+ // ═══════════════════════════════════════════════════════════════════
269
+
270
+ /**
271
+ * @name DisputeType
272
+ * @description Categories of disputes that can be filed against a pending settlement.
273
+ *
274
+ * - `NonDelivery` — Agent took payment but delivered nothing.
275
+ * - `PartialDelivery` — Agent delivered fewer calls than claimed.
276
+ * - `Overcharge` — Agent charged more than the agreed price.
277
+ * - `Quality` — Agent delivered but output quality is disputed.
278
+ *
279
+ * @category Types
280
+ * @since v0.7.0
281
+ */
282
+ export const DisputeType = {
283
+ NonDelivery: 0,
284
+ PartialDelivery: 1,
285
+ Overcharge: 2,
286
+ Quality: 3,
287
+ } as const;
288
+
289
+ export type DisputeTypeValue = (typeof DisputeType)[keyof typeof DisputeType];
290
+
291
+ // ═══════════════════════════════════════════════════════════════════
292
+ // Resolution Layer (v0.7)
293
+ // ═══════════════════════════════════════════════════════════════════
294
+
295
+ /**
296
+ * @name ResolutionLayer
297
+ * @description How a dispute was resolved.
298
+ *
299
+ * - `Pending` — Not yet resolved.
300
+ * - `Auto` — Resolved automatically via receipt merkle proofs.
301
+ * - `Governance` — Resolved via protocol governance (quality disputes).
302
+ *
303
+ * @category Types
304
+ * @since v0.7.0
305
+ */
306
+ export const ResolutionLayer = {
307
+ Pending: { pending: {} },
308
+ Auto: { auto: {} },
309
+ Governance: { governance: {} },
310
+ } as const;
311
+
312
+ export type ResolutionLayerKind =
313
+ (typeof ResolutionLayer)[keyof typeof ResolutionLayer];
314
+
315
+ // ═══════════════════════════════════════════════════════════════════
316
+ // Billing Interval (V2.1)
317
+ // ═══════════════════════════════════════════════════════════════════
318
+
319
+ /**
320
+ * @name BillingInterval
321
+ * @description Anchor-compatible enum variants for subscription billing intervals.
322
+ *
323
+ * @category Types
324
+ * @since v0.5.0
325
+ */
326
+ export const BillingInterval = {
327
+ Daily: { daily: {} },
328
+ Weekly: { weekly: {} },
329
+ Monthly: { monthly: {} },
330
+ } as const;
331
+
332
+ export type BillingIntervalKind =
333
+ (typeof BillingInterval)[keyof typeof BillingInterval];
@@ -27,6 +27,11 @@ export {
27
27
  SettlementMode,
28
28
  ToolHttpMethod,
29
29
  ToolCategory,
30
+ SettlementSecurity,
31
+ DisputeOutcome,
32
+ BillingInterval,
33
+ DisputeType,
34
+ ResolutionLayer,
30
35
  } from "./enums";
31
36
 
32
37
  export type {
@@ -35,6 +40,11 @@ export type {
35
40
  SettlementModeKind,
36
41
  ToolHttpMethodKind,
37
42
  ToolCategoryKind,
43
+ SettlementSecurityKind,
44
+ DisputeOutcomeKind,
45
+ BillingIntervalKind,
46
+ DisputeTypeValue,
47
+ ResolutionLayerKind,
38
48
  } from "./enums";
39
49
 
40
50
  // ── Common Structs ───────────────────────────────────
@@ -65,6 +75,14 @@ export type {
65
75
  AgentAttestationData,
66
76
  MemoryLedgerData,
67
77
  LedgerPageData,
78
+ EscrowAccountV2Data,
79
+ PendingSettlementData,
80
+ DisputeRecordData,
81
+ ReceiptBatchData,
82
+ AgentStakeData,
83
+ SubscriptionData,
84
+ CounterShardData,
85
+ IndexPageData,
68
86
  } from "./accounts";
69
87
 
70
88
  // ── Instruction Args ─────────────────────────────────
@@ -83,6 +101,8 @@ export type {
83
101
  DelegatePermissionBit,
84
102
  SchemaTypeValue,
85
103
  CompressionTypeValue,
104
+ CreateEscrowV2Args,
105
+ CreateSubscriptionArgs,
86
106
  } from "./instructions";
87
107
 
88
108
  export {
@@ -269,6 +269,7 @@ export interface CompactInscribeArgs {
269
269
  *
270
270
  * @category Types
271
271
  * @since v0.1.0
272
+ * @deprecated Since v0.7.0 — Use {@link CreateEscrowV2Args} for V2 escrows.
272
273
  * @see {@link VolumeCurveBreakpoint} for discount curve details.
273
274
  */
274
275
  export interface CreateEscrowArgs {
@@ -411,3 +412,42 @@ export const CompressionType = {
411
412
  */
412
413
  export type CompressionTypeValue =
413
414
  (typeof CompressionType)[keyof typeof CompressionType];
415
+
416
+ // ═══════════════════════════════════════════════════════════════════
417
+ // Escrow V2 Instructions (V2.1)
418
+ // ═══════════════════════════════════════════════════════════════════
419
+
420
+ /**
421
+ * @interface CreateEscrowV2Args
422
+ * @description Arguments for the `createEscrowV2` instruction.
423
+ * @category Types
424
+ * @since v0.5.0
425
+ */
426
+ export interface CreateEscrowV2Args {
427
+ readonly escrowNonce: BN;
428
+ readonly pricePerCall: BN;
429
+ readonly maxCalls: BN;
430
+ readonly initialDeposit: BN;
431
+ readonly expiresAt: BN;
432
+ readonly volumeCurve: VolumeCurveBreakpoint[];
433
+ readonly tokenMint: PublicKey | null;
434
+ readonly tokenDecimals: number;
435
+ /** 0=SelfReport, 1=CoSigned, 2=DisputeWindow */
436
+ readonly settlementSecurity: number;
437
+ readonly disputeWindowSlots: BN;
438
+ readonly coSigner: PublicKey | null;
439
+ readonly arbiter: PublicKey | null;
440
+ }
441
+
442
+ /**
443
+ * @interface CreateSubscriptionArgs
444
+ * @description Arguments for the `createSubscription` instruction.
445
+ * @category Types
446
+ * @since v0.5.0
447
+ */
448
+ export interface CreateSubscriptionArgs {
449
+ readonly subId: BN;
450
+ readonly pricePerInterval: BN;
451
+ readonly billingInterval: number;
452
+ readonly initialFund: BN;
453
+ }
@@ -0,0 +1,301 @@
1
+ /**
2
+ * @module utils/escrow-validation
3
+ * @description Server-side escrow validation pipeline.
4
+ *
5
+ * Provides typed helpers to validate escrow state before settlement
6
+ * and to build the correct SPL `AccountMeta[]` for token escrows.
7
+ *
8
+ * @category Utils
9
+ * @since v0.6.4
10
+ */
11
+
12
+ import {
13
+ PublicKey,
14
+ type Connection,
15
+ type AccountMeta,
16
+ } from "@solana/web3.js";
17
+ import { BN } from "@coral-xyz/anchor";
18
+ import { findATA } from "./rpc-strategy";
19
+ import { deriveAgent, deriveEscrow } from "../pda";
20
+ import { SapError } from "../errors";
21
+ import type { EscrowAccountData } from "../types";
22
+
23
+ // ═══════════════════════════════════════════════════════════════════
24
+ // Types
25
+ // ═══════════════════════════════════════════════════════════════════
26
+
27
+ /**
28
+ * @interface SplAccountMeta
29
+ * @description Typed SPL account metadata for escrow operations.
30
+ * @category Utils
31
+ * @since v0.6.4
32
+ */
33
+ export interface SplAccountMeta {
34
+ /** Account role in the escrow pipeline. */
35
+ readonly kind: "escrowAta" | "depositorAta" | "tokenMint" | "tokenProgram";
36
+ /** Account public key. */
37
+ readonly pubkey: PublicKey;
38
+ /** Whether this account is writable. */
39
+ readonly writable: boolean;
40
+ }
41
+
42
+ /**
43
+ * @interface EscrowValidationResult
44
+ * @description Result of server-side escrow state validation.
45
+ * @category Utils
46
+ * @since v0.6.4
47
+ */
48
+ export interface EscrowValidationResult {
49
+ /** Whether the escrow is valid for settlement. */
50
+ readonly valid: boolean;
51
+ /** Escrow account data (if found). */
52
+ readonly escrow: EscrowAccountData | null;
53
+ /** Escrow PDA address. */
54
+ readonly escrowPda: PublicKey;
55
+ /** Agent PDA address. */
56
+ readonly agentPda: PublicKey;
57
+ /** Whether this is an SPL token escrow (vs SOL). */
58
+ readonly isSplEscrow: boolean;
59
+ /** Generated SPL account metas (empty for SOL escrows). */
60
+ readonly splAccounts: SplAccountMeta[];
61
+ /** Validation errors (empty when valid). */
62
+ readonly errors: string[];
63
+ }
64
+
65
+ // ═══════════════════════════════════════════════════════════════════
66
+ // Error
67
+ // ═══════════════════════════════════════════════════════════════════
68
+
69
+ /**
70
+ * @name MissingEscrowAtaError
71
+ * @description Thrown when an SPL escrow operation is missing required
72
+ * Associated Token Accounts.
73
+ * @category Errors
74
+ * @since v0.6.4
75
+ */
76
+ export class MissingEscrowAtaError extends SapError {
77
+ /** The ATA address that is missing. */
78
+ readonly ataAddress: string;
79
+ /** Which side is missing: depositor or escrow. */
80
+ readonly side: "depositor" | "escrow";
81
+
82
+ constructor(ataAddress: string, side: "depositor" | "escrow") {
83
+ super(
84
+ `Missing ${side} ATA: ${ataAddress}. ` +
85
+ `Settlement mode is Escrow/SPL but the Associated Token Account does not exist. ` +
86
+ `The ${side} must create the ATA before escrow operations.`,
87
+ "SAP_MISSING_ESCROW_ATA",
88
+ );
89
+ this.name = "MissingEscrowAtaError";
90
+ this.ataAddress = ataAddress;
91
+ this.side = side;
92
+ }
93
+ }
94
+
95
+ // ═══════════════════════════════════════════════════════════════════
96
+ // Validation
97
+ // ═══════════════════════════════════════════════════════════════════
98
+
99
+ /** Standard SPL Token program ID. */
100
+ const TOKEN_PROGRAM_ID_STR = "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA";
101
+
102
+ /**
103
+ * @name validateEscrowState
104
+ * @description Validates that an escrow is in a correct state for settlement.
105
+ *
106
+ * Checks:
107
+ * - Escrow PDA exists on-chain
108
+ * - If SPL: depositor ATA exists, escrow ATA exists, token mint matches
109
+ * - Balance >= requested settlement amount
110
+ * - Escrow is not expired
111
+ * - Max calls not exceeded
112
+ *
113
+ * @param connection - Solana RPC connection.
114
+ * @param agentWallet - The agent's wallet public key.
115
+ * @param depositorWallet - The depositor's wallet public key.
116
+ * @param fetchEscrow - Callback to fetch escrow data (avoids coupling to SapProgram).
117
+ * @param opts
118
+ * @param opts.callsToSettle - Number of calls to validate affordability for.
119
+ *
120
+ * @returns A detailed {@link EscrowValidationResult}.
121
+ *
122
+ * @category Utils
123
+ * @since v0.6.4
124
+ *
125
+ * @example
126
+ * ```ts
127
+ * const result = await validateEscrowState(
128
+ * connection,
129
+ * agentWallet,
130
+ * depositorWallet,
131
+ * (pda) => client.escrow.fetchByPda(pda),
132
+ * { callsToSettle: 5 },
133
+ * );
134
+ *
135
+ * if (!result.valid) {
136
+ * console.error("Escrow validation failed:", result.errors);
137
+ * }
138
+ * ```
139
+ */
140
+ export async function validateEscrowState(
141
+ connection: Connection,
142
+ agentWallet: PublicKey,
143
+ depositorWallet: PublicKey,
144
+ fetchEscrow: (escrowPda: PublicKey) => Promise<EscrowAccountData | null>,
145
+ opts?: { callsToSettle?: number },
146
+ ): Promise<EscrowValidationResult> {
147
+ const [agentPda] = deriveAgent(agentWallet);
148
+ const [escrowPda] = deriveEscrow(agentPda, depositorWallet);
149
+ const errors: string[] = [];
150
+
151
+ // 1. Fetch escrow
152
+ const escrow = await fetchEscrow(escrowPda);
153
+ if (!escrow) {
154
+ return {
155
+ valid: false,
156
+ escrow: null,
157
+ escrowPda,
158
+ agentPda,
159
+ isSplEscrow: false,
160
+ splAccounts: [],
161
+ errors: [`Escrow not found at ${escrowPda.toBase58()}`],
162
+ };
163
+ }
164
+
165
+ // 2. Check expiry
166
+ if (escrow.expiresAt.gt(new BN(0))) {
167
+ const now = Math.floor(Date.now() / 1000);
168
+ if (escrow.expiresAt.lt(new BN(now))) {
169
+ errors.push(`Escrow expired at ${escrow.expiresAt.toString()}`);
170
+ }
171
+ }
172
+
173
+ // 3. Check balance / calls
174
+ const callsToSettle = opts?.callsToSettle ?? 1;
175
+ const costForCalls = escrow.pricePerCall.mul(new BN(callsToSettle));
176
+ if (escrow.balance.lt(costForCalls)) {
177
+ errors.push(
178
+ `Insufficient balance: ${escrow.balance.toString()} < ${costForCalls.toString()} (${callsToSettle} calls × ${escrow.pricePerCall.toString()})`,
179
+ );
180
+ }
181
+
182
+ // 4. Check max calls
183
+ if (escrow.maxCalls.gt(new BN(0))) {
184
+ const remaining = escrow.maxCalls.sub(escrow.totalCallsSettled);
185
+ if (remaining.lt(new BN(callsToSettle))) {
186
+ errors.push(
187
+ `Max calls exceeded: ${remaining.toString()} remaining but needs ${callsToSettle}`,
188
+ );
189
+ }
190
+ }
191
+
192
+ // 5. Determine if SPL
193
+ const isSplEscrow =
194
+ escrow.tokenMint !== null &&
195
+ escrow.tokenMint.toBase58() !== "11111111111111111111111111111111";
196
+
197
+ // 6. Build SPL accounts (if SPL escrow)
198
+ const splAccounts: SplAccountMeta[] = [];
199
+ if (isSplEscrow) {
200
+ const mint = escrow.tokenMint!;
201
+ const depositorAta = findATA(depositorWallet, mint);
202
+ const escrowAta = findATA(escrowPda, mint);
203
+
204
+ // Verify depositor ATA exists
205
+ const depositorAtaInfo = await connection.getAccountInfo(depositorAta);
206
+ if (!depositorAtaInfo) {
207
+ errors.push(`Depositor ATA does not exist: ${depositorAta.toBase58()}`);
208
+ }
209
+
210
+ // Verify escrow ATA exists
211
+ const escrowAtaInfo = await connection.getAccountInfo(escrowAta);
212
+ if (!escrowAtaInfo) {
213
+ errors.push(`Escrow ATA does not exist: ${escrowAta.toBase58()}`);
214
+ }
215
+
216
+ // Verify mint matches
217
+ if (depositorAtaInfo) {
218
+ // SPL token account data: bytes 0-32 = mint
219
+ const ataMint = depositorAtaInfo.data.subarray(0, 32);
220
+ if (Buffer.from(ataMint).toString("hex") !== mint.toBuffer().toString("hex")) {
221
+ errors.push(`Depositor ATA mint mismatch: expected ${mint.toBase58()}`);
222
+ }
223
+ }
224
+
225
+ splAccounts.push(
226
+ { kind: "depositorAta", pubkey: depositorAta, writable: true },
227
+ { kind: "escrowAta", pubkey: escrowAta, writable: true },
228
+ { kind: "tokenMint", pubkey: mint, writable: false },
229
+ { kind: "tokenProgram", pubkey: new PublicKey(TOKEN_PROGRAM_ID_STR), writable: false },
230
+ );
231
+ }
232
+
233
+ return {
234
+ valid: errors.length === 0,
235
+ escrow,
236
+ escrowPda,
237
+ agentPda,
238
+ isSplEscrow,
239
+ splAccounts,
240
+ errors,
241
+ };
242
+ }
243
+
244
+ /**
245
+ * @name attachSplAccounts
246
+ * @description Build the typed `SplAccountMeta[]` for an SPL token escrow operation.
247
+ * Does NOT validate existence — use {@link validateEscrowState} for full validation.
248
+ *
249
+ * @param escrowPda - The escrow PDA address.
250
+ * @param depositorWallet - The depositor's wallet public key.
251
+ * @param tokenMint - The SPL token mint.
252
+ * @returns An array of typed {@link SplAccountMeta} for SPL escrow operations.
253
+ *
254
+ * @category Utils
255
+ * @since v0.6.4
256
+ *
257
+ * @example
258
+ * ```ts
259
+ * const splMetas = attachSplAccounts(escrowPda, depositorWallet, usdcMint);
260
+ *
261
+ * // Convert to Anchor-compatible AccountMeta[]
262
+ * const accountMetas = splMetas.map(m => ({
263
+ * pubkey: m.pubkey,
264
+ * isWritable: m.writable,
265
+ * isSigner: false,
266
+ * }));
267
+ *
268
+ * await client.escrow.settle(depositor, calls, hash, accountMetas);
269
+ * ```
270
+ */
271
+ export function attachSplAccounts(
272
+ escrowPda: PublicKey,
273
+ depositorWallet: PublicKey,
274
+ tokenMint: PublicKey,
275
+ ): SplAccountMeta[] {
276
+ return [
277
+ { kind: "depositorAta", pubkey: findATA(depositorWallet, tokenMint), writable: true },
278
+ { kind: "escrowAta", pubkey: findATA(escrowPda, tokenMint), writable: true },
279
+ { kind: "tokenMint", pubkey: tokenMint, writable: false },
280
+ { kind: "tokenProgram", pubkey: new PublicKey(TOKEN_PROGRAM_ID_STR), writable: false },
281
+ ];
282
+ }
283
+
284
+ /**
285
+ * @name toAccountMetas
286
+ * @description Convert typed {@link SplAccountMeta} to Anchor-compatible
287
+ * `AccountMeta[]` for use with `.remainingAccounts()`.
288
+ *
289
+ * @param splMetas - Array of typed SPL account metas.
290
+ * @returns `AccountMeta[]` compatible with Anchor's `remainingAccounts`.
291
+ *
292
+ * @category Utils
293
+ * @since v0.6.4
294
+ */
295
+ export function toAccountMetas(splMetas: SplAccountMeta[]): AccountMeta[] {
296
+ return splMetas.map((m) => ({
297
+ pubkey: m.pubkey,
298
+ isWritable: m.writable,
299
+ isSigner: false,
300
+ }));
301
+ }
@@ -71,3 +71,31 @@ export type {
71
71
  PriorityFeeConfig,
72
72
  SettleOptions,
73
73
  } from "./priority-fee";
74
+
75
+ // ── v0.6.4 Escrow Validation & Merchant Middleware ──
76
+ export {
77
+ validateEscrowState,
78
+ attachSplAccounts,
79
+ toAccountMetas,
80
+ MissingEscrowAtaError,
81
+ } from "./escrow-validation";
82
+ export type {
83
+ SplAccountMeta,
84
+ EscrowValidationResult,
85
+ } from "./escrow-validation";
86
+
87
+ export {
88
+ SapMerchantValidator,
89
+ parseX402Headers,
90
+ } from "./merchant-validator";
91
+ export type {
92
+ ParsedX402Headers,
93
+ MerchantValidationResult,
94
+ } from "./merchant-validator";
95
+
96
+ export { getX402DirectPayments } from "./x402-direct";
97
+ export type {
98
+ X402DirectPayment,
99
+ SettlementPayload,
100
+ GetX402DirectOptions,
101
+ } from "./x402-direct";