@oobe-protocol-labs/synapse-sap-sdk 0.9.3 → 0.10.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/dist/cjs/constants/index.js +9 -1
  2. package/dist/cjs/constants/index.js.map +1 -1
  3. package/dist/cjs/constants/payments.js +87 -0
  4. package/dist/cjs/constants/payments.js.map +1 -0
  5. package/dist/cjs/constants/seeds.js +9 -1
  6. package/dist/cjs/constants/seeds.js.map +1 -1
  7. package/dist/cjs/idl/synapse_agent_sap.json +270 -2
  8. package/dist/cjs/index.js +13 -2
  9. package/dist/cjs/index.js.map +1 -1
  10. package/dist/cjs/modules/escrow-v2.js +10 -0
  11. package/dist/cjs/modules/escrow-v2.js.map +1 -1
  12. package/dist/cjs/modules/escrow.js +29 -4
  13. package/dist/cjs/modules/escrow.js.map +1 -1
  14. package/dist/cjs/pda/index.js +31 -1
  15. package/dist/cjs/pda/index.js.map +1 -1
  16. package/dist/cjs/plugin/index.js +3 -1
  17. package/dist/cjs/plugin/index.js.map +1 -1
  18. package/dist/cjs/utils/hash.js +52 -1
  19. package/dist/cjs/utils/hash.js.map +1 -1
  20. package/dist/cjs/utils/index.js +2 -1
  21. package/dist/cjs/utils/index.js.map +1 -1
  22. package/dist/esm/constants/index.js +2 -0
  23. package/dist/esm/constants/index.js.map +1 -1
  24. package/dist/esm/constants/payments.js +82 -0
  25. package/dist/esm/constants/payments.js.map +1 -0
  26. package/dist/esm/constants/seeds.js +9 -1
  27. package/dist/esm/constants/seeds.js.map +1 -1
  28. package/dist/esm/idl/synapse_agent_sap.json +270 -2
  29. package/dist/esm/index.js +4 -2
  30. package/dist/esm/index.js.map +1 -1
  31. package/dist/esm/modules/escrow-v2.js +11 -1
  32. package/dist/esm/modules/escrow-v2.js.map +1 -1
  33. package/dist/esm/modules/escrow.js +30 -5
  34. package/dist/esm/modules/escrow.js.map +1 -1
  35. package/dist/esm/pda/index.js +29 -0
  36. package/dist/esm/pda/index.js.map +1 -1
  37. package/dist/esm/plugin/index.js +3 -1
  38. package/dist/esm/plugin/index.js.map +1 -1
  39. package/dist/esm/utils/hash.js +50 -0
  40. package/dist/esm/utils/hash.js.map +1 -1
  41. package/dist/esm/utils/index.js +1 -1
  42. package/dist/esm/utils/index.js.map +1 -1
  43. package/dist/types/constants/index.d.ts +1 -0
  44. package/dist/types/constants/index.d.ts.map +1 -1
  45. package/dist/types/constants/payments.d.ts +78 -0
  46. package/dist/types/constants/payments.d.ts.map +1 -0
  47. package/dist/types/constants/seeds.d.ts +9 -1
  48. package/dist/types/constants/seeds.d.ts.map +1 -1
  49. package/dist/types/index.d.ts +3 -2
  50. package/dist/types/index.d.ts.map +1 -1
  51. package/dist/types/modules/escrow-v2.d.ts.map +1 -1
  52. package/dist/types/modules/escrow.d.ts +9 -1
  53. package/dist/types/modules/escrow.d.ts.map +1 -1
  54. package/dist/types/pda/index.d.ts +21 -0
  55. package/dist/types/pda/index.d.ts.map +1 -1
  56. package/dist/types/plugin/index.d.ts.map +1 -1
  57. package/dist/types/utils/hash.d.ts +27 -0
  58. package/dist/types/utils/hash.d.ts.map +1 -1
  59. package/dist/types/utils/index.d.ts +1 -1
  60. package/dist/types/utils/index.d.ts.map +1 -1
  61. package/package.json +1 -1
  62. package/src/constants/index.ts +11 -1
  63. package/src/constants/payments.ts +92 -0
  64. package/src/constants/seeds.ts +9 -1
  65. package/src/idl/synapse_agent_sap.json +270 -2
  66. package/src/index.ts +12 -1
  67. package/src/modules/escrow-v2.ts +15 -0
  68. package/src/modules/escrow.ts +36 -3
  69. package/src/pda/index.ts +39 -0
  70. package/src/plugin/index.ts +2 -0
  71. package/src/utils/hash.ts +56 -0
  72. package/src/utils/index.ts +1 -1
