@brainai/satp-client 2.0.2 → 2.0.4

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/src/v3-sdk.js CHANGED
@@ -20,6 +20,11 @@ const {
20
20
  getV3ReviewCounterPDA,
21
21
  getV3AttestationPDA,
22
22
  getV3EscrowPDA,
23
+ getAssociatedTokenAddress,
24
+ getV3EscrowVaultATA,
25
+ V3_DEVNET_TOKEN_MINTS,
26
+ SPL_TOKEN_PROGRAM_ID,
27
+ ASSOCIATED_TOKEN_PROGRAM_ID,
23
28
  } = require('./v3-pda');
24
29
 
25
30
  const DEVNET_RPC = 'https://api.devnet.solana.com';
@@ -37,13 +42,10 @@ function normalizeSDKOptions(opts = {}) {
37
42
  : { rpcUrl: opts };
38
43
  }
39
44
  const rpcUrl = normalized.rpcUrl || normalized.url || normalized.endpoint;
40
- if (!normalized.network && isMainnetRpc(rpcUrl)) {
41
- throw new Error('Mainnet RPC requires network=mainnet, but SATP V3 mainnet program IDs are not configured');
42
- }
43
45
  return {
44
46
  ...normalized,
45
47
  rpcUrl,
46
- network: normalized.network || 'devnet',
48
+ network: normalized.network || (isMainnetRpc(rpcUrl) ? 'mainnet' : 'devnet'),
47
49
  };
48
50
  }
49
51
 
@@ -79,6 +81,93 @@ function serializeVecString(arr) {
79
81
  return Buffer.concat([count, ...parts]);
80
82
  }
81
83
 