@@ -0,0 +1,92 @@
1
+ /**
2
+ * @module constants/payments
3
+ * @description Payment-token allowlist + agent stake collateral constants
4
+ * mirroring the on-chain v0.10.0 hardening.
5
+ *
6
+ * The on-chain program now accepts only:
7
+ * - **Native SOL** — signalled by `tokenMint = null` on escrow creation.
8
+ * - **USDC mainnet** — `EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v`.
9
+ * - **USDC devnet** — `4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU`.
10
+ *
11
+ * Any other SPL mint passed to `createEscrow` / `createEscrowV2` is
12
+ * rejected on-chain with `PaymentTokenNotAllowed` (error code 6093).
13
+ *
14
+ * Additionally, the agent owner MUST have an `AgentStake` PDA with
15
+ * `staked_amount >= MIN_AGENT_STAKE_LAMPORTS` (0.1 SOL) BEFORE any
16
+ * client can create a new escrow against that agent. Use
17
+ * {@link StakingModule.initStake} + {@link StakingModule.deposit}
18
+ * to satisfy the requirement.
19
+ *
20
+ * @category Constants
21
+ * @since v0.10.0
22
+ */
23
+
24
+ import { PublicKey } from "@solana/web3.js";
25
+
26
+ /**
27
+ * USDC mint on Solana mainnet-beta (Circle).
28
+ *
29
+ * @name USDC_MINT_MAINNET
30
+ * @category Constants
31
+ * @since v0.10.0
32
+ */
33
+ export const USDC_MINT_MAINNET = new PublicKey(
34
+ "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
35
+ );
36
+
37
+ /**
38
+ * USDC mint on Solana devnet (Circle test mint).
39
+ *
40
+ * @name USDC_MINT_DEVNET
41
+ * @category Constants
42
+ * @since v0.10.0
43
+ */
44
+ export const USDC_MINT_DEVNET = new PublicKey(
45
+ "4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU",
46
+ );
47
+
48
+ /**
49
+ * Minimum agent stake required to gate escrow creation, expressed in lamports.
50
+ * Mirrors `AgentStake::MIN_STAKE` on-chain (`100_000_000` = 0.1 SOL).
51
+ *
52
+ * @name MIN_AGENT_STAKE_LAMPORTS
53
+ * @category Constants
54
+ * @since v0.10.0
55
+ */
56
+ export const MIN_AGENT_STAKE_LAMPORTS = BigInt(100_000_000);
57
+
58
+ /**
59
+ * Maximum delegate duration (seconds) accepted by `add_vault_delegate`.
60
+ * Mirrors `VaultDelegate::MAX_DELEGATE_DURATION_SECS` on-chain
61
+ * (`365 * 86_400` = 1 year).
62
+ *
63
+ * @name MAX_DELEGATE_DURATION_SECS
64
+ * @category Constants
65
+ * @since v0.10.0
66
+ */
67
+ export const MAX_DELEGATE_DURATION_SECS = 365 * 86_400;
68
+
69
+ /**
70
+ * Returns true when `mint` is an accepted USDC mint (mainnet or devnet).
71
+ *
72
+ * @name isAcceptedUsdcMint
73
+ * @param mint - Candidate SPL token mint.
74
+ * @category Constants
75
+ * @since v0.10.0
76
+ */
77
+ export function isAcceptedUsdcMint(mint: PublicKey): boolean {
78
+ return mint.equals(USDC_MINT_MAINNET) || mint.equals(USDC_MINT_DEVNET);
79
+ }
80
+
81
+ /**
82
+ * Returns true when the (optional) `tokenMint` is acceptable as escrow payment.
83
+ * `null` represents native SOL and is always accepted.
84
+ *
85
+ * @name isAcceptedPaymentToken
86
+ * @param tokenMint - The mint passed to `createEscrow*`. `null` = SOL.
87
+ * @category Constants
88
+ * @since v0.10.0
89
+ */
90
+ export function isAcceptedPaymentToken(tokenMint: PublicKey | null): boolean {
91
+ return tokenMint === null || isAcceptedUsdcMint(tokenMint);
92
+ }
@@ -61,7 +61,15 @@ export const SEEDS = {
61
61
  SUBSCRIPTION: "sap_sub",
62
62
  SHARD: "sap_shard",
63
63
  INDEX_PAGE: "sap_idx_page",
64
- /** @since v0.7.0 — Receipt batch merkle root PDA */
64
+ /**
65
+ * @since v0.10.0 — Anti-replay receipt PDA bound to (escrow, service_hash) or (escrow, batch_root).
66
+ * Created via `init` on every settle_calls / settle_batch / settle_calls_v2; replays fail.
67
+ */
68
+ SETTLEMENT_RECEIPT: "sap_recv",
69
+ /**
70
+ * @deprecated v0.10.0 — Was reserved for receipt batch merkle root (never used on-chain).
71
+ * Use {@link SEEDS.SETTLEMENT_RECEIPT} (`sap_recv`) instead. Will be removed in v0.11.0.
72
+ */
65
73
  RECEIPT: "sap_receipt",
66
74
  } as const;
67
75
 
@@ -2,7 +2,7 @@
2
2
  "address": "SAPpUhsWLJG1FfkGRcXagEDMrMsWGjbky7AyhGpFETZ",
3
3
  "metadata": {
4
4
  "name": "synapse_agent_sap",
5
- "version": "0.1.0",
5
+ "version": "0.2.0",
6
6
  "spec": "0.1.0",
7
7
  "description": "Created with Anchor"
8
8
  },
@@ -2693,6 +2693,37 @@
2693
2693
  "Agent to prepay for — anyone can create escrow"
2694
2694
  ]
2695
2695
  },
2696
+ {
2697
+ "name": "agent_stake",
2698
+ "docs": [
2699
+ "v0.10 hardening: agent MUST hold at least `AgentStake::MIN_STAKE`",
2700
+ "in collateral before any new escrow can be opened. This guarantees",
2701
+ "every accepted client has a slashable bond to claim against in case",
2702
+ "of dispute."
2703
+ ],
2704
+ "pda": {
2705
+ "seeds": [
2706
+ {
2707
+ "kind": "const",
2708
+ "value": [
2709
+ 115,
2710
+ 97,
2711
+ 112,
2712
+ 95,
2713
+ 115,
2714
+ 116,
2715
+ 97,
2716
+ 107,
2717
+ 101
2718
+ ]
2719
+ },
2720
+ {
2721
+ "kind": "account",
2722
+ "path": "agent"
2723
+ }
2724
+ ]
2725
+ }
2726
+ },
2696
2727
  {
2697
2728
  "name": "escrow",
2698
2729
  "writable": true,
@@ -2792,6 +2823,35 @@
2792
2823
  {
2793
2824
  "name": "agent"
2794
2825
  },
2826
+ {
2827
+ "name": "agent_stake",
2828
+ "docs": [
2829
+ "v0.10 hardening: agent MUST have an active stake ≥ MIN_STAKE",
2830
+ "before any new escrow can be opened."
2831
+ ],
2832
+ "pda": {
2833
+ "seeds": [
2834
+ {
2835
+ "kind": "const",
2836
+ "value": [
2837
+ 115,
2838
+ 97,
2839
+ 112,
2840
+ 95,
2841
+ 115,
2842
+ 116,
2843
+ 97,
2844
+ 107,
2845
+ 101
2846
+ ]
2847
+ },
2848
+ {
2849
+ "kind": "account",
2850
+ "path": "agent"
2851
+ }
2852
+ ]
2853
+ }
2854
+ },
2795
2855
  {
2796
2856
  "name": "escrow",
2797
2857
  "writable": true,
@@ -6956,7 +7016,9 @@
6956
7016
  {
6957
7017
  "name": "settle_batch",
6958
7018
  "docs": [
6959
- "Batch settle up to 10 settlements in one TX. Volume curve spans batch."
7019
+ "Batch settle up to 10 settlements in one TX. Volume curve spans batch.",
7020
+ "v0.10: requires `batch_root = sha256(s_0 || s_1 || ... || s_{N-1})`",
7021
+ "to seed the anti-replay receipt PDA."
6960
7022
  ],
6961
7023
  "discriminator": [
6962
7024
  22,
@@ -7056,6 +7118,44 @@
7056
7118
  }
7057
7119
  ]
7058
7120
  }
7121
+ },
7122
+ {
7123
+ "name": "settlement_receipt",
7124
+ "docs": [
7125
+ "v0.10 anti-replay: PDA bound to the batch_root (sha256 of all",
7126
+ "service_hashes in order). Replaying the same batch fails the",
7127
+ "`init` constraint."
7128
+ ],
7129
+ "writable": true,
7130
+ "pda": {
7131
+ "seeds": [
7132
+ {
7133
+ "kind": "const",
7134
+ "value": [
7135
+ 115,
7136
+ 97,
7137
+ 112,
7138
+ 95,
7139
+ 114,
7140
+ 101,
7141
+ 99,
7142
+ 118
7143
+ ]
7144
+ },
7145
+ {
7146
+ "kind": "account",
7147
+ "path": "escrow"
7148
+ },
7149
+ {
7150
+ "kind": "arg",
7151
+ "path": "batch_root"
7152
+ }
7153
+ ]
7154
+ }
7155
+ },
7156
+ {
7157
+ "name": "system_program",
7158
+ "address": "11111111111111111111111111111111"
7059
7159
  }