84
+ function hashDescription(descriptionOrHash) {
85
+ return Buffer.isBuffer(descriptionOrHash) && descriptionOrHash.length === 32
86
+ ? descriptionOrHash
87
+ : crypto.createHash('sha256')
88
+ .update(typeof descriptionOrHash === 'string' ? descriptionOrHash : Buffer.from(descriptionOrHash))
89
+ .digest();
90
+ }
91
+
92
+ function normalizeCurrency(currency) {
93
+ const value = String(currency || 'SOL').toUpperCase();
94
+ if (value !== 'SOL' && value !== 'USDC') {
95
+ throw new Error('Unsupported escrow currency: expected SOL or USDC');
96
+ }
97
+ return value;
98
+ }
99
+
100
+ function resolveTokenMint(currency, mint) {
101
+ if (mint) return new PublicKey(mint);
102
+ if (currency === 'USDC') return V3_DEVNET_TOKEN_MINTS.USDC;
103
+ return null;
104
+ }
105
+
106
+ function createAssociatedTokenAccountIdempotentInstruction(payer, owner, mint) {
107
+ const payerKey = new PublicKey(payer);
108
+ const ownerKey = new PublicKey(owner);
109
+ const mintKey = new PublicKey(mint);
110
+ const [ata] = getAssociatedTokenAddress(ownerKey, mintKey);
111
+
112
+ return new TransactionInstruction({
113
+ programId: ASSOCIATED_TOKEN_PROGRAM_ID,
114
+ keys: [
115
+ { pubkey: payerKey, isSigner: true, isWritable: true },
116
+ { pubkey: ata, isSigner: false, isWritable: true },
117
+ { pubkey: ownerKey, isSigner: false, isWritable: false },
118
+ { pubkey: mintKey, isSigner: false, isWritable: false },
119
+ { pubkey: SystemProgram.programId, isSigner: false, isWritable: false },
120
+ { pubkey: SPL_TOKEN_PROGRAM_ID, isSigner: false, isWritable: false },
121
+ ],
122
+ data: Buffer.from([1]), // SPL Associated Token Account: CreateIdempotent
123
+ });
124
+ }
125
+
126
+ function normalizeTokenOpts(opts = {}) {
127
+ const currency = normalizeCurrency(opts.currency);
128
+ const mint = resolveTokenMint(currency, opts.mint);
129
+ return {
130
+ currency,
131
+ mint,
132
+ tokenDecimals: opts.tokenDecimals == null ? 6 : opts.tokenDecimals,
133
+ };
134
+ }
135
+
136
+ function maybeAddAtaInstruction(tx, enabled, payer, owner, mint) {
137
+ if (enabled) {
138
+ tx.add(createAssociatedTokenAccountIdempotentInstruction(payer, owner, mint));
139
+ }
140
+ }
141
+
142
+ function readOptionPubkey(data, offset) {
143
+ const hasValue = data[offset] === 1;
144
+ offset += 1;
145
+ if (!hasValue) return { value: null, offset };
146
+ const value = new PublicKey(data.slice(offset, offset + 32)).toBase58();
147
+ return { value, offset: offset + 32 };
148
+ }
149
+
150
+ function readOptionU8(data, offset) {
151
+ const hasValue = data[offset] === 1;
152
+ offset += 1;
153
+ if (!hasValue) return { value: null, offset };
154
+ return { value: data[offset], offset: offset + 1 };
155
+ }
156
+
157
+ function readOptionBytes32(data, offset) {
158
+ const hasValue = data[offset] === 1;
159
+ offset += 1;
160
+ if (!hasValue) return { value: null, offset };
161
+ return { value: Buffer.from(data.slice(offset, offset + 32)).toString('hex'), offset: offset + 32 };
162
+ }
163
+
164
+ function readOptionI64(data, offset) {
165
+ const hasValue = data[offset] === 1;
166
+ offset += 1;
167
+ if (!hasValue) return { value: null, offset };
168
+ return { value: Number(data.readBigInt64LE(offset)), offset: offset + 8 };
169
+ }
170
+
82
171
  class SATPV3SDK {
83
172
  /**
84
173
  * @param {object} opts
@@ -1084,11 +1173,12 @@ class SATPV3SDK {
1084
1173
  * @param {PublicKey|string} client - Client wallet (signer + payer)
1085
1174
  * @param {PublicKey|string} agentWallet - Agent's wallet to receive funds
1086
1175
  * @param {string} agentId - Agent identifier (for Genesis Record lookup)
1087
- * @param {number} amount - Lamports to escrow
1176
+ * @param {number} amount - Lamports or token base units to escrow
1088
1177
  * @param {string|Buffer} descriptionOrHash - Job description string (will be SHA-256 hashed) or 32-byte hash buffer
1089
1178
  * @param {number} deadline - Unix timestamp deadline
1090
1179
  * @param {number} [nonce=0] - Nonce for uniqueness (multiple escrows between same parties)
1091
- * @param {object} [opts={}] - Optional trust requirements
1180
+ * @param {object} [opts={}] - Optional trust requirements and currency selection
1181
+ * @param {'SOL'|'USDC'} [opts.currency='SOL'] - Escrow funding currency
1092
1182
  * @param {number} [opts.minVerificationLevel=0] - Minimum verification level (0-5)
1093
1183
  * @param {boolean} [opts.requireBorn=false] - Require agent to have completed burn-to-become
1094
1184
  * @param {PublicKey|string} [opts.arbiter] - Arbiter for dispute resolution (defaults to client)
@@ -1097,9 +1187,20 @@ class SATPV3SDK {
1097
1187
  async buildCreateEscrow(client, agentWallet, agentId, amount, descriptionOrHash, deadline, nonce = 0, opts = {}) {
1098
1188
  const clientKey = new PublicKey(client);
1099
1189
  const agentWalletKey = new PublicKey(agentWallet);
1100
- const descriptionHash = Buffer.isBuffer(descriptionOrHash) && descriptionOrHash.length === 32
1101
- ? descriptionOrHash
1102
- : crypto.createHash('sha256').update(typeof descriptionOrHash === 'string' ? descriptionOrHash : Buffer.from(descriptionOrHash)).digest();
1190
+ const descriptionHash = hashDescription(descriptionOrHash);
1191
+ const currency = normalizeCurrency(opts.currency);
1192
+ if (currency === 'USDC') {
1193
+ return this.buildCreateUsdcEscrow(
1194
+ clientKey,
1195
+ agentWalletKey,
1196
+ agentId,
1197
+ amount,
1198
+ descriptionHash,
1199
+ deadline,
1200
+ nonce,
1201
+ opts
1202
+ );
1203
+ }
1103
1204
 
1104
1205
  const [escrowPDA] = getV3EscrowPDA(clientKey, descriptionHash, nonce, this.network);
1105
1206
  const [agentIdentityPDA] = getGenesisPDA(agentId, this.network);
@@ -1150,6 +1251,135 @@ class SATPV3SDK {
1150
1251
  return { transaction: tx, escrowPDA, descriptionHash };
1151
1252
  }
1152
1253
 
1254
+ /**
1255
+ * Build createEscrow transaction for USDC/SPL token escrow.
1256
+ * Includes idempotent vault ATA creation by default; client source ATA must
1257
+ * exist and hold enough token base units for transfer_checked to succeed.
1258
+ * @param {PublicKey|string} client - Client wallet (signer + payer)
1259
+ * @param {PublicKey|string} agentWallet - Agent's wallet to receive funds
1260
+ * @param {string} agentId - Agent identifier (for Genesis Record lookup)
1261
+ * @param {number} amount - Token base units to escrow
1262
+ * @param {string|Buffer} descriptionOrHash - Job description or 32-byte hash
1263
+ * @param {number} deadline - Unix timestamp deadline
1264
+ * @param {number} [nonce=0] - Nonce for uniqueness
1265
+ * @param {object} [opts={}]
1266
+ * @param {PublicKey|string} [opts.mint] - SPL mint; defaults to devnet USDC
1267
+ * @param {number} [opts.tokenDecimals=6] - Mint decimals used by transfer_checked
1268
+ * @param {boolean} [opts.createVaultAta=true] - Add idempotent vault ATA creation
1269
+ * @returns {{ transaction: Transaction, escrowPDA: PublicKey, descriptionHash: Buffer, currency: 'USDC', mint: PublicKey, clientTokenAccount: PublicKey, vaultTokenAccount: PublicKey }}
1270
+ */
1271
+ async buildCreateUsdcEscrow(client, agentWallet, agentId, amount, descriptionOrHash, deadline, nonce = 0, opts = {}) {
1272
+ const clientKey = new PublicKey(client);
1273
+ const agentWalletKey = new PublicKey(agentWallet);
1274
+ const descriptionHash = hashDescription(descriptionOrHash);
1275
+ const mintKey = resolveTokenMint('USDC', opts.mint);
1276
+ const tokenDecimals = opts.tokenDecimals == null ? 6 : opts.tokenDecimals;
1277
+ const { escrowPDA, vaultATA } = getV3EscrowVaultATA(clientKey, descriptionHash, nonce, mintKey, this.network);
1278
+ const [agentIdentityPDA] = getGenesisPDA(agentId, this.network);
1279
+ const [clientTokenAccount] = getAssociatedTokenAddress(clientKey, mintKey);
1280
+
1281
+ const arbiterKey = opts.arbiter ? new PublicKey(opts.arbiter) : clientKey;
1282
+ const minVerificationLevel = opts.minVerificationLevel || 0;
1283
+ const requireBorn = opts.requireBorn || false;
1284
+
1285
+ const amountBuf = Buffer.alloc(8);
1286
+ amountBuf.writeBigUInt64LE(BigInt(amount));
1287
+ const deadlineBuf = Buffer.alloc(8);
1288
+ deadlineBuf.writeBigInt64LE(BigInt(deadline));
1289
+ const nonceBuf = Buffer.alloc(8);
1290
+ nonceBuf.writeBigUInt64LE(BigInt(nonce));
1291
+
1292
+ const data = Buffer.concat([
1293
+ anchorDiscriminator('create_usdc_escrow'),
1294
+ serializeString(agentId),
1295
+ amountBuf,
1296
+ descriptionHash,
1297
+ deadlineBuf,
1298
+ nonceBuf,
1299
+ Buffer.from([minVerificationLevel]),
1300
+ Buffer.from([requireBorn ? 1 : 0]),
1301
+ Buffer.from([tokenDecimals]),
1302
+ ]);
1303
+
1304
+ const tx = new Transaction();
1305
+ if (opts.createVaultAta !== false) {
1306
+ tx.add(createAssociatedTokenAccountIdempotentInstruction(clientKey, escrowPDA, mintKey));
1307
+ }
1308
+
1309
+ tx.add(new TransactionInstruction({
1310
+ programId: this.programIds.ESCROW,
1311
+ keys: [
1312
+ { pubkey: clientKey, isSigner: true, isWritable: true },
1313
+ { pubkey: agentWalletKey, isSigner: false, isWritable: false },
1314
+ { pubkey: agentIdentityPDA, isSigner: false, isWritable: false },
1315
+ { pubkey: arbiterKey, isSigner: false, isWritable: false },
1316
+ { pubkey: escrowPDA, isSigner: false, isWritable: true },
1317
+ { pubkey: mintKey, isSigner: false, isWritable: false },
1318
+ { pubkey: clientTokenAccount, isSigner: false, isWritable: true },
1319
+ { pubkey: vaultATA, isSigner: false, isWritable: true },
1320
+ { pubkey: SPL_TOKEN_PROGRAM_ID, isSigner: false, isWritable: false },
1321
+ { pubkey: SystemProgram.programId, isSigner: false, isWritable: false },
1322
+ ],
1323
+ data,
1324
+ }));
1325
+
1326
+ tx.feePayer = clientKey;
1327
+ tx.recentBlockhash = (await this.connection.getLatestBlockhash()).blockhash;
1328
+
1329
+ return {
1330
+ transaction: tx,
1331
+ escrowPDA,
1332
+ descriptionHash,
1333
+ currency: 'USDC',
1334
+ mint: mintKey,
1335
+ clientTokenAccount,
1336
+ vaultTokenAccount: vaultATA,
1337
+ };
1338
+ }
1339
+
1340
+ /**
1341
+ * Build release transaction for USDC/SPL escrow funds.
1342
+ * Adds idempotent agent ATA creation by default so the destination account
1343
+ * exists before the escrow program performs transfer_checked.
1344
+ * @param {PublicKey|string} client
1345
+ * @param {PublicKey|string} agent
1346
+ * @param {PublicKey|string} escrowPDA
1347
+ * @param {object} [opts={}]
1348
+ * @param {PublicKey|string} [opts.mint]
1349
+ * @param {boolean} [opts.createAgentAta=true]
1350
+ * @returns {{ transaction: Transaction, mint: PublicKey, vaultTokenAccount: PublicKey, agentTokenAccount: PublicKey }}
1351
+ */
1352
+ async buildUsdcEscrowRelease(client, agent, escrowPDA, opts = {}) {
1353
+ const clientKey = new PublicKey(client);
1354
+ const agentKey = new PublicKey(agent);
1355
+ const escrowKey = new PublicKey(escrowPDA);
1356
+ const { mint } = normalizeTokenOpts({ ...opts, currency: 'USDC' });
1357
+ const [vaultTokenAccount] = getAssociatedTokenAddress(escrowKey, mint);
1358
+ const [agentTokenAccount] = getAssociatedTokenAddress(agentKey, mint);
1359
+
1360
+ const tx = new Transaction();
1361
+ maybeAddAtaInstruction(tx, opts.createAgentAta !== false, clientKey, agentKey, mint);
1362
+
1363
+ tx.add(new TransactionInstruction({
1364
+ programId: this.programIds.ESCROW,
1365
+ keys: [
1366
+ { pubkey: escrowKey, isSigner: false, isWritable: true },
1367
+ { pubkey: clientKey, isSigner: true, isWritable: false },
1368
+ { pubkey: agentKey, isSigner: false, isWritable: false },
1369
+ { pubkey: mint, isSigner: false, isWritable: false },
1370
+ { pubkey: vaultTokenAccount, isSigner: false, isWritable: true },
1371
+ { pubkey: agentTokenAccount, isSigner: false, isWritable: true },
1372
+ { pubkey: SPL_TOKEN_PROGRAM_ID, isSigner: false, isWritable: false },
1373
+ ],
1374
+ data: anchorDiscriminator('release_usdc'),
1375
+ }));
1376
+
1377
+ tx.feePayer = clientKey;
1378
+ tx.recentBlockhash = (await this.connection.getLatestBlockhash()).blockhash;
1379
+
1380
+ return { transaction: tx, mint, vaultTokenAccount, agentTokenAccount };
1381
+ }
1382
+
1153
1383
  /**
1154
1384
  * Build submitWork transaction (agent submits work proof).
1155
1385
  * @param {PublicKey|string} agent - Agent wallet (signer)
@@ -1190,7 +1420,11 @@ class SATPV3SDK {
1190
1420
  * @param {PublicKey|string} escrowPDA - Escrow account address
1191
1421
  * @returns {{ transaction: Transaction }}
1192
1422
  */
1193
- async buildEscrowRelease(client, agent, escrowPDA) {
1423
+ async buildEscrowRelease(client, agent, escrowPDA, opts = {}) {
1424
+ if (normalizeCurrency(opts.currency) === 'USDC') {
1425
+ return this.buildUsdcEscrowRelease(client, agent, escrowPDA, opts);
1426
+ }
1427
+
1194
1428
  const clientKey = new PublicKey(client);
1195
1429
  const agentKey = new PublicKey(agent);
1196
1430
  const escrowKey = new PublicKey(escrowPDA);
@@ -1223,6 +1457,11 @@ class SATPV3SDK {
1223
1457
  * @returns {{ transaction: Transaction }}
1224
1458
  */
1225
1459
  async buildPartialRelease(client, agent, escrowPDA, amount) {
1460
+ const opts = arguments.length >= 5 ? arguments[4] || {} : {};
1461
+ if (normalizeCurrency(opts.currency) === 'USDC') {
1462
+ return this.buildPartialUsdcEscrowRelease(client, agent, escrowPDA, amount, opts);
1463
+ }
1464
+
1226
1465
  const clientKey = new PublicKey(client);
1227
1466
  const agentKey = new PublicKey(agent);
1228
1467
  const escrowKey = new PublicKey(escrowPDA);
@@ -1250,13 +1489,38 @@ class SATPV3SDK {
1250
1489
  return { transaction: tx };
1251
1490
  }
1252
1491
 
1492
+ /**
1493
+ * Build partial release transaction for USDC/SPL escrow funds.
1494
+ * @param {PublicKey|string} client
1495
+ * @param {PublicKey|string} agent
1496
+ * @param {PublicKey|string} escrowPDA
1497
+ * @param {number} amount
1498
+ * @param {object} [opts={}]
1499
+ * @returns {{ transaction: Transaction, mint: PublicKey, vaultTokenAccount: PublicKey, agentTokenAccount: PublicKey }}
1500
+ */
1501
+ async buildPartialUsdcEscrowRelease(client, agent, escrowPDA, amount, opts = {}) {
1502
+ const result = await this.buildUsdcEscrowRelease(client, agent, escrowPDA, {
1503
+ ...opts,
1504
+ createAgentAta: opts.createAgentAta,
1505
+ });
1506
+ const amountBuf = Buffer.alloc(8);
1507
+ amountBuf.writeBigUInt64LE(BigInt(amount));
1508
+ const ix = result.transaction.instructions[result.transaction.instructions.length - 1];
1509
+ ix.data = Buffer.concat([anchorDiscriminator('partial_release_usdc'), amountBuf]);
1510
+ return result;
1511
+ }
1512
+
1253
1513
  /**
1254
1514
  * Build cancel transaction (client cancels after deadline, gets refund).
1255
1515
  * @param {PublicKey|string} client - Client wallet (signer)
1256
1516
  * @param {PublicKey|string} escrowPDA - Escrow account address
1257
1517
  * @returns {{ transaction: Transaction }}
1258
1518
  */
1259
- async buildCancelEscrow(client, escrowPDA) {
1519
+ async buildCancelEscrow(client, escrowPDA, opts = {}) {
1520
+ if (normalizeCurrency(opts.currency) === 'USDC') {
1521
+ return this.buildUsdcCancelEscrow(client, escrowPDA, opts);
1522
+ }
1523
+
1260
1524
  const clientKey = new PublicKey(client);
1261
1525
  const escrowKey = new PublicKey(escrowPDA);
1262
1526
 
@@ -1278,6 +1542,41 @@ class SATPV3SDK {
1278
1542
  return { transaction: tx };
1279
1543
  }
1280
1544
 
1545
+ /**
1546
+ * Build cancel transaction for USDC/SPL escrow refund.
1547
+ * @param {PublicKey|string} client
1548
+ * @param {PublicKey|string} escrowPDA
1549
+ * @param {object} [opts={}]
1550
+ * @returns {{ transaction: Transaction, mint: PublicKey, vaultTokenAccount: PublicKey, clientTokenAccount: PublicKey }}
1551
+ */
1552
+ async buildUsdcCancelEscrow(client, escrowPDA, opts = {}) {
1553
+ const clientKey = new PublicKey(client);
1554
+ const escrowKey = new PublicKey(escrowPDA);
1555
+ const { mint } = normalizeTokenOpts({ ...opts, currency: 'USDC' });
1556
+ const [vaultTokenAccount] = getAssociatedTokenAddress(escrowKey, mint);
1557
+ const [clientTokenAccount] = getAssociatedTokenAddress(clientKey, mint);
1558
+
1559
+ const tx = new Transaction();
1560
+ maybeAddAtaInstruction(tx, opts.createClientAta === true, clientKey, clientKey, mint);
1561
+ tx.add(new TransactionInstruction({
1562
+ programId: this.programIds.ESCROW,
1563
+ keys: [
1564
+ { pubkey: escrowKey, isSigner: false, isWritable: true },
1565
+ { pubkey: clientKey, isSigner: true, isWritable: true },
1566
+ { pubkey: mint, isSigner: false, isWritable: false },
1567
+ { pubkey: vaultTokenAccount, isSigner: false, isWritable: true },
1568
+ { pubkey: clientTokenAccount, isSigner: false, isWritable: true },
1569
+ { pubkey: SPL_TOKEN_PROGRAM_ID, isSigner: false, isWritable: false },
1570
+ ],
1571
+ data: anchorDiscriminator('cancel_usdc'),
1572
+ }));
1573
+
1574
+ tx.feePayer = clientKey;
1575
+ tx.recentBlockhash = (await this.connection.getLatestBlockhash()).blockhash;
1576
+
1577
+ return { transaction: tx, mint, vaultTokenAccount, clientTokenAccount };
1578
+ }
1579
+
1281
1580
  /**
1282
1581
  * Build raiseDispute transaction (either client or agent).
1283
1582
  * @param {PublicKey|string} signer - Client or agent wallet (signer)
@@ -1321,7 +1620,11 @@ class SATPV3SDK {
1321
1620
  * @param {number} clientAmount - Lamports to refund to client
1322
1621
  * @returns {{ transaction: Transaction }}
1323
1622
  */
1324
- async buildResolveDispute(arbiter, agent, clientWallet, escrowPDA, agentAmount, clientAmount) {
1623
+ async buildResolveDispute(arbiter, agent, clientWallet, escrowPDA, agentAmount, clientAmount, opts = {}) {
1624
+ if (normalizeCurrency(opts.currency) === 'USDC') {
1625
+ return this.buildUsdcResolveDispute(arbiter, agent, clientWallet, escrowPDA, agentAmount, clientAmount, opts);
1626
+ }
1627
+
1325
1628
  const arbiterKey = new PublicKey(arbiter);
1326
1629
  const agentKey = new PublicKey(agent);
1327
1630
  const clientKey = new PublicKey(clientWallet);
@@ -1353,6 +1656,58 @@ class SATPV3SDK {
1353
1656
  return { transaction: tx };
1354
1657
  }
1355
1658
 
1659
+ /**
1660
+ * Build resolveDispute transaction for USDC/SPL escrow funds.
1661
+ * Adds idempotent recipient ATA creation by default.
1662
+ * @param {PublicKey|string} arbiter
1663
+ * @param {PublicKey|string} agent
1664
+ * @param {PublicKey|string} clientWallet
1665
+ * @param {PublicKey|string} escrowPDA
1666
+ * @param {number} agentAmount
1667
+ * @param {number} clientAmount
1668
+ * @param {object} [opts={}]
1669
+ * @returns {{ transaction: Transaction, mint: PublicKey, vaultTokenAccount: PublicKey, agentTokenAccount: PublicKey, clientTokenAccount: PublicKey }}
1670
+ */
1671
+ async buildUsdcResolveDispute(arbiter, agent, clientWallet, escrowPDA, agentAmount, clientAmount, opts = {}) {
1672
+ const arbiterKey = new PublicKey(arbiter);
1673
+ const agentKey = new PublicKey(agent);
1674
+ const clientKey = new PublicKey(clientWallet);
1675
+ const escrowKey = new PublicKey(escrowPDA);
1676
+ const { mint } = normalizeTokenOpts({ ...opts, currency: 'USDC' });
1677
+ const [vaultTokenAccount] = getAssociatedTokenAddress(escrowKey, mint);
1678
+ const [agentTokenAccount] = getAssociatedTokenAddress(agentKey, mint);
1679
+ const [clientTokenAccount] = getAssociatedTokenAddress(clientKey, mint);
1680
+
1681
+ const agentAmtBuf = Buffer.alloc(8);
1682
+ agentAmtBuf.writeBigUInt64LE(BigInt(agentAmount));
1683
+ const clientAmtBuf = Buffer.alloc(8);
1684
+ clientAmtBuf.writeBigUInt64LE(BigInt(clientAmount));
1685
+
1686
+ const tx = new Transaction();
1687
+ maybeAddAtaInstruction(tx, opts.createAgentAta !== false, arbiterKey, agentKey, mint);
1688
+ maybeAddAtaInstruction(tx, opts.createClientAta !== false, arbiterKey, clientKey, mint);
1689
+ tx.add(new TransactionInstruction({
1690
+ programId: this.programIds.ESCROW,
1691
+ keys: [
1692
+ { pubkey: escrowKey, isSigner: false, isWritable: true },
1693
+ { pubkey: arbiterKey, isSigner: true, isWritable: false },
1694
+ { pubkey: agentKey, isSigner: false, isWritable: false },
1695
+ { pubkey: clientKey, isSigner: false, isWritable: false },
1696
+ { pubkey: mint, isSigner: false, isWritable: false },
1697
+ { pubkey: vaultTokenAccount, isSigner: false, isWritable: true },
1698
+ { pubkey: agentTokenAccount, isSigner: false, isWritable: true },
1699
+ { pubkey: clientTokenAccount, isSigner: false, isWritable: true },
1700
+ { pubkey: SPL_TOKEN_PROGRAM_ID, isSigner: false, isWritable: false },
1701
+ ],
1702
+ data: Buffer.concat([anchorDiscriminator('resolve_dispute_usdc'), agentAmtBuf, clientAmtBuf]),
1703
+ }));
1704
+
1705
+ tx.feePayer = arbiterKey;
1706
+ tx.recentBlockhash = (await this.connection.getLatestBlockhash()).blockhash;
1707
+
1708
+ return { transaction: tx, mint, vaultTokenAccount, agentTokenAccount, clientTokenAccount };
1709
+ }
1710
+
1356
1711
  /**
1357
1712
  * Build extendDeadline transaction (client extends escrow deadline).
1358
1713
  * Only when Active. New deadline must be strictly after current.
@@ -1438,6 +1793,10 @@ class SATPV3SDK {
1438
1793
  const descriptionHash = data.slice(offset, offset + 32); offset += 32;
1439
1794
  const deadline = Number(data.readBigInt64LE(offset)); offset += 8;
1440
1795
  const nonce = Number(data.readBigUInt64LE(offset)); offset += 8;
1796
+ const currencyByte = data[offset]; offset += 1;
1797
+ const tokenMintResult = readOptionPubkey(data, offset); offset = tokenMintResult.offset;
1798
+ const tokenVaultResult = readOptionPubkey(data, offset); offset = tokenVaultResult.offset;
1799
+ const tokenDecimalsResult = readOptionU8(data, offset); offset = tokenDecimalsResult.offset;
1441
1800
  const statusByte = data[offset]; offset += 1;
1442
1801
  const minVerificationLevel = data[offset]; offset += 1;
1443
1802
  const requireBorn = data[offset] === 1; offset += 1;
@@ -1487,6 +1846,7 @@ class SATPV3SDK {
1487
1846
  const bump = data[offset]; offset += 1;
1488
1847
 
1489
1848
  const STATUS_MAP = ['Active', 'WorkSubmitted', 'Released', 'Cancelled', 'Disputed', 'Resolved'];
1849
+ const CURRENCY_MAP = ['SOL', 'USDC'];
1490
1850
 
1491
1851
  return {
1492
1852
  pda: escrowKey.toBase58(),
@@ -1499,6 +1859,11 @@ class SATPV3SDK {
1499
1859
  descriptionHash: Buffer.from(descriptionHash).toString('hex'),
1500
1860
  deadline,
1501
1861
  nonce,
1862
+ currency: CURRENCY_MAP[currencyByte] || `Unknown(${currencyByte})`,
1863
+ currencyCode: currencyByte,
1864
+ tokenMint: tokenMintResult.value,
1865
+ tokenVault: tokenVaultResult.value,
1866
+ tokenDecimals: tokenDecimalsResult.value,
1502
1867
  status: STATUS_MAP[statusByte] || `Unknown(${statusByte})`,
1503
1868
  statusCode: statusByte,
1504
1869
  minVerificationLevel,
@@ -1623,7 +1988,7 @@ class SATPV3SDK {
1623
1988
  const faceMint = new PublicKey(data.slice(offset, offset + 32)); offset += 32;
1624
1989
  const faceBurnTx = readString();
1625
1990
  const genesisRecord = Number(data.readBigInt64LE(offset)); offset += 8;
1626
- // No isActive on current deployed program GTppU4E44BqXTQg...
1991
+ const isActive = data[offset] === 1; offset += 1;
1627
1992
  const authority = new PublicKey(data.slice(offset, offset + 32)); offset += 32;
1628
1993
 
1629
1994
  // Option<Pubkey> — Borsh: 0x00 = None (1 byte only), 0x01 + 32 bytes = Some
@@ -1657,6 +2022,7 @@ class SATPV3SDK {
1657
2022
  faceBurnTx: faceBurnTx || null,
1658
2023
  genesisRecord,
1659
2024
  isBorn,
2025
+ isActive,
1660
2026
  authority: authority.toBase58(),
1661
2027
  pendingAuthority,
1662
2028
  reputationScore,