7060
7160
  ],
7061
7161
  "args": [
@@ -7068,6 +7168,15 @@
7068
7168
  }
7069
7169
  }
7070
7170
  }
7171
+ },
7172
+ {
7173
+ "name": "batch_root",
7174
+ "type": {
7175
+ "array": [
7176
+ "u8",
7177
+ 32
7178
+ ]
7179
+ }
7071
7180
  }
7072
7181
  ]
7073
7182
  },
@@ -7180,6 +7289,45 @@
7180
7289
  }
7181
7290
  ]
7182
7291
  }
7292
+ },
7293
+ {
7294
+ "name": "settlement_receipt",
7295
+ "docs": [
7296
+ "v0.10 anti-replay: an `init` of this PDA fails if the same",
7297
+ "`service_hash` was already used to settle this escrow.",
7298
+ "Receipt PDAs are intentionally NOT closeable to preserve the",
7299
+ "uniqueness invariant for the lifetime of the escrow."
7300
+ ],
7301
+ "writable": true,
7302
+ "pda": {
7303
+ "seeds": [
7304
+ {
7305
+ "kind": "const",
7306
+ "value": [
7307
+ 115,
7308
+ 97,
7309
+ 112,
7310
+ 95,
7311
+ 114,
7312
+ 101,
7313
+ 99,
7314
+ 118
7315
+ ]
7316
+ },
7317
+ {
7318
+ "kind": "account",
7319
+ "path": "escrow"
7320
+ },
7321
+ {
7322
+ "kind": "arg",
7323
+ "path": "service_hash"
7324
+ }
7325
+ ]
7326
+ }
7327
+ },
7328
+ {
7329
+ "name": "system_program",
7330
+ "address": "11111111111111111111111111111111"
7183
7331
  }
7184
7332
  ],
7185
7333
  "args": [
@@ -7312,6 +7460,41 @@
7312
7460
  ]
7313
7461
  }
7314
7462
  },
7463
+ {
7464
+ "name": "settlement_receipt",
7465
+ "docs": [
7466
+ "v0.10 anti-replay: PDA bound to (escrow, service_hash).",
7467
+ "In CoSigned mode this gates the immediate transfer; in",
7468
+ "DisputeWindow mode this gates the pending-amount bump so the",
7469
+ "same `service_hash` cannot be re-applied to inflate pending."
7470
+ ],
7471
+ "writable": true,
7472
+ "pda": {
7473
+ "seeds": [
7474
+ {
7475
+ "kind": "const",
7476
+ "value": [
7477
+ 115,
7478
+ 97,
7479
+ 112,
7480
+ 95,
7481
+ 114,
7482
+ 101,
7483
+ 99,
7484
+ 118
7485
+ ]
7486
+ },
7487
+ {
7488
+ "kind": "account",
7489
+ "path": "escrow"
7490
+ },
7491
+ {
7492
+ "kind": "arg",
7493
+ "path": "service_hash"
7494
+ }
7495
+ ]
7496
+ }
7497
+ },
7315
7498
  {
7316
7499
  "name": "system_program",
7317
7500
  "address": "11111111111111111111111111111111"
@@ -8486,6 +8669,19 @@
8486
8669
  179
8487
8670
  ]
8488
8671
  },
8672
+ {
8673
+ "name": "SettlementReceipt",
8674
+ "discriminator": [
8675
+ 52,
8676
+ 249,
8677
+ 252,
8678
+ 121,
8679
+ 4,
8680
+ 232,
8681
+ 187,
8682
+ 4
8683
+ ]
8684
+ },
8489
8685
  {
8490
8686
  "name": "Subscription",
8491
8687
  "discriminator": [
@@ -9980,6 +10176,41 @@
9980
10176
  "code": 6136,
9981
10177
  "name": "InvalidReceiptProof",
9982
10178
  "msg": "bad receipt proof"
10179
+ },
10180
+ {
10181
+ "code": 6137,
10182
+ "name": "SettlementReplay",
10183
+ "msg": "settlement replay"
10184
+ },
10185
+ {
10186
+ "code": 6138,
10187
+ "name": "PaymentTokenNotAllowed",
10188
+ "msg": "token not allowed"
10189
+ },
10190
+ {
10191
+ "code": 6139,
10192
+ "name": "AgentStakeRequired",
10193
+ "msg": "agent stake required"
10194
+ },
10195
+ {
10196
+ "code": 6140,
10197
+ "name": "DelegateExpiryInvalid",
10198
+ "msg": "delegate expiry invalid"
10199
+ },
10200
+ {
10201
+ "code": 6141,
10202
+ "name": "EscrowNotClosed",
10203
+ "msg": "escrow not closed"
10204
+ },
10205
+ {
10206
+ "code": 6142,
10207
+ "name": "VolumeCurveNotDescending",
10208
+ "msg": "curve not descending"
10209
+ },
10210
+ {
10211
+ "code": 6143,
10212
+ "name": "DuplicateServiceHash",
10213
+ "msg": "dup service hash"
9983
10214
  }
9984
10215
  ],
9985
10216
  "types": [
@@ -13125,6 +13356,43 @@
13125
13356
  ]
13126
13357
  }
13127
13358
  },
13359
+ {
13360
+ "name": "SettlementReceipt",
13361
+ "type": {
13362
+ "kind": "struct",
13363
+ "fields": [
13364
+ {
13365
+ "name": "bump",
13366
+ "type": "u8"
13367
+ },
13368
+ {
13369
+ "name": "escrow",
13370
+ "type": "pubkey"
13371
+ },
13372
+ {
13373
+ "name": "service_hash",
13374
+ "type": {
13375
+ "array": [
13376
+ "u8",
13377
+ 32
13378
+ ]
13379
+ }
13380
+ },
13381
+ {
13382
+ "name": "calls_settled",
13383
+ "type": "u64"
13384
+ },
13385
+ {
13386
+ "name": "amount",
13387
+ "type": "u64"
13388
+ },
13389
+ {
13390
+ "name": "settled_at",
13391
+ "type": "i64"
13392
+ }
13393
+ ]
13394
+ }
13395
+ },
13128
13396
  {
13129
13397
  "name": "SettlementSecurity",
13130
13398
  "docs": [
package/src/index.ts CHANGED
@@ -184,12 +184,23 @@ export {
184
184
  deriveLedger,
185
185
  deriveLedgerPage,
186
186
  deriveReceiptBatch,
187
+ deriveSettlementReceipt,
187
188
  } from "./pda";
188
189
 
189
190
  // ── Utilities ────────────────────────────────────────
190
- export { sha256, hashToArray, assert } from "./utils";
191
+ export { sha256, hashToArray, assert, computeBatchRoot } from "./utils";
191
192
  export { serializeAccount, serializeValue } from "./utils";
192
193
 
194
+ // v0.10 — payment + stake constants
195
+ export {
196
+ USDC_MINT_MAINNET,
197
+ USDC_MINT_DEVNET,
198
+ MIN_AGENT_STAKE_LAMPORTS,
199
+ MAX_DELEGATE_DURATION_SECS,
200
+ isAcceptedUsdcMint,
201
+ isAcceptedPaymentToken,
202
+ } from "./constants";
203
+
193
204
  // v0.6.0 — Network normalizer
194
205
  export {
195
206
  normalizeNetworkId,
@@ -23,6 +23,8 @@ import {
23
23
  deriveEscrowV2,
24
24
  derivePendingSettlement as derivePendingPda,
25
25
  deriveDispute as deriveDisputePda,
26
+ deriveStake,
27
+ deriveSettlementReceipt,
26
28
  } from "../pda";
27
29
  import type {
28
30
  EscrowAccountV2Data,
@@ -35,6 +37,7 @@ import {
35
37
  buildRpcOptions,
36
38
  } from "../utils/priority-fee";
37
39
  import type { SettleOptions } from "../utils/priority-fee";
40
+ import { isAcceptedPaymentToken } from "../constants/payments";
38
41
 
39
42
  /**
40
43
  * @name EscrowV2Module
@@ -83,8 +86,17 @@ export class EscrowV2Module extends BaseModule {
83
86
  args: CreateEscrowV2Args,
84
87
  splAccounts: AccountMeta[] = [],
85
88
  ): Promise<TransactionSignature> {
89
+ // v0.10.0: payment-token allowlist (SOL or USDC only).
90
+ if (!isAcceptedPaymentToken(args.tokenMint ?? null)) {
91
+ throw new Error(
92
+ "createEscrowV2: tokenMint must be null (SOL) or USDC (mainnet/devnet). " +
93
+ "On-chain will reject with PaymentTokenNotAllowed.",
94
+ );
95
+ }
96
+
86
97
  const [agentPda] = deriveAgent(agentWallet);
87
98
  const [escrowPda] = this.deriveEscrow(agentPda, undefined, args.escrowNonce);
99
+ const [stakePda] = deriveStake(agentPda);
88
100
 
89
101
  return this.methods
90
102
  .createEscrowV2(
@@ -104,6 +116,7 @@ export class EscrowV2Module extends BaseModule {
104
116
  .accounts({
105
117
  depositor: this.walletPubkey,
106
118
  agent: agentPda,
119
+ agentStake: stakePda,
107
120
  escrow: escrowPda,
108
121
  systemProgram: SystemProgram.programId,
109
122
  })
@@ -142,6 +155,7 @@ export class EscrowV2Module extends BaseModule {
142
155
  const [agentPda] = deriveAgent(this.walletPubkey);
143
156
  const [escrowPda] = this.deriveEscrow(agentPda, depositorWallet, nonce);
144
157
  const [statsPda] = deriveAgentStats(agentPda);
158
+ const [receiptPda] = deriveSettlementReceipt(escrowPda, serviceHash);
145
159
 
146
160
  const preIxs = buildPriorityFeeIxs(opts);
147
161
  const rpcOpts = buildRpcOptions(opts);
@@ -153,6 +167,7 @@ export class EscrowV2Module extends BaseModule {
153
167
  agent: agentPda,
154
168
  agentStats: statsPda,
155
169
  escrow: escrowPda,
170
+ settlementReceipt: receiptPda,
156
171
  systemProgram: SystemProgram.programId,
157
172
  })
158
173
  .remainingAccounts(splAccounts);
@@ -23,6 +23,8 @@ import {
23
23
  deriveAgent,
24
24
  deriveAgentStats,
25
25
  deriveEscrow,
26
+ deriveStake,
27
+ deriveSettlementReceipt,
26
28
  } from "../pda";
27
29
  import type {
28
30
  EscrowAccountData,
@@ -34,6 +36,8 @@ import {
34
36
  buildRpcOptions,
35
37
  } from "../utils/priority-fee";
36
38
  import type { SettleOptions } from "../utils/priority-fee";
39
+ import { computeBatchRoot, hashToArray } from "../utils/hash";
40
+ import { isAcceptedPaymentToken } from "../constants/payments";
37
41
 
38
42
  /**
39
43
  * @name EscrowModule
@@ -105,9 +109,17 @@ export class EscrowModule extends BaseModule {
105
109
  args: CreateEscrowArgs,
106
110
  splAccounts: AccountMeta[] = [],
107
111
  ): Promise<TransactionSignature> {
112
+ // v0.10.0 client-side guard — surface the on-chain rejection earlier.
113
+ if (!isAcceptedPaymentToken(args.tokenMint ?? null)) {
114
+ throw new Error(
115
+ "createEscrow: tokenMint must be null (SOL) or USDC (mainnet/devnet). " +
116
+ "On-chain will reject with PaymentTokenNotAllowed.",
117
+ );
118
+ }
119
+
108
120
  const [agentPda] = deriveAgent(agentWallet);
109
121
  const [escrowPda] = this.deriveEscrow(agentPda);
110
- const [statsPda] = deriveAgentStats(agentPda);
122
+ const [stakePda] = deriveStake(agentPda);
111
123
 
112
124
  return this.methods
113
125
  .createEscrow(
@@ -122,7 +134,7 @@ export class EscrowModule extends BaseModule {
122
134
  .accounts({
123
135
  depositor: this.walletPubkey,
124
136
  agent: agentPda,
125
- agentStats: statsPda,
137
+ agentStake: stakePda,
126
138
  escrow: escrowPda,
127
139
  systemProgram: SystemProgram.programId,
128
140
  })
@@ -181,6 +193,7 @@ export class EscrowModule extends BaseModule {
181
193
  const [agentPda] = deriveAgent(this.walletPubkey);
182
194
  const [escrowPda] = deriveEscrow(agentPda, depositorWallet);
183
195
  const [statsPda] = deriveAgentStats(agentPda);
196
+ const [receiptPda] = deriveSettlementReceipt(escrowPda, serviceHash);
184
197
 
185
198
  const preIxs = buildPriorityFeeIxs(opts);
186
199
  const rpcOpts = buildRpcOptions(opts);
@@ -192,6 +205,7 @@ export class EscrowModule extends BaseModule {
192
205
  agent: agentPda,
193
206
  agentStats: statsPda,
194
207
  escrow: escrowPda,
208
+ settlementReceipt: receiptPda,
195
209
  systemProgram: SystemProgram.programId,
196
210
  })
197
211
  .remainingAccounts(splAccounts);
@@ -255,17 +269,26 @@ export class EscrowModule extends BaseModule {
255
269
  * @name settleBatch
256
270
  * @description Batch settlement — process up to 10 settlements in a single
257
271
  * transaction. Must be called by the agent owner wallet.
272
+ *
258
273
  * @param depositorWallet - The wallet of the client who funded the escrow.
259
274
  * @param settlements - Array of settlement entries (up to 10).
275
+ * @param batchRoot - Optional pre-computed batch root
276
+ * (`sha256(s_0 || s_1 || ... || s_{N-1})`). When omitted the SDK
277
+ * computes it from `settlements[*].serviceHash`. **Required on-chain
278
+ * since v0.10.0** to seed the anti-replay receipt PDA.
260
279
  * @param splAccounts - Remaining accounts for SPL token transfers. Defaults to `[]`.
261
280
  * @param opts - Optional {@link SettleOptions} for priority fees and RPC tuning.
262
281
  * @returns {Promise<TransactionSignature>} The transaction signature.
282
+ *
263
283
  * @since v0.1.0
264
284
  * @updated v0.6.2 — Added optional `opts` parameter for priority fees.
285
+ * @updated v0.10.0 — Added `batchRoot` argument and required
286
+ * `settlementReceipt` PDA on-chain (anti-replay).
265
287
  */
266
288
  async settleBatch(
267
289
  depositorWallet: PublicKey,
268
290
  settlements: Settlement[],
291
+ batchRoot?: Uint8Array | number[] | null,
269
292
  splAccounts: AccountMeta[] = [],
270
293
  opts?: SettleOptions,
271
294
  ): Promise<TransactionSignature> {
@@ -273,16 +296,26 @@ export class EscrowModule extends BaseModule {
273
296
  const [escrowPda] = deriveEscrow(agentPda, depositorWallet);
274
297
  const [statsPda] = deriveAgentStats(agentPda);
275
298
 
299
+ const rootBytes =
300
+ batchRoot && batchRoot !== null
301
+ ? batchRoot instanceof Uint8Array
302
+ ? batchRoot
303
+ : Uint8Array.from(batchRoot)
304
+ : computeBatchRoot(settlements.map((s) => s.serviceHash));
305
+ const rootArr = hashToArray(rootBytes);
306
+ const [receiptPda] = deriveSettlementReceipt(escrowPda, rootBytes);
307
+
276
308
  const preIxs = buildPriorityFeeIxs(opts);
277
309
  const rpcOpts = buildRpcOptions(opts);
278
310
 
279
311
  let builder = this.methods
280
- .settleBatch(settlements)
312
+ .settleBatch(settlements, rootArr)
281
313
  .accounts({
282
314
  wallet: this.walletPubkey,
283
315
  agent: agentPda,
284
316
  agentStats: statsPda,
285
317
  escrow: escrowPda,
318
+ settlementReceipt: receiptPda,
286
319
  systemProgram: SystemProgram.programId,
287
320
  })
288
321
  .remainingAccounts(splAccounts);
package/src/pda/index.ts CHANGED
@@ -878,3 +878,42 @@ export const deriveReceiptBatch = (
878
878
  ],
879
879
  programId,
880
880
  );
881
+
882
+ /**
883
+ * Derive the **SettlementReceipt** PDA (v0.10.0 anti-replay).
884
+ *
885
+ * Seeds: `["sap_recv", escrow_pda, service_hash_or_batch_root]`
886
+ *
887
+ * Created via `init` on every `settle_calls`, `settle_batch`, and
888
+ * `settle_calls_v2`. The PDA's existence after the first call blocks
889
+ * any future replay of the same `service_hash` (or `batch_root`)
890
+ * against the same escrow.
891
+ *
892
+ * @name deriveSettlementReceipt
893
+ * @description Compute the receipt PDA that gates settlement replay.
894
+ * @param escrowPda - Parent escrow PDA (V1 `EscrowAccount` or V2 `EscrowAccountV2`).
895
+ * @param receiptKey - 32-byte `service_hash` (single settle / V2 settle)
896
+ * or `batch_root = sha256(s_0 || ... || s_{N-1})` (settle_batch).
897
+ * @param programId - Override program ID.
898
+ * @returns {PdaResult} `[pda, bump]` tuple.
899
+ * @category PDA
900
+ * @since v0.10.0
901
+ */
902
+ export const deriveSettlementReceipt = (
903
+ escrowPda: PublicKey,
904
+ receiptKey: Uint8Array | number[] | Buffer,
905
+ programId = SAP_PROGRAM_ID,
906
+ ): PdaResult => {
907
+ const buf = Buffer.isBuffer(receiptKey)
908
+ ? receiptKey
909
+ : Buffer.from(receiptKey as Uint8Array);
910
+ if (buf.length !== 32) {
911
+ throw new Error(
912
+ `deriveSettlementReceipt: receiptKey must be 32 bytes, got ${buf.length}`,
913
+ );
914
+ }
915
+ return findPda(
916
+ [toSeedBuf(SEEDS.SETTLEMENT_RECEIPT), escrowPda.toBuffer(), buf],
917
+ programId,
918
+ );
919
+ };
@@ -782,6 +782,8 @@ async function executeEscrow(client: SapClient, name: string, input: any) {
782
782
  const tx = await client.escrow.settleBatch(
783
783
  new PublicKey(input.depositorWallet),
784
784
  settlements,
785
+ // batchRoot omitted \u2192 SDK auto-derives sha256(s_0 || ... || s_N)
786
+ undefined,
785
787
  [],
786
788
  batchOpts,
787
789
  );