@brainai/satp-client 0.1.0-rc.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.
- package/README.md +515 -0
- package/examples/integration-patterns.js +495 -0
- package/examples/runtime-policy-adapter.js +75 -0
- package/package.json +66 -0
- package/src/attestation-request.js +149 -0
- package/src/borsh-reader.d.ts +213 -0
- package/src/borsh-reader.js +587 -0
- package/src/constants.js +77 -0
- package/src/index.d.ts +420 -0
- package/src/index.js +987 -0
- package/src/pda.js +191 -0
- package/src/runtime-policy-adapter.js +206 -0
- package/src/schema.js +59 -0
- package/src/trust-packet.js +148 -0
- package/src/v3-pda.d.ts +72 -0
- package/src/v3-pda.js +281 -0
- package/src/v3-sdk.d.ts +508 -0
- package/src/v3-sdk.js +1800 -0
- package/src/wallet-control-challenge.js +351 -0
package/src/v3-sdk.js
ADDED
|
@@ -0,0 +1,1800 @@
|
|
|
1
|
+
const {
|
|
2
|
+
Connection,
|
|
3
|
+
PublicKey,
|
|
4
|
+
Transaction,
|
|
5
|
+
TransactionInstruction,
|
|
6
|
+
SystemProgram,
|
|
7
|
+
} = require('@solana/web3.js');
|
|
8
|
+
const crypto = require('crypto');
|
|
9
|
+
const {
|
|
10
|
+
getV3ProgramIds,
|
|
11
|
+
hashAgentId,
|
|
12
|
+
hashName,
|
|
13
|
+
getGenesisPDA,
|
|
14
|
+
getV3ReputationAuthorityPDA,
|
|
15
|
+
getV3ValidationAuthorityPDA,
|
|
16
|
+
getV3MintTrackerPDA,
|
|
17
|
+
getNameRegistryPDA,
|
|
18
|
+
getLinkedWalletPDA,
|
|
19
|
+
getV3ReviewPDA,
|
|
20
|
+
getV3ReviewCounterPDA,
|
|
21
|
+
getV3AttestationPDA,
|
|
22
|
+
getV3EscrowPDA,
|
|
23
|
+
} = require('./v3-pda');
|
|
24
|
+
|
|
25
|
+
const DEVNET_RPC = 'https://api.devnet.solana.com';
|
|
26
|
+
const MAINNET_RPC = 'https://api.mainnet-beta.solana.com';
|
|
27
|
+
|
|
28
|
+
function isMainnetRpc(rpcUrl) {
|
|
29
|
+
return typeof rpcUrl === 'string' && /mainnet/i.test(rpcUrl);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function normalizeSDKOptions(opts = {}) {
|
|
33
|
+
let normalized = opts;
|
|
34
|
+
if (typeof opts === 'string') {
|
|
35
|
+
normalized = opts === 'devnet' || opts === 'mainnet'
|
|
36
|
+
? { network: opts }
|
|
37
|
+
: { rpcUrl: opts };
|
|
38
|
+
}
|
|
39
|
+
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
|
+
return {
|
|
44
|
+
...normalized,
|
|
45
|
+
rpcUrl,
|
|
46
|
+
network: normalized.network || 'devnet',
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Compute Anchor instruction discriminator.
|
|
52
|
+
* @param {string} ixName - e.g. "create_identity"
|
|
53
|
+
* @returns {Buffer} 8-byte discriminator
|
|
54
|
+
*/
|
|
55
|
+
function anchorDiscriminator(ixName) {
|
|
56
|
+
return crypto.createHash('sha256')
|
|
57
|
+
.update(`global:${ixName}`)
|
|
58
|
+
.digest()
|
|
59
|
+
.slice(0, 8);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Serialize a Rust String: 4-byte LE length prefix + UTF-8 bytes.
|
|
64
|
+
*/
|
|
65
|
+
function serializeString(str) {
|
|
66
|
+
const bytes = Buffer.from(str, 'utf8');
|
|
67
|
+
const len = Buffer.alloc(4);
|
|
68
|
+
len.writeUInt32LE(bytes.length);
|
|
69
|
+
return Buffer.concat([len, bytes]);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Serialize a Vec<String>: 4-byte LE count + each string serialized.
|
|
74
|
+
*/
|
|
75
|
+
function serializeVecString(arr) {
|
|
76
|
+
const count = Buffer.alloc(4);
|
|
77
|
+
count.writeUInt32LE(arr.length);
|
|
78
|
+
const parts = arr.map(s => serializeString(s));
|
|
79
|
+
return Buffer.concat([count, ...parts]);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
class SATPV3SDK {
|
|
83
|
+
/**
|
|
84
|
+
* @param {object} opts
|
|
85
|
+
* @param {'mainnet'|'devnet'} [opts.network='devnet']
|
|
86
|
+
* @param {string} [opts.rpcUrl]
|
|
87
|
+
* @param {string} [opts.commitment='confirmed']
|
|
88
|
+
*/
|
|
89
|
+
constructor(opts = {}) {
|
|
90
|
+
const normalized = normalizeSDKOptions(opts);
|
|
91
|
+
this.network = normalized.network;
|
|
92
|
+
this.rpcUrl = normalized.rpcUrl || (this.network === 'mainnet' ? MAINNET_RPC : DEVNET_RPC);
|
|
93
|
+
this.commitment = normalized.commitment || 'confirmed';
|
|
94
|
+
this.connection = new Connection(this.rpcUrl, this.commitment);
|
|
95
|
+
this.programIds = getV3ProgramIds(this.network);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// ═══════════════════════════════════════════════════
|
|
99
|
+
// IDENTITY — Genesis Record CRUD
|
|
100
|
+
// ═══════════════════════════════════════════════════
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Build createIdentity transaction.
|
|
104
|
+
* @param {PublicKey|string} creator - Wallet that pays rent and becomes authority
|
|
105
|
+
* @param {string} agentId - Agent identifier (hashed to derive PDA)
|
|
106
|
+
* @param {object} meta - { name, description, category, capabilities, metadataUri }
|
|
107
|
+
* @returns {{ transaction: Transaction, genesisPDA: PublicKey, agentIdHash: Buffer }}
|
|
108
|
+
*/
|
|
109
|
+
async buildCreateIdentity(creator, agentId, meta) {
|
|
110
|
+
const creatorKey = new PublicKey(creator);
|
|
111
|
+
const agentIdHash = hashAgentId(agentId);
|
|
112
|
+
const [genesisPDA] = getGenesisPDA(agentIdHash, this.network);
|
|
113
|
+
|
|
114
|
+
const disc = anchorDiscriminator('create_identity');
|
|
115
|
+
const data = Buffer.concat([
|
|
116
|
+
disc,
|
|
117
|
+
agentIdHash, // [u8; 32]
|
|
118
|
+
serializeString(meta.name || ''), // String
|
|
119
|
+
serializeString(meta.description || ''), // String
|
|
120
|
+
serializeString(meta.category || ''), // String
|
|
121
|
+
serializeVecString(meta.capabilities || []), // Vec<String>
|
|
122
|
+
serializeString(meta.metadataUri || ''), // String
|
|
123
|
+
]);
|
|
124
|
+
|
|
125
|
+
const ix = new TransactionInstruction({
|
|
126
|
+
programId: this.programIds.IDENTITY,
|
|
127
|
+
keys: [
|
|
128
|
+
{ pubkey: genesisPDA, isSigner: false, isWritable: true },
|
|
129
|
+
{ pubkey: creatorKey, isSigner: true, isWritable: true },
|
|
130
|
+
{ pubkey: SystemProgram.programId, isSigner: false, isWritable: false },
|
|
131
|
+
],
|
|
132
|
+
data,
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
const tx = new Transaction().add(ix);
|
|
136
|
+
tx.feePayer = creatorKey;
|
|
137
|
+
tx.recentBlockhash = (await this.connection.getLatestBlockhash()).blockhash;
|
|
138
|
+
|
|
139
|
+
return { transaction: tx, genesisPDA, agentIdHash };
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Build burnToBecome transaction (agent's permanent face/birth event).
|
|
144
|
+
* @param {PublicKey|string} authority - Genesis record authority (signer)
|
|
145
|
+
* @param {string|Buffer} agentIdOrHash - Agent ID string or 32-byte hash
|
|
146
|
+
* @param {string} faceImage - Arweave/IPFS URL to face image
|
|
147
|
+
* @param {PublicKey|string} faceMint - Soulbound BOA NFT mint address
|
|
148
|
+
* @param {string} faceBurnTx - Burn transaction signature
|
|
149
|
+
* @returns {{ transaction: Transaction }}
|
|
150
|
+
*/
|
|
151
|
+
async buildBurnToBecome(authority, agentIdOrHash, faceImage, faceMint, faceBurnTx) {
|
|
152
|
+
const authorityKey = new PublicKey(authority);
|
|
153
|
+
const [genesisPDA] = getGenesisPDA(agentIdOrHash, this.network);
|
|
154
|
+
const faceMintKey = new PublicKey(faceMint);
|
|
155
|
+
|
|
156
|
+
const disc = anchorDiscriminator('burn_to_become');
|
|
157
|
+
const data = Buffer.concat([
|
|
158
|
+
disc,
|
|
159
|
+
serializeString(faceImage),
|
|
160
|
+
faceMintKey.toBuffer(), // Pubkey (32 bytes)
|
|
161
|
+
serializeString(faceBurnTx),
|
|
162
|
+
]);
|
|
163
|
+
|
|
164
|
+
const ix = new TransactionInstruction({
|
|
165
|
+
programId: this.programIds.IDENTITY,
|
|
166
|
+
keys: [
|
|
167
|
+
{ pubkey: genesisPDA, isSigner: false, isWritable: true },
|
|
168
|
+
{ pubkey: authorityKey, isSigner: true, isWritable: false },
|
|
169
|
+
],
|
|
170
|
+
data,
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
const tx = new Transaction().add(ix);
|
|
174
|
+
tx.feePayer = authorityKey;
|
|
175
|
+
tx.recentBlockhash = (await this.connection.getLatestBlockhash()).blockhash;
|
|
176
|
+
|
|
177
|
+
return { transaction: tx };
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Build updateIdentity transaction.
|
|
182
|
+
* Pass null/undefined for fields to leave unchanged.
|
|
183
|
+
* @param {PublicKey|string} authority
|
|
184
|
+
* @param {string|Buffer} agentIdOrHash
|
|
185
|
+
* @param {object} updates - { name?, description?, category?, capabilities?, metadataUri? }
|
|
186
|
+
* @returns {{ transaction: Transaction }}
|
|
187
|
+
*/
|
|
188
|
+
async buildUpdateIdentity(authority, agentIdOrHash, updates) {
|
|
189
|
+
const authorityKey = new PublicKey(authority);
|
|
190
|
+
const [genesisPDA] = getGenesisPDA(agentIdOrHash, this.network);
|
|
191
|
+
|
|
192
|
+
const disc = anchorDiscriminator('update_identity');
|
|
193
|
+
|
|
194
|
+
// Serialize Option<String> — 0x00 for None, 0x01 + string for Some
|
|
195
|
+
function optString(val) {
|
|
196
|
+
if (val == null) return Buffer.from([0x00]);
|
|
197
|
+
return Buffer.concat([Buffer.from([0x01]), serializeString(val)]);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Serialize Option<Vec<String>>
|
|
201
|
+
function optVecString(val) {
|
|
202
|
+
if (val == null) return Buffer.from([0x00]);
|
|
203
|
+
return Buffer.concat([Buffer.from([0x01]), serializeVecString(val)]);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const data = Buffer.concat([
|
|
207
|
+
disc,
|
|
208
|
+
optString(updates.name),
|
|
209
|
+
optString(updates.description),
|
|
210
|
+
optString(updates.category),
|
|
211
|
+
optVecString(updates.capabilities),
|
|
212
|
+
optString(updates.metadataUri),
|
|
213
|
+
]);
|
|
214
|
+
|
|
215
|
+
const ix = new TransactionInstruction({
|
|
216
|
+
programId: this.programIds.IDENTITY,
|
|
217
|
+
keys: [
|
|
218
|
+
{ pubkey: genesisPDA, isSigner: false, isWritable: true },
|
|
219
|
+
{ pubkey: authorityKey, isSigner: true, isWritable: false },
|
|
220
|
+
],
|
|
221
|
+
data,
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
const tx = new Transaction().add(ix);
|
|
225
|
+
tx.feePayer = authorityKey;
|
|
226
|
+
tx.recentBlockhash = (await this.connection.getLatestBlockhash()).blockhash;
|
|
227
|
+
|
|
228
|
+
return { transaction: tx };
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Build proposeAuthority transaction (2-step rotation).
|
|
233
|
+
* @param {PublicKey|string} authority - Current authority (signer)
|
|
234
|
+
* @param {string|Buffer} agentIdOrHash
|
|
235
|
+
* @param {PublicKey|string} newAuthority
|
|
236
|
+
* @returns {{ transaction: Transaction }}
|
|
237
|
+
*/
|
|
238
|
+
async buildProposeAuthority(authority, agentIdOrHash, newAuthority) {
|
|
239
|
+
const authorityKey = new PublicKey(authority);
|
|
240
|
+
const newAuthKey = new PublicKey(newAuthority);
|
|
241
|
+
const [genesisPDA] = getGenesisPDA(agentIdOrHash, this.network);
|
|
242
|
+
|
|
243
|
+
const disc = anchorDiscriminator('propose_authority');
|
|
244
|
+
const data = Buffer.concat([disc, newAuthKey.toBuffer()]);
|
|
245
|
+
|
|
246
|
+
const ix = new TransactionInstruction({
|
|
247
|
+
programId: this.programIds.IDENTITY,
|
|
248
|
+
keys: [
|
|
249
|
+
{ pubkey: genesisPDA, isSigner: false, isWritable: true },
|
|
250
|
+
{ pubkey: authorityKey, isSigner: true, isWritable: false },
|
|
251
|
+
],
|
|
252
|
+
data,
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
const tx = new Transaction().add(ix);
|
|
256
|
+
tx.feePayer = authorityKey;
|
|
257
|
+
tx.recentBlockhash = (await this.connection.getLatestBlockhash()).blockhash;
|
|
258
|
+
|
|
259
|
+
return { transaction: tx };
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Build acceptAuthority transaction.
|
|
264
|
+
* @param {PublicKey|string} newAuthority - Pending authority (signer)
|
|
265
|
+
* @param {string|Buffer} agentIdOrHash
|
|
266
|
+
* @returns {{ transaction: Transaction }}
|
|
267
|
+
*/
|
|
268
|
+
async buildAcceptAuthority(newAuthority, agentIdOrHash) {
|
|
269
|
+
const newAuthKey = new PublicKey(newAuthority);
|
|
270
|
+
const [genesisPDA] = getGenesisPDA(agentIdOrHash, this.network);
|
|
271
|
+
|
|
272
|
+
const disc = anchorDiscriminator('accept_authority');
|
|
273
|
+
|
|
274
|
+
const ix = new TransactionInstruction({
|
|
275
|
+
programId: this.programIds.IDENTITY,
|
|
276
|
+
keys: [
|
|
277
|
+
{ pubkey: genesisPDA, isSigner: false, isWritable: true },
|
|
278
|
+
{ pubkey: newAuthKey, isSigner: true, isWritable: false },
|
|
279
|
+
],
|
|
280
|
+
data: disc,
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
const tx = new Transaction().add(ix);
|
|
284
|
+
tx.feePayer = newAuthKey;
|
|
285
|
+
tx.recentBlockhash = (await this.connection.getLatestBlockhash()).blockhash;
|
|
286
|
+
|
|
287
|
+
return { transaction: tx };
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// ═══════════════════════════════════════════════════
|
|
291
|
+
// IDENTITY — Name Registry
|
|
292
|
+
// ═══════════════════════════════════════════════════
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Build registerName transaction.
|
|
296
|
+
* @param {PublicKey|string} authority - Identity authority (signer + payer)
|
|
297
|
+
* @param {string|Buffer} agentIdOrHash
|
|
298
|
+
* @param {string} name - Display name (2-32 chars)
|
|
299
|
+
* @returns {{ transaction: Transaction, nameRegistryPDA: PublicKey }}
|
|
300
|
+
*/
|
|
301
|
+
async buildRegisterName(authority, agentIdOrHash, name) {
|
|
302
|
+
const authorityKey = new PublicKey(authority);
|
|
303
|
+
const [genesisPDA] = getGenesisPDA(agentIdOrHash, this.network);
|
|
304
|
+
const nameHash = hashName(name);
|
|
305
|
+
const [nameRegistryPDA] = getNameRegistryPDA(nameHash, this.network);
|
|
306
|
+
|
|
307
|
+
const disc = anchorDiscriminator('register_name');
|
|
308
|
+
const data = Buffer.concat([
|
|
309
|
+
disc,
|
|
310
|
+
serializeString(name),
|
|
311
|
+
nameHash, // [u8; 32]
|
|
312
|
+
]);
|
|
313
|
+
|
|
314
|
+
const ix = new TransactionInstruction({
|
|
315
|
+
programId: this.programIds.IDENTITY,
|
|
316
|
+
keys: [
|
|
317
|
+
{ pubkey: genesisPDA, isSigner: false, isWritable: true },
|
|
318
|
+
{ pubkey: nameRegistryPDA, isSigner: false, isWritable: true },
|
|
319
|
+
{ pubkey: authorityKey, isSigner: true, isWritable: true },
|
|
320
|
+
{ pubkey: SystemProgram.programId, isSigner: false, isWritable: false },
|
|
321
|
+
],
|
|
322
|
+
data,
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
const tx = new Transaction().add(ix);
|
|
326
|
+
tx.feePayer = authorityKey;
|
|
327
|
+
tx.recentBlockhash = (await this.connection.getLatestBlockhash()).blockhash;
|
|
328
|
+
|
|
329
|
+
return { transaction: tx, nameRegistryPDA };
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* Build releaseName transaction (frees name for others to claim).
|
|
334
|
+
* @param {PublicKey|string} authority
|
|
335
|
+
* @param {string|Buffer} agentIdOrHash
|
|
336
|
+
* @param {string} name - The registered name to release
|
|
337
|
+
* @returns {{ transaction: Transaction }}
|
|
338
|
+
*/
|
|
339
|
+
async buildReleaseName(authority, agentIdOrHash, name) {
|
|
340
|
+
const authorityKey = new PublicKey(authority);
|
|
341
|
+
const [genesisPDA] = getGenesisPDA(agentIdOrHash, this.network);
|
|
342
|
+
const nameHash = hashName(name);
|
|
343
|
+
const [nameRegistryPDA] = getNameRegistryPDA(nameHash, this.network);
|
|
344
|
+
|
|
345
|
+
const disc = anchorDiscriminator('release_name');
|
|
346
|
+
|
|
347
|
+
const ix = new TransactionInstruction({
|
|
348
|
+
programId: this.programIds.IDENTITY,
|
|
349
|
+
keys: [
|
|
350
|
+
{ pubkey: genesisPDA, isSigner: false, isWritable: false },
|
|
351
|
+
{ pubkey: nameRegistryPDA, isSigner: false, isWritable: true },
|
|
352
|
+
{ pubkey: authorityKey, isSigner: true, isWritable: false },
|
|
353
|
+
],
|
|
354
|
+
data: disc,
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
const tx = new Transaction().add(ix);
|
|
358
|
+
tx.feePayer = authorityKey;
|
|
359
|
+
tx.recentBlockhash = (await this.connection.getLatestBlockhash()).blockhash;
|
|
360
|
+
|
|
361
|
+
return { transaction: tx };
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* Build cancelAuthorityTransfer transaction.
|
|
366
|
+
* @param {PublicKey|string} authority - Current authority (signer)
|
|
367
|
+
* @param {string|Buffer} agentIdOrHash
|
|
368
|
+
* @returns {{ transaction: Transaction }}
|
|
369
|
+
*/
|
|
370
|
+
async buildCancelAuthorityTransfer(authority, agentIdOrHash) {
|
|
371
|
+
const authorityKey = new PublicKey(authority);
|
|
372
|
+
const [genesisPDA] = getGenesisPDA(agentIdOrHash, this.network);
|
|
373
|
+
|
|
374
|
+
const disc = anchorDiscriminator('cancel_authority_transfer');
|
|
375
|
+
|
|
376
|
+
const ix = new TransactionInstruction({
|
|
377
|
+
programId: this.programIds.IDENTITY,
|
|
378
|
+
keys: [
|
|
379
|
+
{ pubkey: genesisPDA, isSigner: false, isWritable: true },
|
|
380
|
+
{ pubkey: authorityKey, isSigner: true, isWritable: false },
|
|
381
|
+
],
|
|
382
|
+
data: disc,
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
const tx = new Transaction().add(ix);
|
|
386
|
+
tx.feePayer = authorityKey;
|
|
387
|
+
tx.recentBlockhash = (await this.connection.getLatestBlockhash()).blockhash;
|
|
388
|
+
|
|
389
|
+
return { transaction: tx };
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// ═══════════════════════════════════════════════════
|
|
393
|
+
// IDENTITY — Linked Wallets
|
|
394
|
+
// ═══════════════════════════════════════════════════
|
|
395
|
+
|
|
396
|
+
/**
|
|
397
|
+
* Build linkWallet transaction.
|
|
398
|
+
* @param {PublicKey|string} authority
|
|
399
|
+
* @param {string|Buffer} agentIdOrHash
|
|
400
|
+
* @param {PublicKey|string} wallet - Wallet to link
|
|
401
|
+
* @param {string} chain - Chain identifier (e.g. "solana", max 16 chars)
|
|
402
|
+
* @param {string} label - Label (e.g. "deploy", max 32 chars)
|
|
403
|
+
* @returns {{ transaction: Transaction, linkedWalletPDA: PublicKey }}
|
|
404
|
+
*/
|
|
405
|
+
async buildLinkWallet(authority, agentIdOrHash, wallet, chain, label) {
|
|
406
|
+
const authorityKey = new PublicKey(authority);
|
|
407
|
+
const walletKey = new PublicKey(wallet);
|
|
408
|
+
const [genesisPDA] = getGenesisPDA(agentIdOrHash, this.network);
|
|
409
|
+
const [linkedWalletPDA] = getLinkedWalletPDA(genesisPDA, walletKey, this.network);
|
|
410
|
+
|
|
411
|
+
const disc = anchorDiscriminator('link_wallet');
|
|
412
|
+
const data = Buffer.concat([
|
|
413
|
+
disc,
|
|
414
|
+
walletKey.toBuffer(),
|
|
415
|
+
serializeString(chain),
|
|
416
|
+
serializeString(label),
|
|
417
|
+
]);
|
|
418
|
+
|
|
419
|
+
const ix = new TransactionInstruction({
|
|
420
|
+
programId: this.programIds.IDENTITY,
|
|
421
|
+
keys: [
|
|
422
|
+
{ pubkey: genesisPDA, isSigner: false, isWritable: false },
|
|
423
|
+
{ pubkey: linkedWalletPDA, isSigner: false, isWritable: true },
|
|
424
|
+
{ pubkey: authorityKey, isSigner: true, isWritable: true },
|
|
425
|
+
{ pubkey: SystemProgram.programId, isSigner: false, isWritable: false },
|
|
426
|
+
],
|
|
427
|
+
data,
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
const tx = new Transaction().add(ix);
|
|
431
|
+
tx.feePayer = authorityKey;
|
|
432
|
+
tx.recentBlockhash = (await this.connection.getLatestBlockhash()).blockhash;
|
|
433
|
+
|
|
434
|
+
return { transaction: tx, linkedWalletPDA };
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
/**
|
|
438
|
+
* Build unlinkWallet transaction.
|
|
439
|
+
* @param {PublicKey|string} authority
|
|
440
|
+
* @param {string|Buffer} agentIdOrHash
|
|
441
|
+
* @param {PublicKey|string} wallet - Wallet to unlink
|
|
442
|
+
* @returns {{ transaction: Transaction }}
|
|
443
|
+
*/
|
|
444
|
+
async buildUnlinkWallet(authority, agentIdOrHash, wallet) {
|
|
445
|
+
const authorityKey = new PublicKey(authority);
|
|
446
|
+
const walletKey = new PublicKey(wallet);
|
|
447
|
+
const [genesisPDA] = getGenesisPDA(agentIdOrHash, this.network);
|
|
448
|
+
const [linkedWalletPDA] = getLinkedWalletPDA(genesisPDA, walletKey, this.network);
|
|
449
|
+
|
|
450
|
+
const disc = anchorDiscriminator('unlink_wallet');
|
|
451
|
+
|
|
452
|
+
const ix = new TransactionInstruction({
|
|
453
|
+
programId: this.programIds.IDENTITY,
|
|
454
|
+
keys: [
|
|
455
|
+
{ pubkey: genesisPDA, isSigner: false, isWritable: false },
|
|
456
|
+
{ pubkey: linkedWalletPDA, isSigner: false, isWritable: true },
|
|
457
|
+
{ pubkey: authorityKey, isSigner: true, isWritable: false },
|
|
458
|
+
],
|
|
459
|
+
data: disc,
|
|
460
|
+
});
|
|
461
|
+
|
|
462
|
+
const tx = new Transaction().add(ix);
|
|
463
|
+
tx.feePayer = authorityKey;
|
|
464
|
+
tx.recentBlockhash = (await this.connection.getLatestBlockhash()).blockhash;
|
|
465
|
+
|
|
466
|
+
return { transaction: tx };
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
// ═══════════════════════════════════════════════════
|
|
470
|
+
// IDENTITY — MintTracker
|
|
471
|
+
// ═══════════════════════════════════════════════════
|
|
472
|
+
|
|
473
|
+
/**
|
|
474
|
+
* Build initMintTracker transaction.
|
|
475
|
+
* @param {PublicKey|string} authority
|
|
476
|
+
* @param {string|Buffer} agentIdOrHash
|
|
477
|
+
* @returns {{ transaction: Transaction, mintTrackerPDA: PublicKey }}
|
|
478
|
+
*/
|
|
479
|
+
async buildInitMintTracker(authority, agentIdOrHash) {
|
|
480
|
+
const authorityKey = new PublicKey(authority);
|
|
481
|
+
const [genesisPDA] = getGenesisPDA(agentIdOrHash, this.network);
|
|
482
|
+
const [mintTrackerPDA] = getV3MintTrackerPDA(genesisPDA, this.network);
|
|
483
|
+
|
|
484
|
+
const disc = anchorDiscriminator('init_mint_tracker');
|
|
485
|
+
|
|
486
|
+
const ix = new TransactionInstruction({
|
|
487
|
+
programId: this.programIds.IDENTITY,
|
|
488
|
+
keys: [
|
|
489
|
+
{ pubkey: genesisPDA, isSigner: false, isWritable: false },
|
|
490
|
+
{ pubkey: mintTrackerPDA, isSigner: false, isWritable: true },
|
|
491
|
+
{ pubkey: authorityKey, isSigner: true, isWritable: true },
|
|
492
|
+
{ pubkey: SystemProgram.programId, isSigner: false, isWritable: false },
|
|
493
|
+
],
|
|
494
|
+
data: disc,
|
|
495
|
+
});
|
|
496
|
+
|
|
497
|
+
const tx = new Transaction().add(ix);
|
|
498
|
+
tx.feePayer = authorityKey;
|
|
499
|
+
tx.recentBlockhash = (await this.connection.getLatestBlockhash()).blockhash;
|
|
500
|
+
|
|
501
|
+
return { transaction: tx, mintTrackerPDA };
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
/**
|
|
505
|
+
* Build recordMint transaction (tracks mints, max 3 per identity).
|
|
506
|
+
* @param {PublicKey|string} authority
|
|
507
|
+
* @param {string|Buffer} agentIdOrHash
|
|
508
|
+
* @returns {{ transaction: Transaction }}
|
|
509
|
+
*/
|
|
510
|
+
async buildRecordMint(authority, agentIdOrHash) {
|
|
511
|
+
const authorityKey = new PublicKey(authority);
|
|
512
|
+
const [genesisPDA] = getGenesisPDA(agentIdOrHash, this.network);
|
|
513
|
+
const [mintTrackerPDA] = getV3MintTrackerPDA(genesisPDA, this.network);
|
|
514
|
+
|
|
515
|
+
const disc = anchorDiscriminator('record_mint');
|
|
516
|
+
|
|
517
|
+
const ix = new TransactionInstruction({
|
|
518
|
+
programId: this.programIds.IDENTITY,
|
|
519
|
+
keys: [
|
|
520
|
+
{ pubkey: genesisPDA, isSigner: false, isWritable: false },
|
|
521
|
+
{ pubkey: mintTrackerPDA, isSigner: false, isWritable: true },
|
|
522
|
+
{ pubkey: authorityKey, isSigner: true, isWritable: false },
|
|
523
|
+
],
|
|
524
|
+
data: disc,
|
|
525
|
+
});
|
|
526
|
+
|
|
527
|
+
const tx = new Transaction().add(ix);
|
|
528
|
+
tx.feePayer = authorityKey;
|
|
529
|
+
tx.recentBlockhash = (await this.connection.getLatestBlockhash()).blockhash;
|
|
530
|
+
|
|
531
|
+
return { transaction: tx };
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
// ═══════════════════════════════════════════════════
|
|
535
|
+
// IDENTITY — Deactivation
|
|
536
|
+
// ═══════════════════════════════════════════════════
|
|
537
|
+
|
|
538
|
+
/**
|
|
539
|
+
* Build deactivateIdentity transaction.
|
|
540
|
+
* @param {PublicKey|string} authority
|
|
541
|
+
* @param {string|Buffer} agentIdOrHash
|
|
542
|
+
* @returns {{ transaction: Transaction }}
|
|
543
|
+
*/
|
|
544
|
+
async buildDeactivateIdentity(authority, agentIdOrHash) {
|
|
545
|
+
const authorityKey = new PublicKey(authority);
|
|
546
|
+
const [genesisPDA] = getGenesisPDA(agentIdOrHash, this.network);
|
|
547
|
+
|
|
548
|
+
const disc = anchorDiscriminator('deactivate_identity');
|
|
549
|
+
|
|
550
|
+
const ix = new TransactionInstruction({
|
|
551
|
+
programId: this.programIds.IDENTITY,
|
|
552
|
+
keys: [
|
|
553
|
+
{ pubkey: genesisPDA, isSigner: false, isWritable: true },
|
|
554
|
+
{ pubkey: authorityKey, isSigner: true, isWritable: false },
|
|
555
|
+
],
|
|
556
|
+
data: disc,
|
|
557
|
+
});
|
|
558
|
+
|
|
559
|
+
const tx = new Transaction().add(ix);
|
|
560
|
+
tx.feePayer = authorityKey;
|
|
561
|
+
tx.recentBlockhash = (await this.connection.getLatestBlockhash()).blockhash;
|
|
562
|
+
|
|
563
|
+
return { transaction: tx };
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
/**
|
|
567
|
+
* Build reactivateIdentity transaction.
|
|
568
|
+
* @param {PublicKey|string} authority
|
|
569
|
+
* @param {string|Buffer} agentIdOrHash
|
|
570
|
+
* @returns {{ transaction: Transaction }}
|
|
571
|
+
*/
|
|
572
|
+
async buildReactivateIdentity(authority, agentIdOrHash) {
|
|
573
|
+
const authorityKey = new PublicKey(authority);
|
|
574
|
+
const [genesisPDA] = getGenesisPDA(agentIdOrHash, this.network);
|
|
575
|
+
|
|
576
|
+
const disc = anchorDiscriminator('reactivate_identity');
|
|
577
|
+
|
|
578
|
+
const ix = new TransactionInstruction({
|
|
579
|
+
programId: this.programIds.IDENTITY,
|
|
580
|
+
keys: [
|
|
581
|
+
{ pubkey: genesisPDA, isSigner: false, isWritable: true },
|
|
582
|
+
{ pubkey: authorityKey, isSigner: true, isWritable: false },
|
|
583
|
+
],
|
|
584
|
+
data: disc,
|
|
585
|
+
});
|
|
586
|
+
|
|
587
|
+
const tx = new Transaction().add(ix);
|
|
588
|
+
tx.feePayer = authorityKey;
|
|
589
|
+
tx.recentBlockhash = (await this.connection.getLatestBlockhash()).blockhash;
|
|
590
|
+
|
|
591
|
+
return { transaction: tx };
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
// ═══════════════════════════════════════════════════
|
|
595
|
+
// ATTESTATIONS V3 — Create, Verify, Revoke
|
|
596
|
+
// ═══════════════════════════════════════════════════
|
|
597
|
+
|
|
598
|
+
/**
|
|
599
|
+
* Build createAttestation transaction.
|
|
600
|
+
* @param {PublicKey|string} issuer - Signer + fee payer
|
|
601
|
+
* @param {string} agentId - Agent identifier
|
|
602
|
+
* @param {string} attestationType - Type of attestation (max 32 chars)
|
|
603
|
+
* @param {string} proofData - JSON proof data (max 512 chars)
|
|
604
|
+
* @param {number|null} expiresAt - Optional Unix timestamp for expiry
|
|
605
|
+
* @returns {{ transaction: Transaction, attestationPDA: PublicKey }}
|
|
606
|
+
*/
|
|
607
|
+
async buildCreateAttestation(issuer, agentId, attestationType, proofData, expiresAt = null) {
|
|
608
|
+
const issuerKey = new PublicKey(issuer);
|
|
609
|
+
const [attPDA] = getV3AttestationPDA(agentId, issuerKey, attestationType, this.network);
|
|
610
|
+
|
|
611
|
+
const disc = anchorDiscriminator('create_attestation');
|
|
612
|
+
|
|
613
|
+
// Encode args: agent_id (string), attestation_type (string), proof_data (string), expires_at (Option<i64>)
|
|
614
|
+
const agentIdBuf = Buffer.from(agentId);
|
|
615
|
+
const typeBuf = Buffer.from(attestationType);
|
|
616
|
+
const proofBuf = Buffer.from(proofData);
|
|
617
|
+
|
|
618
|
+
const parts = [
|
|
619
|
+
disc,
|
|
620
|
+
// agent_id: string (4-byte len + data)
|
|
621
|
+
Buffer.alloc(4),
|
|
622
|
+
agentIdBuf,
|
|
623
|
+
// attestation_type: string (4-byte len + data)
|
|
624
|
+
Buffer.alloc(4),
|
|
625
|
+
typeBuf,
|
|
626
|
+
// proof_data: string (4-byte len + data)
|
|
627
|
+
Buffer.alloc(4),
|
|
628
|
+
proofBuf,
|
|
629
|
+
];
|
|
630
|
+
|
|
631
|
+
parts[1].writeUInt32LE(agentIdBuf.length);
|
|
632
|
+
parts[3].writeUInt32LE(typeBuf.length);
|
|
633
|
+
parts[5].writeUInt32LE(proofBuf.length);
|
|
634
|
+
|
|
635
|
+
// expires_at: Option<i64>
|
|
636
|
+
if (expiresAt !== null && expiresAt !== undefined) {
|
|
637
|
+
const optBuf = Buffer.alloc(9);
|
|
638
|
+
optBuf.writeUInt8(1, 0); // Some
|
|
639
|
+
optBuf.writeBigInt64LE(BigInt(expiresAt), 1);
|
|
640
|
+
parts.push(optBuf);
|
|
641
|
+
} else {
|
|
642
|
+
parts.push(Buffer.from([0])); // None
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
const data = Buffer.concat(parts);
|
|
646
|
+
|
|
647
|
+
const ix = new TransactionInstruction({
|
|
648
|
+
programId: this.programIds.ATTESTATIONS,
|
|
649
|
+
keys: [
|
|
650
|
+
{ pubkey: attPDA, isSigner: false, isWritable: true },
|
|
651
|
+
{ pubkey: issuerKey, isSigner: true, isWritable: true },
|
|
652
|
+
{ pubkey: SystemProgram.programId, isSigner: false, isWritable: false },
|
|
653
|
+
],
|
|
654
|
+
data,
|
|
655
|
+
});
|
|
656
|
+
|
|
657
|
+
const tx = new Transaction().add(ix);
|
|
658
|
+
tx.feePayer = issuerKey;
|
|
659
|
+
tx.recentBlockhash = (await this.connection.getLatestBlockhash()).blockhash;
|
|
660
|
+
|
|
661
|
+
return { transaction: tx, attestationPDA: attPDA };
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
/**
|
|
665
|
+
* Build verifyAttestation transaction (issuer-only).
|
|
666
|
+
* @param {PublicKey|string} issuer - Must be the original issuer
|
|
667
|
+
* @param {PublicKey|string} attestationPDA - Attestation account address
|
|
668
|
+
* @returns {{ transaction: Transaction }}
|
|
669
|
+
*/
|
|
670
|
+
async buildVerifyAttestation(issuer, attestationPDA) {
|
|
671
|
+
const issuerKey = new PublicKey(issuer);
|
|
672
|
+
const attKey = new PublicKey(attestationPDA);
|
|
673
|
+
|
|
674
|
+
const disc = anchorDiscriminator('verify_attestation');
|
|
675
|
+
|
|
676
|
+
const ix = new TransactionInstruction({
|
|
677
|
+
programId: this.programIds.ATTESTATIONS,
|
|
678
|
+
keys: [
|
|
679
|
+
{ pubkey: attKey, isSigner: false, isWritable: true },
|
|
680
|
+
{ pubkey: issuerKey, isSigner: true, isWritable: false },
|
|
681
|
+
],
|
|
682
|
+
data: disc,
|
|
683
|
+
});
|
|
684
|
+
|
|
685
|
+
const tx = new Transaction().add(ix);
|
|
686
|
+
tx.feePayer = issuerKey;
|
|
687
|
+
tx.recentBlockhash = (await this.connection.getLatestBlockhash()).blockhash;
|
|
688
|
+
|
|
689
|
+
return { transaction: tx };
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
/**
|
|
693
|
+
* Build revokeAttestation transaction (issuer-only).
|
|
694
|
+
* @param {PublicKey|string} issuer - Must be the original issuer
|
|
695
|
+
* @param {PublicKey|string} attestationPDA - Attestation account address
|
|
696
|
+
* @returns {{ transaction: Transaction }}
|
|
697
|
+
*/
|
|
698
|
+
async buildRevokeAttestation(issuer, attestationPDA) {
|
|
699
|
+
const issuerKey = new PublicKey(issuer);
|
|
700
|
+
const attKey = new PublicKey(attestationPDA);
|
|
701
|
+
|
|
702
|
+
const disc = anchorDiscriminator('revoke_attestation');
|
|
703
|
+
|
|
704
|
+
const ix = new TransactionInstruction({
|
|
705
|
+
programId: this.programIds.ATTESTATIONS,
|
|
706
|
+
keys: [
|
|
707
|
+
{ pubkey: attKey, isSigner: false, isWritable: true },
|
|
708
|
+
{ pubkey: issuerKey, isSigner: true, isWritable: false },
|
|
709
|
+
],
|
|
710
|
+
data: disc,
|
|
711
|
+
});
|
|
712
|
+
|
|
713
|
+
const tx = new Transaction().add(ix);
|
|
714
|
+
tx.feePayer = issuerKey;
|
|
715
|
+
tx.recentBlockhash = (await this.connection.getLatestBlockhash()).blockhash;
|
|
716
|
+
|
|
717
|
+
return { transaction: tx };
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
// ═══════════════════════════════════════════════════
|
|
721
|
+
// REVIEWS — Create / Update / Delete / Counter
|
|
722
|
+
// ═══════════════════════════════════════════════════
|
|
723
|
+
|
|
724
|
+
/**
|
|
725
|
+
* Build initReviewCounter transaction. Must be called once per agent before any reviews.
|
|
726
|
+
* PDA: ["review_counter_v3", SHA256(agent_id)]
|
|
727
|
+
* @param {PublicKey|string} payer - Transaction signer + fee payer
|
|
728
|
+
* @param {string} agentId - Agent identifier
|
|
729
|
+
* @returns {{ transaction: Transaction, counterPDA: PublicKey }}
|
|
730
|
+
*/
|
|
731
|
+
async buildInitReviewCounter(payer, agentId) {
|
|
732
|
+
const payerKey = new PublicKey(payer);
|
|
733
|
+
const [counterPDA] = getV3ReviewCounterPDA(agentId, this.network);
|
|
734
|
+
|
|
735
|
+
const disc = anchorDiscriminator('init_review_counter');
|
|
736
|
+
const data = Buffer.concat([disc, serializeString(agentId)]);
|
|
737
|
+
|
|
738
|
+
const ix = new TransactionInstruction({
|
|
739
|
+
programId: this.programIds.REVIEWS,
|
|
740
|
+
keys: [
|
|
741
|
+
{ pubkey: counterPDA, isSigner: false, isWritable: true },
|
|
742
|
+
{ pubkey: payerKey, isSigner: true, isWritable: true },
|
|
743
|
+
{ pubkey: SystemProgram.programId, isSigner: false, isWritable: false },
|
|
744
|
+
],
|
|
745
|
+
data,
|
|
746
|
+
});
|
|
747
|
+
|
|
748
|
+
const tx = new Transaction().add(ix);
|
|
749
|
+
tx.feePayer = payerKey;
|
|
750
|
+
tx.recentBlockhash = (await this.connection.getLatestBlockhash()).blockhash;
|
|
751
|
+
|
|
752
|
+
return { transaction: tx, counterPDA };
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
/**
|
|
756
|
+
* Build createReview transaction (V3.1 with self-review prevention).
|
|
757
|
+
* PDA: ["review_v3", SHA256(agent_id), reviewer]
|
|
758
|
+
*
|
|
759
|
+
* @param {PublicKey|string} reviewer - Reviewer wallet (signer + fee payer)
|
|
760
|
+
* @param {string} agentId - Agent identifier being reviewed
|
|
761
|
+
* @param {number} rating - 1-5 rating
|
|
762
|
+
* @param {string} reviewText - Review text (max 512 chars)
|
|
763
|
+
* @param {string} [metadata=''] - Optional metadata JSON (max 256 chars)
|
|
764
|
+
* @param {object} [opts={}] - Optional self-review check params
|
|
765
|
+
* @param {PublicKey|string} [opts.identityProgram] - Identity program ID for self-review check
|
|
766
|
+
* @param {PublicKey|string} [opts.identityAccount] - Identity PDA for self-review check
|
|
767
|
+
* @returns {{ transaction: Transaction, reviewPDA: PublicKey }}
|
|
768
|
+
*/
|
|
769
|
+
async buildCreateReview(reviewer, agentId, rating, reviewText, metadata = '', opts = {}) {
|
|
770
|
+
const reviewerKey = new PublicKey(reviewer);
|
|
771
|
+
const [reviewPDA] = getV3ReviewPDA(agentId, reviewerKey, this.network);
|
|
772
|
+
const [counterPDA] = getV3ReviewCounterPDA(agentId, this.network);
|
|
773
|
+
|
|
774
|
+
// Self-review check: if identityProgram is provided, use it; otherwise use system_program (skip check)
|
|
775
|
+
const identityProgram = opts.identityProgram
|
|
776
|
+
? new PublicKey(opts.identityProgram)
|
|
777
|
+
: SystemProgram.programId;
|
|
778
|
+
const identityAccount = opts.identityAccount
|
|
779
|
+
? new PublicKey(opts.identityAccount)
|
|
780
|
+
: SystemProgram.programId; // placeholder when check is skipped
|
|
781
|
+
|
|
782
|
+
const disc = anchorDiscriminator('create_review');
|
|
783
|
+
const data = Buffer.concat([
|
|
784
|
+
disc,
|
|
785
|
+
serializeString(agentId),
|
|
786
|
+
Buffer.from([rating]),
|
|
787
|
+
serializeString(reviewText),
|
|
788
|
+
serializeString(metadata),
|
|
789
|
+
]);
|
|
790
|
+
|
|
791
|
+
const ix = new TransactionInstruction({
|
|
792
|
+
programId: this.programIds.REVIEWS,
|
|
793
|
+
keys: [
|
|
794
|
+
{ pubkey: reviewPDA, isSigner: false, isWritable: true },
|
|
795
|
+
{ pubkey: counterPDA, isSigner: false, isWritable: true },
|
|
796
|
+
{ pubkey: reviewerKey, isSigner: true, isWritable: true },
|
|
797
|
+
{ pubkey: identityProgram, isSigner: false, isWritable: false },
|
|
798
|
+
{ pubkey: identityAccount, isSigner: false, isWritable: false },
|
|
799
|
+
{ pubkey: SystemProgram.programId, isSigner: false, isWritable: false },
|
|
800
|
+
],
|
|
801
|
+
data,
|
|
802
|
+
});
|
|
803
|
+
|
|
804
|
+
const tx = new Transaction().add(ix);
|
|
805
|
+
tx.feePayer = reviewerKey;
|
|
806
|
+
tx.recentBlockhash = (await this.connection.getLatestBlockhash()).blockhash;
|
|
807
|
+
|
|
808
|
+
return { transaction: tx, reviewPDA };
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
/**
|
|
812
|
+
* Build createReview with self-review prevention enabled (convenience).
|
|
813
|
+
* Automatically resolves identity PDA from agentId.
|
|
814
|
+
* @param {PublicKey|string} reviewer
|
|
815
|
+
* @param {string} agentId
|
|
816
|
+
* @param {number} rating
|
|
817
|
+
* @param {string} reviewText
|
|
818
|
+
* @param {string} [metadata='']
|
|
819
|
+
* @returns {{ transaction: Transaction, reviewPDA: PublicKey }}
|
|
820
|
+
*/
|
|
821
|
+
async buildCreateReviewWithSelfCheck(reviewer, agentId, rating, reviewText, metadata = '') {
|
|
822
|
+
const [identityAccount] = getGenesisPDA(agentId, this.network);
|
|
823
|
+
return this.buildCreateReview(reviewer, agentId, rating, reviewText, metadata, {
|
|
824
|
+
identityProgram: this.programIds.IDENTITY,
|
|
825
|
+
identityAccount,
|
|
826
|
+
});
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
/**
|
|
830
|
+
* Build updateReview transaction (reviewer only).
|
|
831
|
+
* @param {PublicKey|string} reviewer - Must be the original reviewer
|
|
832
|
+
* @param {PublicKey|string} reviewPDA - Review account address
|
|
833
|
+
* @param {object} updates - { rating?: number, reviewText?: string, metadata?: string }
|
|
834
|
+
* @returns {{ transaction: Transaction }}
|
|
835
|
+
*/
|
|
836
|
+
async buildUpdateReview(reviewer, reviewPDA, updates = {}) {
|
|
837
|
+
const reviewerKey = new PublicKey(reviewer);
|
|
838
|
+
const reviewKey = new PublicKey(reviewPDA);
|
|
839
|
+
|
|
840
|
+
const disc = anchorDiscriminator('update_review');
|
|
841
|
+
|
|
842
|
+
// Encode Option<u8> rating
|
|
843
|
+
const ratingBuf = updates.rating != null
|
|
844
|
+
? Buffer.from([1, updates.rating])
|
|
845
|
+
: Buffer.from([0]);
|
|
846
|
+
|
|
847
|
+
// Encode Option<String> review_text
|
|
848
|
+
const textBuf = updates.reviewText != null
|
|
849
|
+
? Buffer.concat([Buffer.from([1]), serializeString(updates.reviewText)])
|
|
850
|
+
: Buffer.from([0]);
|
|
851
|
+
|
|
852
|
+
// Encode Option<String> metadata
|
|
853
|
+
const metaBuf = updates.metadata != null
|
|
854
|
+
? Buffer.concat([Buffer.from([1]), serializeString(updates.metadata)])
|
|
855
|
+
: Buffer.from([0]);
|
|
856
|
+
|
|
857
|
+
const data = Buffer.concat([disc, ratingBuf, textBuf, metaBuf]);
|
|
858
|
+
|
|
859
|
+
const ix = new TransactionInstruction({
|
|
860
|
+
programId: this.programIds.REVIEWS,
|
|
861
|
+
keys: [
|
|
862
|
+
{ pubkey: reviewKey, isSigner: false, isWritable: true },
|
|
863
|
+
{ pubkey: reviewerKey, isSigner: true, isWritable: false },
|
|
864
|
+
],
|
|
865
|
+
data,
|
|
866
|
+
});
|
|
867
|
+
|
|
868
|
+
const tx = new Transaction().add(ix);
|
|
869
|
+
tx.feePayer = reviewerKey;
|
|
870
|
+
tx.recentBlockhash = (await this.connection.getLatestBlockhash()).blockhash;
|
|
871
|
+
|
|
872
|
+
return { transaction: tx };
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
/**
|
|
876
|
+
* Build deleteReview transaction (soft-delete, reviewer only).
|
|
877
|
+
* @param {PublicKey|string} reviewer - Must be the original reviewer
|
|
878
|
+
* @param {PublicKey|string} reviewPDA - Review account address
|
|
879
|
+
* @returns {{ transaction: Transaction }}
|
|
880
|
+
*/
|
|
881
|
+
async buildDeleteReview(reviewer, reviewPDA) {
|
|
882
|
+
const reviewerKey = new PublicKey(reviewer);
|
|
883
|
+
const reviewKey = new PublicKey(reviewPDA);
|
|
884
|
+
|
|
885
|
+
const disc = anchorDiscriminator('delete_review');
|
|
886
|
+
|
|
887
|
+
const ix = new TransactionInstruction({
|
|
888
|
+
programId: this.programIds.REVIEWS,
|
|
889
|
+
keys: [
|
|
890
|
+
{ pubkey: reviewKey, isSigner: false, isWritable: true },
|
|
891
|
+
{ pubkey: reviewerKey, isSigner: true, isWritable: false },
|
|
892
|
+
],
|
|
893
|
+
data: disc,
|
|
894
|
+
});
|
|
895
|
+
|
|
896
|
+
const tx = new Transaction().add(ix);
|
|
897
|
+
tx.feePayer = reviewerKey;
|
|
898
|
+
tx.recentBlockhash = (await this.connection.getLatestBlockhash()).blockhash;
|
|
899
|
+
|
|
900
|
+
return { transaction: tx };
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
/**
|
|
904
|
+
* Fetch a Review account.
|
|
905
|
+
* @param {string} agentId - Agent identifier
|
|
906
|
+
* @param {PublicKey|string} reviewer - Reviewer pubkey
|
|
907
|
+
* @returns {object|null}
|
|
908
|
+
*/
|
|
909
|
+
async getReview(agentId, reviewer) {
|
|
910
|
+
const reviewerKey = new PublicKey(reviewer);
|
|
911
|
+
const [pda] = getV3ReviewPDA(agentId, reviewerKey, this.network);
|
|
912
|
+
const acct = await this.connection.getAccountInfo(pda);
|
|
913
|
+
if (!acct) return null;
|
|
914
|
+
|
|
915
|
+
try {
|
|
916
|
+
const data = acct.data.slice(8); // skip Anchor discriminator
|
|
917
|
+
let offset = 0;
|
|
918
|
+
|
|
919
|
+
const readString = () => {
|
|
920
|
+
const len = data.readUInt32LE(offset); offset += 4;
|
|
921
|
+
const str = data.slice(offset, offset + len).toString('utf8'); offset += len;
|
|
922
|
+
return str;
|
|
923
|
+
};
|
|
924
|
+
|
|
925
|
+
const reviewAgentId = readString();
|
|
926
|
+
const agentIdHash = data.slice(offset, offset + 32); offset += 32;
|
|
927
|
+
const reviewerPk = new PublicKey(data.slice(offset, offset + 32)); offset += 32;
|
|
928
|
+
const rating = data[offset]; offset += 1;
|
|
929
|
+
const reviewText = readString();
|
|
930
|
+
const metadata = readString();
|
|
931
|
+
const createdAt = Number(data.readBigInt64LE(offset)); offset += 8;
|
|
932
|
+
const updatedAt = Number(data.readBigInt64LE(offset)); offset += 8;
|
|
933
|
+
const isActive = data[offset] === 1; offset += 1;
|
|
934
|
+
const bump = data[offset]; offset += 1;
|
|
935
|
+
|
|
936
|
+
return {
|
|
937
|
+
pda: pda.toBase58(),
|
|
938
|
+
agentId: reviewAgentId,
|
|
939
|
+
agentIdHash: Buffer.from(agentIdHash).toString('hex'),
|
|
940
|
+
reviewer: reviewerPk.toBase58(),
|
|
941
|
+
rating,
|
|
942
|
+
reviewText,
|
|
943
|
+
metadata,
|
|
944
|
+
createdAt,
|
|
945
|
+
updatedAt,
|
|
946
|
+
bump,
|
|
947
|
+
};
|
|
948
|
+
} catch (e) {
|
|
949
|
+
return { pda: pda.toBase58(), raw: acct.data.toString('hex'), error: e.message };
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
/**
|
|
954
|
+
* Fetch review counter for an agent.
|
|
955
|
+
* @param {string} agentId
|
|
956
|
+
* @returns {{ count: number, pda: string }|null}
|
|
957
|
+
*/
|
|
958
|
+
async getReviewCount(agentId) {
|
|
959
|
+
const [pda] = getV3ReviewCounterPDA(agentId, this.network);
|
|
960
|
+
const acct = await this.connection.getAccountInfo(pda);
|
|
961
|
+
if (!acct) return null;
|
|
962
|
+
|
|
963
|
+
try {
|
|
964
|
+
const data = acct.data.slice(8);
|
|
965
|
+
let offset = 0;
|
|
966
|
+
|
|
967
|
+
const readString = () => {
|
|
968
|
+
const len = data.readUInt32LE(offset); offset += 4;
|
|
969
|
+
const str = data.slice(offset, offset + len).toString('utf8'); offset += len;
|
|
970
|
+
return str;
|
|
971
|
+
};
|
|
972
|
+
|
|
973
|
+
const counterAgentId = readString();
|
|
974
|
+
offset += 32; // agent_id_hash
|
|
975
|
+
const count = Number(data.readBigUInt64LE(offset)); offset += 8;
|
|
976
|
+
const bump = data[offset]; offset += 1;
|
|
977
|
+
|
|
978
|
+
return { pda: pda.toBase58(), agentId: counterAgentId, count, bump };
|
|
979
|
+
} catch (e) {
|
|
980
|
+
return { pda: pda.toBase58(), raw: acct.data.toString('hex'), error: e.message };
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
// ═══════════════════════════════════════════════════
|
|
985
|
+
// REPUTATION — Permissionless Recompute
|
|
986
|
+
// ═══════════════════════════════════════════════════
|
|
987
|
+
|
|
988
|
+
/**
|
|
989
|
+
* Build recomputeReputation transaction (permissionless).
|
|
990
|
+
* Reads review accounts from remaining_accounts and CPIs into Identity.
|
|
991
|
+
* @param {PublicKey|string} caller - Transaction signer + fee payer
|
|
992
|
+
* @param {string|Buffer} agentIdOrHash
|
|
993
|
+
* @param {PublicKey[]} reviewAccounts - Array of Review account pubkeys to include
|
|
994
|
+
* @returns {{ transaction: Transaction }}
|
|
995
|
+
*/
|
|
996
|
+
async buildRecomputeReputation(caller, agentIdOrHash, reviewAccounts = []) {
|
|
997
|
+
const callerKey = new PublicKey(caller);
|
|
998
|
+
const [genesisPDA] = getGenesisPDA(agentIdOrHash, this.network);
|
|
999
|
+
const [repAuthority] = getV3ReputationAuthorityPDA(this.network);
|
|
1000
|
+
|
|
1001
|
+
const disc = anchorDiscriminator('recompute_reputation');
|
|
1002
|
+
|
|
1003
|
+
const keys = [
|
|
1004
|
+
{ pubkey: genesisPDA, isSigner: false, isWritable: true },
|
|
1005
|
+
{ pubkey: repAuthority, isSigner: false, isWritable: false },
|
|
1006
|
+
{ pubkey: this.programIds.IDENTITY, isSigner: false, isWritable: false },
|
|
1007
|
+
{ pubkey: callerKey, isSigner: true, isWritable: true },
|
|
1008
|
+
];
|
|
1009
|
+
|
|
1010
|
+
// Add review accounts as remaining_accounts (read-only)
|
|
1011
|
+
for (const acct of reviewAccounts) {
|
|
1012
|
+
keys.push({ pubkey: new PublicKey(acct), isSigner: false, isWritable: false });
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
const ix = new TransactionInstruction({
|
|
1016
|
+
programId: this.programIds.REPUTATION,
|
|
1017
|
+
keys,
|
|
1018
|
+
data: disc,
|
|
1019
|
+
});
|
|
1020
|
+
|
|
1021
|
+
const tx = new Transaction().add(ix);
|
|
1022
|
+
tx.feePayer = callerKey;
|
|
1023
|
+
tx.recentBlockhash = (await this.connection.getLatestBlockhash()).blockhash;
|
|
1024
|
+
|
|
1025
|
+
return { transaction: tx };
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
// ═══════════════════════════════════════════════════
|
|
1029
|
+
// VALIDATION — Permissionless Recompute
|
|
1030
|
+
// ═══════════════════════════════════════════════════
|
|
1031
|
+
|
|
1032
|
+
/**
|
|
1033
|
+
* Build recomputeLevel transaction (permissionless).
|
|
1034
|
+
* Reads attestation accounts from remaining_accounts and CPIs into Identity.
|
|
1035
|
+
* @param {PublicKey|string} caller
|
|
1036
|
+
* @param {string|Buffer} agentIdOrHash
|
|
1037
|
+
* @param {PublicKey[]} attestationAccounts - Array of Attestation account pubkeys
|
|
1038
|
+
* @returns {{ transaction: Transaction }}
|
|
1039
|
+
*/
|
|
1040
|
+
async buildRecomputeLevel(caller, agentIdOrHash, attestationAccounts = []) {
|
|
1041
|
+
const callerKey = new PublicKey(caller);
|
|
1042
|
+
const [genesisPDA] = getGenesisPDA(agentIdOrHash, this.network);
|
|
1043
|
+
const [valAuthority] = getV3ValidationAuthorityPDA(this.network);
|
|
1044
|
+
|
|
1045
|
+
const disc = anchorDiscriminator('recompute_level');
|
|
1046
|
+
|
|
1047
|
+
const keys = [
|
|
1048
|
+
{ pubkey: genesisPDA, isSigner: false, isWritable: true },
|
|
1049
|
+
{ pubkey: valAuthority, isSigner: false, isWritable: false },
|
|
1050
|
+
{ pubkey: this.programIds.IDENTITY, isSigner: false, isWritable: false },
|
|
1051
|
+
{ pubkey: callerKey, isSigner: true, isWritable: true },
|
|
1052
|
+
];
|
|
1053
|
+
|
|
1054
|
+
for (const acct of attestationAccounts) {
|
|
1055
|
+
keys.push({ pubkey: new PublicKey(acct), isSigner: false, isWritable: false });
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
const ix = new TransactionInstruction({
|
|
1059
|
+
programId: this.programIds.VALIDATION,
|
|
1060
|
+
keys,
|
|
1061
|
+
data: disc,
|
|
1062
|
+
});
|
|
1063
|
+
|
|
1064
|
+
const tx = new Transaction().add(ix);
|
|
1065
|
+
tx.feePayer = callerKey;
|
|
1066
|
+
tx.recentBlockhash = (await this.connection.getLatestBlockhash()).blockhash;
|
|
1067
|
+
|
|
1068
|
+
return { transaction: tx };
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
// ═══════════════════════════════════════════════════
|
|
1072
|
+
// ESCROW V3 — Identity-Verified On-Chain Escrow
|
|
1073
|
+
// ═══════════════════════════════════════════════════
|
|
1074
|
+
|
|
1075
|
+
/**
|
|
1076
|
+
* Build createEscrow transaction (V3 — identity-verified).
|
|
1077
|
+
*
|
|
1078
|
+
* Creates an escrow between a client and a verified SATP V3 agent.
|
|
1079
|
+
* Verifies agent's Genesis Record on-chain (PDA derivation + owner check).
|
|
1080
|
+
* Optionally enforces minimum verification level and born status.
|
|
1081
|
+
*
|
|
1082
|
+
* PDA seeds: ["escrow_v3", client, description_hash, nonce_le_bytes]
|
|
1083
|
+
*
|
|
1084
|
+
* @param {PublicKey|string} client - Client wallet (signer + payer)
|
|
1085
|
+
* @param {PublicKey|string} agentWallet - Agent's wallet to receive funds
|
|
1086
|
+
* @param {string} agentId - Agent identifier (for Genesis Record lookup)
|
|
1087
|
+
* @param {number} amount - Lamports to escrow
|
|
1088
|
+
* @param {string|Buffer} descriptionOrHash - Job description string (will be SHA-256 hashed) or 32-byte hash buffer
|
|
1089
|
+
* @param {number} deadline - Unix timestamp deadline
|
|
1090
|
+
* @param {number} [nonce=0] - Nonce for uniqueness (multiple escrows between same parties)
|
|
1091
|
+
* @param {object} [opts={}] - Optional trust requirements
|
|
1092
|
+
* @param {number} [opts.minVerificationLevel=0] - Minimum verification level (0-5)
|
|
1093
|
+
* @param {boolean} [opts.requireBorn=false] - Require agent to have completed burn-to-become
|
|
1094
|
+
* @param {PublicKey|string} [opts.arbiter] - Arbiter for dispute resolution (defaults to client)
|
|
1095
|
+
* @returns {{ transaction: Transaction, escrowPDA: PublicKey, descriptionHash: Buffer }}
|
|
1096
|
+
*/
|
|
1097
|
+
async buildCreateEscrow(client, agentWallet, agentId, amount, descriptionOrHash, deadline, nonce = 0, opts = {}) {
|
|
1098
|
+
const clientKey = new PublicKey(client);
|
|
1099
|
+
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();
|
|
1103
|
+
|
|
1104
|
+
const [escrowPDA] = getV3EscrowPDA(clientKey, descriptionHash, nonce, this.network);
|
|
1105
|
+
const [agentIdentityPDA] = getGenesisPDA(agentId, this.network);
|
|
1106
|
+
|
|
1107
|
+
const arbiterKey = opts.arbiter ? new PublicKey(opts.arbiter) : clientKey;
|
|
1108
|
+
const minVerificationLevel = opts.minVerificationLevel || 0;
|
|
1109
|
+
const requireBorn = opts.requireBorn || false;
|
|
1110
|
+
|
|
1111
|
+
const disc = anchorDiscriminator('create_escrow');
|
|
1112
|
+
|
|
1113
|
+
// Serialize args: agent_id (String), amount (u64), description_hash ([u8;32]),
|
|
1114
|
+
// deadline (i64), nonce (u64), min_verification_level (u8), require_born (bool)
|
|
1115
|
+
const amountBuf = Buffer.alloc(8);
|
|
1116
|
+
amountBuf.writeBigUInt64LE(BigInt(amount));
|
|
1117
|
+
const deadlineBuf = Buffer.alloc(8);
|
|
1118
|
+
deadlineBuf.writeBigInt64LE(BigInt(deadline));
|
|
1119
|
+
const nonceBuf = Buffer.alloc(8);
|
|
1120
|
+
nonceBuf.writeBigUInt64LE(BigInt(nonce));
|
|
1121
|
+
|
|
1122
|
+
const data = Buffer.concat([
|
|
1123
|
+
disc,
|
|
1124
|
+
serializeString(agentId), // String
|
|
1125
|
+
amountBuf, // u64
|
|
1126
|
+
descriptionHash, // [u8; 32]
|
|
1127
|
+
deadlineBuf, // i64
|
|
1128
|
+
nonceBuf, // u64
|
|
1129
|
+
Buffer.from([minVerificationLevel]), // u8
|
|
1130
|
+
Buffer.from([requireBorn ? 1 : 0]), // bool
|
|
1131
|
+
]);
|
|
1132
|
+
|
|
1133
|
+
const ix = new TransactionInstruction({
|
|
1134
|
+
programId: this.programIds.ESCROW,
|
|
1135
|
+
keys: [
|
|
1136
|
+
{ pubkey: clientKey, isSigner: true, isWritable: true },
|
|
1137
|
+
{ pubkey: agentWalletKey, isSigner: false, isWritable: false },
|
|
1138
|
+
{ pubkey: agentIdentityPDA, isSigner: false, isWritable: false },
|
|
1139
|
+
{ pubkey: arbiterKey, isSigner: false, isWritable: false },
|
|
1140
|
+
{ pubkey: escrowPDA, isSigner: false, isWritable: true },
|
|
1141
|
+
{ pubkey: SystemProgram.programId, isSigner: false, isWritable: false },
|
|
1142
|
+
],
|
|
1143
|
+
data,
|
|
1144
|
+
});
|
|
1145
|
+
|
|
1146
|
+
const tx = new Transaction().add(ix);
|
|
1147
|
+
tx.feePayer = clientKey;
|
|
1148
|
+
tx.recentBlockhash = (await this.connection.getLatestBlockhash()).blockhash;
|
|
1149
|
+
|
|
1150
|
+
return { transaction: tx, escrowPDA, descriptionHash };
|
|
1151
|
+
}
|
|
1152
|
+
|
|
1153
|
+
/**
|
|
1154
|
+
* Build submitWork transaction (agent submits work proof).
|
|
1155
|
+
* @param {PublicKey|string} agent - Agent wallet (signer)
|
|
1156
|
+
* @param {PublicKey|string} escrowPDA - Escrow account address
|
|
1157
|
+
* @param {string|Buffer} workProofOrHash - Work proof string (SHA-256 hashed) or 32-byte hash
|
|
1158
|
+
* @returns {{ transaction: Transaction, workHash: Buffer }}
|
|
1159
|
+
*/
|
|
1160
|
+
async buildSubmitWork(agent, escrowPDA, workProofOrHash) {
|
|
1161
|
+
const agentKey = new PublicKey(agent);
|
|
1162
|
+
const escrowKey = new PublicKey(escrowPDA);
|
|
1163
|
+
const workHash = Buffer.isBuffer(workProofOrHash) && workProofOrHash.length === 32
|
|
1164
|
+
? workProofOrHash
|
|
1165
|
+
: crypto.createHash('sha256').update(typeof workProofOrHash === 'string' ? workProofOrHash : Buffer.from(workProofOrHash)).digest();
|
|
1166
|
+
|
|
1167
|
+
const disc = anchorDiscriminator('submit_work');
|
|
1168
|
+
const data = Buffer.concat([disc, workHash]);
|
|
1169
|
+
|
|
1170
|
+
const ix = new TransactionInstruction({
|
|
1171
|
+
programId: this.programIds.ESCROW,
|
|
1172
|
+
keys: [
|
|
1173
|
+
{ pubkey: escrowKey, isSigner: false, isWritable: true },
|
|
1174
|
+
{ pubkey: agentKey, isSigner: true, isWritable: false },
|
|
1175
|
+
],
|
|
1176
|
+
data,
|
|
1177
|
+
});
|
|
1178
|
+
|
|
1179
|
+
const tx = new Transaction().add(ix);
|
|
1180
|
+
tx.feePayer = agentKey;
|
|
1181
|
+
tx.recentBlockhash = (await this.connection.getLatestBlockhash()).blockhash;
|
|
1182
|
+
|
|
1183
|
+
return { transaction: tx, workHash };
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1186
|
+
/**
|
|
1187
|
+
* Build release transaction (client releases full remaining funds to agent).
|
|
1188
|
+
* @param {PublicKey|string} client - Client wallet (signer)
|
|
1189
|
+
* @param {PublicKey|string} agent - Agent wallet (receives funds)
|
|
1190
|
+
* @param {PublicKey|string} escrowPDA - Escrow account address
|
|
1191
|
+
* @returns {{ transaction: Transaction }}
|
|
1192
|
+
*/
|
|
1193
|
+
async buildEscrowRelease(client, agent, escrowPDA) {
|
|
1194
|
+
const clientKey = new PublicKey(client);
|
|
1195
|
+
const agentKey = new PublicKey(agent);
|
|
1196
|
+
const escrowKey = new PublicKey(escrowPDA);
|
|
1197
|
+
|
|
1198
|
+
const disc = anchorDiscriminator('release');
|
|
1199
|
+
|
|
1200
|
+
const ix = new TransactionInstruction({
|
|
1201
|
+
programId: this.programIds.ESCROW,
|
|
1202
|
+
keys: [
|
|
1203
|
+
{ pubkey: escrowKey, isSigner: false, isWritable: true },
|
|
1204
|
+
{ pubkey: clientKey, isSigner: true, isWritable: false },
|
|
1205
|
+
{ pubkey: agentKey, isSigner: false, isWritable: true },
|
|
1206
|
+
],
|
|
1207
|
+
data: disc,
|
|
1208
|
+
});
|
|
1209
|
+
|
|
1210
|
+
const tx = new Transaction().add(ix);
|
|
1211
|
+
tx.feePayer = clientKey;
|
|
1212
|
+
tx.recentBlockhash = (await this.connection.getLatestBlockhash()).blockhash;
|
|
1213
|
+
|
|
1214
|
+
return { transaction: tx };
|
|
1215
|
+
}
|
|
1216
|
+
|
|
1217
|
+
/**
|
|
1218
|
+
* Build partialRelease transaction (milestone payment).
|
|
1219
|
+
* @param {PublicKey|string} client - Client wallet (signer)
|
|
1220
|
+
* @param {PublicKey|string} agent - Agent wallet (receives funds)
|
|
1221
|
+
* @param {PublicKey|string} escrowPDA - Escrow account address
|
|
1222
|
+
* @param {number} amount - Lamports to release
|
|
1223
|
+
* @returns {{ transaction: Transaction }}
|
|
1224
|
+
*/
|
|
1225
|
+
async buildPartialRelease(client, agent, escrowPDA, amount) {
|
|
1226
|
+
const clientKey = new PublicKey(client);
|
|
1227
|
+
const agentKey = new PublicKey(agent);
|
|
1228
|
+
const escrowKey = new PublicKey(escrowPDA);
|
|
1229
|
+
|
|
1230
|
+
const disc = anchorDiscriminator('partial_release');
|
|
1231
|
+
const amountBuf = Buffer.alloc(8);
|
|
1232
|
+
amountBuf.writeBigUInt64LE(BigInt(amount));
|
|
1233
|
+
|
|
1234
|
+
const data = Buffer.concat([disc, amountBuf]);
|
|
1235
|
+
|
|
1236
|
+
const ix = new TransactionInstruction({
|
|
1237
|
+
programId: this.programIds.ESCROW,
|
|
1238
|
+
keys: [
|
|
1239
|
+
{ pubkey: escrowKey, isSigner: false, isWritable: true },
|
|
1240
|
+
{ pubkey: clientKey, isSigner: true, isWritable: false },
|
|
1241
|
+
{ pubkey: agentKey, isSigner: false, isWritable: true },
|
|
1242
|
+
],
|
|
1243
|
+
data,
|
|
1244
|
+
});
|
|
1245
|
+
|
|
1246
|
+
const tx = new Transaction().add(ix);
|
|
1247
|
+
tx.feePayer = clientKey;
|
|
1248
|
+
tx.recentBlockhash = (await this.connection.getLatestBlockhash()).blockhash;
|
|
1249
|
+
|
|
1250
|
+
return { transaction: tx };
|
|
1251
|
+
}
|
|
1252
|
+
|
|
1253
|
+
/**
|
|
1254
|
+
* Build cancel transaction (client cancels after deadline, gets refund).
|
|
1255
|
+
* @param {PublicKey|string} client - Client wallet (signer)
|
|
1256
|
+
* @param {PublicKey|string} escrowPDA - Escrow account address
|
|
1257
|
+
* @returns {{ transaction: Transaction }}
|
|
1258
|
+
*/
|
|
1259
|
+
async buildCancelEscrow(client, escrowPDA) {
|
|
1260
|
+
const clientKey = new PublicKey(client);
|
|
1261
|
+
const escrowKey = new PublicKey(escrowPDA);
|
|
1262
|
+
|
|
1263
|
+
const disc = anchorDiscriminator('cancel');
|
|
1264
|
+
|
|
1265
|
+
const ix = new TransactionInstruction({
|
|
1266
|
+
programId: this.programIds.ESCROW,
|
|
1267
|
+
keys: [
|
|
1268
|
+
{ pubkey: escrowKey, isSigner: false, isWritable: true },
|
|
1269
|
+
{ pubkey: clientKey, isSigner: true, isWritable: true },
|
|
1270
|
+
],
|
|
1271
|
+
data: disc,
|
|
1272
|
+
});
|
|
1273
|
+
|
|
1274
|
+
const tx = new Transaction().add(ix);
|
|
1275
|
+
tx.feePayer = clientKey;
|
|
1276
|
+
tx.recentBlockhash = (await this.connection.getLatestBlockhash()).blockhash;
|
|
1277
|
+
|
|
1278
|
+
return { transaction: tx };
|
|
1279
|
+
}
|
|
1280
|
+
|
|
1281
|
+
/**
|
|
1282
|
+
* Build raiseDispute transaction (either client or agent).
|
|
1283
|
+
* @param {PublicKey|string} signer - Client or agent wallet (signer)
|
|
1284
|
+
* @param {PublicKey|string} escrowPDA - Escrow account address
|
|
1285
|
+
* @param {string|Buffer} reasonOrHash - Dispute reason string (SHA-256 hashed) or 32-byte hash
|
|
1286
|
+
* @returns {{ transaction: Transaction, reasonHash: Buffer }}
|
|
1287
|
+
*/
|
|
1288
|
+
async buildRaiseDispute(signer, escrowPDA, reasonOrHash) {
|
|
1289
|
+
const signerKey = new PublicKey(signer);
|
|
1290
|
+
const escrowKey = new PublicKey(escrowPDA);
|
|
1291
|
+
const reasonHash = Buffer.isBuffer(reasonOrHash) && reasonOrHash.length === 32
|
|
1292
|
+
? reasonOrHash
|
|
1293
|
+
: crypto.createHash('sha256').update(typeof reasonOrHash === 'string' ? reasonOrHash : Buffer.from(reasonOrHash)).digest();
|
|
1294
|
+
|
|
1295
|
+
const disc = anchorDiscriminator('raise_dispute');
|
|
1296
|
+
const data = Buffer.concat([disc, reasonHash]);
|
|
1297
|
+
|
|
1298
|
+
const ix = new TransactionInstruction({
|
|
1299
|
+
programId: this.programIds.ESCROW,
|
|
1300
|
+
keys: [
|
|
1301
|
+
{ pubkey: escrowKey, isSigner: false, isWritable: true },
|
|
1302
|
+
{ pubkey: signerKey, isSigner: true, isWritable: false },
|
|
1303
|
+
],
|
|
1304
|
+
data,
|
|
1305
|
+
});
|
|
1306
|
+
|
|
1307
|
+
const tx = new Transaction().add(ix);
|
|
1308
|
+
tx.feePayer = signerKey;
|
|
1309
|
+
tx.recentBlockhash = (await this.connection.getLatestBlockhash()).blockhash;
|
|
1310
|
+
|
|
1311
|
+
return { transaction: tx, reasonHash };
|
|
1312
|
+
}
|
|
1313
|
+
|
|
1314
|
+
/**
|
|
1315
|
+
* Build resolveDispute transaction (arbiter splits funds).
|
|
1316
|
+
* @param {PublicKey|string} arbiter - Designated arbiter (signer)
|
|
1317
|
+
* @param {PublicKey|string} agent - Agent wallet (receives agent_amount)
|
|
1318
|
+
* @param {PublicKey|string} clientWallet - Client wallet (receives client_amount)
|
|
1319
|
+
* @param {PublicKey|string} escrowPDA - Escrow account address
|
|
1320
|
+
* @param {number} agentAmount - Lamports to release to agent
|
|
1321
|
+
* @param {number} clientAmount - Lamports to refund to client
|
|
1322
|
+
* @returns {{ transaction: Transaction }}
|
|
1323
|
+
*/
|
|
1324
|
+
async buildResolveDispute(arbiter, agent, clientWallet, escrowPDA, agentAmount, clientAmount) {
|
|
1325
|
+
const arbiterKey = new PublicKey(arbiter);
|
|
1326
|
+
const agentKey = new PublicKey(agent);
|
|
1327
|
+
const clientKey = new PublicKey(clientWallet);
|
|
1328
|
+
const escrowKey = new PublicKey(escrowPDA);
|
|
1329
|
+
|
|
1330
|
+
const disc = anchorDiscriminator('resolve_dispute');
|
|
1331
|
+
const agentAmtBuf = Buffer.alloc(8);
|
|
1332
|
+
agentAmtBuf.writeBigUInt64LE(BigInt(agentAmount));
|
|
1333
|
+
const clientAmtBuf = Buffer.alloc(8);
|
|
1334
|
+
clientAmtBuf.writeBigUInt64LE(BigInt(clientAmount));
|
|
1335
|
+
|
|
1336
|
+
const data = Buffer.concat([disc, agentAmtBuf, clientAmtBuf]);
|
|
1337
|
+
|
|
1338
|
+
const ix = new TransactionInstruction({
|
|
1339
|
+
programId: this.programIds.ESCROW,
|
|
1340
|
+
keys: [
|
|
1341
|
+
{ pubkey: escrowKey, isSigner: false, isWritable: true },
|
|
1342
|
+
{ pubkey: arbiterKey, isSigner: true, isWritable: false },
|
|
1343
|
+
{ pubkey: agentKey, isSigner: false, isWritable: true },
|
|
1344
|
+
{ pubkey: clientKey, isSigner: false, isWritable: true },
|
|
1345
|
+
],
|
|
1346
|
+
data,
|
|
1347
|
+
});
|
|
1348
|
+
|
|
1349
|
+
const tx = new Transaction().add(ix);
|
|
1350
|
+
tx.feePayer = arbiterKey;
|
|
1351
|
+
tx.recentBlockhash = (await this.connection.getLatestBlockhash()).blockhash;
|
|
1352
|
+
|
|
1353
|
+
return { transaction: tx };
|
|
1354
|
+
}
|
|
1355
|
+
|
|
1356
|
+
/**
|
|
1357
|
+
* Build extendDeadline transaction (client extends escrow deadline).
|
|
1358
|
+
* Only when Active. New deadline must be strictly after current.
|
|
1359
|
+
* @param {PublicKey|string} client - Client wallet (signer)
|
|
1360
|
+
* @param {PublicKey|string} escrowPDA - Escrow account address
|
|
1361
|
+
* @param {number} newDeadline - New Unix timestamp deadline
|
|
1362
|
+
* @returns {{ transaction: Transaction }}
|
|
1363
|
+
*/
|
|
1364
|
+
async buildExtendDeadline(client, escrowPDA, newDeadline) {
|
|
1365
|
+
const clientKey = new PublicKey(client);
|
|
1366
|
+
const escrowKey = new PublicKey(escrowPDA);
|
|
1367
|
+
|
|
1368
|
+
const disc = anchorDiscriminator('extend_deadline');
|
|
1369
|
+
const deadlineBuf = Buffer.alloc(8);
|
|
1370
|
+
deadlineBuf.writeBigInt64LE(BigInt(newDeadline));
|
|
1371
|
+
|
|
1372
|
+
const data = Buffer.concat([disc, deadlineBuf]);
|
|
1373
|
+
|
|
1374
|
+
const ix = new TransactionInstruction({
|
|
1375
|
+
programId: this.programIds.ESCROW,
|
|
1376
|
+
keys: [
|
|
1377
|
+
{ pubkey: escrowKey, isSigner: false, isWritable: true },
|
|
1378
|
+
{ pubkey: clientKey, isSigner: true, isWritable: false },
|
|
1379
|
+
],
|
|
1380
|
+
data,
|
|
1381
|
+
});
|
|
1382
|
+
|
|
1383
|
+
const tx = new Transaction().add(ix);
|
|
1384
|
+
tx.feePayer = clientKey;
|
|
1385
|
+
tx.recentBlockhash = (await this.connection.getLatestBlockhash()).blockhash;
|
|
1386
|
+
|
|
1387
|
+
return { transaction: tx };
|
|
1388
|
+
}
|
|
1389
|
+
|
|
1390
|
+
/**
|
|
1391
|
+
* Build closeEscrow transaction (returns rent to client).
|
|
1392
|
+
* Only when Released, Cancelled, or Resolved.
|
|
1393
|
+
* @param {PublicKey|string} client - Client wallet (signer, receives rent)
|
|
1394
|
+
* @param {PublicKey|string} escrowPDA - Escrow account address
|
|
1395
|
+
* @returns {{ transaction: Transaction }}
|
|
1396
|
+
*/
|
|
1397
|
+
async buildCloseEscrow(client, escrowPDA) {
|
|
1398
|
+
const clientKey = new PublicKey(client);
|
|
1399
|
+
const escrowKey = new PublicKey(escrowPDA);
|
|
1400
|
+
|
|
1401
|
+
const disc = anchorDiscriminator('close_escrow');
|
|
1402
|
+
|
|
1403
|
+
const ix = new TransactionInstruction({
|
|
1404
|
+
programId: this.programIds.ESCROW,
|
|
1405
|
+
keys: [
|
|
1406
|
+
{ pubkey: escrowKey, isSigner: false, isWritable: true },
|
|
1407
|
+
{ pubkey: clientKey, isSigner: true, isWritable: true },
|
|
1408
|
+
],
|
|
1409
|
+
data: disc,
|
|
1410
|
+
});
|
|
1411
|
+
|
|
1412
|
+
const tx = new Transaction().add(ix);
|
|
1413
|
+
tx.feePayer = clientKey;
|
|
1414
|
+
tx.recentBlockhash = (await this.connection.getLatestBlockhash()).blockhash;
|
|
1415
|
+
|
|
1416
|
+
return { transaction: tx };
|
|
1417
|
+
}
|
|
1418
|
+
|
|
1419
|
+
/**
|
|
1420
|
+
* Fetch and deserialize an Escrow V3 account.
|
|
1421
|
+
* @param {PublicKey|string} escrowPDA - Escrow account address
|
|
1422
|
+
* @returns {object|null}
|
|
1423
|
+
*/
|
|
1424
|
+
async getEscrow(escrowPDA) {
|
|
1425
|
+
const escrowKey = new PublicKey(escrowPDA);
|
|
1426
|
+
const acct = await this.connection.getAccountInfo(escrowKey);
|
|
1427
|
+
if (!acct) return null;
|
|
1428
|
+
|
|
1429
|
+
try {
|
|
1430
|
+
const data = acct.data.slice(8); // skip Anchor discriminator
|
|
1431
|
+
let offset = 0;
|
|
1432
|
+
|
|
1433
|
+
const client = new PublicKey(data.slice(offset, offset + 32)); offset += 32;
|
|
1434
|
+
const agent = new PublicKey(data.slice(offset, offset + 32)); offset += 32;
|
|
1435
|
+
const agentIdHash = data.slice(offset, offset + 32); offset += 32;
|
|
1436
|
+
const amount = Number(data.readBigUInt64LE(offset)); offset += 8;
|
|
1437
|
+
const releasedAmount = Number(data.readBigUInt64LE(offset)); offset += 8;
|
|
1438
|
+
const descriptionHash = data.slice(offset, offset + 32); offset += 32;
|
|
1439
|
+
const deadline = Number(data.readBigInt64LE(offset)); offset += 8;
|
|
1440
|
+
const nonce = Number(data.readBigUInt64LE(offset)); offset += 8;
|
|
1441
|
+
const statusByte = data[offset]; offset += 1;
|
|
1442
|
+
const minVerificationLevel = data[offset]; offset += 1;
|
|
1443
|
+
const requireBorn = data[offset] === 1; offset += 1;
|
|
1444
|
+
const createdAt = Number(data.readBigInt64LE(offset)); offset += 8;
|
|
1445
|
+
const arbiter = new PublicKey(data.slice(offset, offset + 32)); offset += 32;
|
|
1446
|
+
|
|
1447
|
+
// Option<[u8; 32]> work_hash
|
|
1448
|
+
const hasWorkHash = data[offset] === 1; offset += 1;
|
|
1449
|
+
let workHash = null;
|
|
1450
|
+
if (hasWorkHash) {
|
|
1451
|
+
workHash = Buffer.from(data.slice(offset, offset + 32)).toString('hex');
|
|
1452
|
+
}
|
|
1453
|
+
offset += 32;
|
|
1454
|
+
|
|
1455
|
+
// Option<i64> work_submitted_at
|
|
1456
|
+
const hasWorkSubmittedAt = data[offset] === 1; offset += 1;
|
|
1457
|
+
let workSubmittedAt = null;
|
|
1458
|
+
if (hasWorkSubmittedAt) {
|
|
1459
|
+
workSubmittedAt = Number(data.readBigInt64LE(offset));
|
|
1460
|
+
}
|
|
1461
|
+
offset += 8;
|
|
1462
|
+
|
|
1463
|
+
// Option<[u8; 32]> dispute_reason_hash
|
|
1464
|
+
const hasDisputeHash = data[offset] === 1; offset += 1;
|
|
1465
|
+
let disputeReasonHash = null;
|
|
1466
|
+
if (hasDisputeHash) {
|
|
1467
|
+
disputeReasonHash = Buffer.from(data.slice(offset, offset + 32)).toString('hex');
|
|
1468
|
+
}
|
|
1469
|
+
offset += 32;
|
|
1470
|
+
|
|
1471
|
+
// Option<i64> disputed_at
|
|
1472
|
+
const hasDisputedAt = data[offset] === 1; offset += 1;
|
|
1473
|
+
let disputedAt = null;
|
|
1474
|
+
if (hasDisputedAt) {
|
|
1475
|
+
disputedAt = Number(data.readBigInt64LE(offset));
|
|
1476
|
+
}
|
|
1477
|
+
offset += 8;
|
|
1478
|
+
|
|
1479
|
+
// Option<Pubkey> disputed_by
|
|
1480
|
+
const hasDisputedBy = data[offset] === 1; offset += 1;
|
|
1481
|
+
let disputedBy = null;
|
|
1482
|
+
if (hasDisputedBy) {
|
|
1483
|
+
disputedBy = new PublicKey(data.slice(offset, offset + 32)).toBase58();
|
|
1484
|
+
}
|
|
1485
|
+
offset += 32;
|
|
1486
|
+
|
|
1487
|
+
const bump = data[offset]; offset += 1;
|
|
1488
|
+
|
|
1489
|
+
const STATUS_MAP = ['Active', 'WorkSubmitted', 'Released', 'Cancelled', 'Disputed', 'Resolved'];
|
|
1490
|
+
|
|
1491
|
+
return {
|
|
1492
|
+
pda: escrowKey.toBase58(),
|
|
1493
|
+
client: client.toBase58(),
|
|
1494
|
+
agent: agent.toBase58(),
|
|
1495
|
+
agentIdHash: Buffer.from(agentIdHash).toString('hex'),
|
|
1496
|
+
amount,
|
|
1497
|
+
releasedAmount,
|
|
1498
|
+
remaining: amount - releasedAmount,
|
|
1499
|
+
descriptionHash: Buffer.from(descriptionHash).toString('hex'),
|
|
1500
|
+
deadline,
|
|
1501
|
+
nonce,
|
|
1502
|
+
status: STATUS_MAP[statusByte] || `Unknown(${statusByte})`,
|
|
1503
|
+
statusCode: statusByte,
|
|
1504
|
+
minVerificationLevel,
|
|
1505
|
+
requireBorn,
|
|
1506
|
+
createdAt,
|
|
1507
|
+
arbiter: arbiter.toBase58(),
|
|
1508
|
+
workHash,
|
|
1509
|
+
workSubmittedAt,
|
|
1510
|
+
disputeReasonHash,
|
|
1511
|
+
disputedAt,
|
|
1512
|
+
disputedBy,
|
|
1513
|
+
bump,
|
|
1514
|
+
};
|
|
1515
|
+
} catch (e) {
|
|
1516
|
+
return { pda: escrowKey.toBase58(), raw: acct.data.toString('hex'), error: e.message };
|
|
1517
|
+
}
|
|
1518
|
+
}
|
|
1519
|
+
|
|
1520
|
+
/**
|
|
1521
|
+
* Derive Escrow V3 PDA without RPC calls.
|
|
1522
|
+
* @param {PublicKey|string} client - Client wallet
|
|
1523
|
+
* @param {string|Buffer} descriptionOrHash - Job description or 32-byte hash
|
|
1524
|
+
* @param {number} [nonce=0]
|
|
1525
|
+
* @returns {{ escrowPDA: string, bump: number, descriptionHash: string }}
|
|
1526
|
+
*/
|
|
1527
|
+
getEscrowPDA(client, descriptionOrHash, nonce = 0) {
|
|
1528
|
+
const descriptionHash = Buffer.isBuffer(descriptionOrHash) && descriptionOrHash.length === 32
|
|
1529
|
+
? descriptionOrHash
|
|
1530
|
+
: crypto.createHash('sha256').update(typeof descriptionOrHash === 'string' ? descriptionOrHash : Buffer.from(descriptionOrHash)).digest();
|
|
1531
|
+
const [pda, bump] = getV3EscrowPDA(client, descriptionHash, nonce, this.network);
|
|
1532
|
+
return {
|
|
1533
|
+
escrowPDA: pda.toBase58(),
|
|
1534
|
+
bump,
|
|
1535
|
+
descriptionHash: descriptionHash.toString('hex'),
|
|
1536
|
+
};
|
|
1537
|
+
}
|
|
1538
|
+
|
|
1539
|
+
// ═══════════════════════════════════════════════════
|
|
1540
|
+
// V2 → V3 MIGRATION
|
|
1541
|
+
// ═══════════════════════════════════════════════════
|
|
1542
|
+
|
|
1543
|
+
/**
|
|
1544
|
+
* Build migrateV2ToV3 transaction.
|
|
1545
|
+
* @param {PublicKey|string} v2Authority - V2 identity authority (signer + payer)
|
|
1546
|
+
* @param {string} agentId - Agent identifier
|
|
1547
|
+
* @param {object} meta - { name, description, category, capabilities, metadataUri }
|
|
1548
|
+
* @returns {{ transaction: Transaction, genesisPDA: PublicKey }}
|
|
1549
|
+
*/
|
|
1550
|
+
async buildMigrateV2ToV3(v2Authority, agentId, meta) {
|
|
1551
|
+
const v2AuthKey = new PublicKey(v2Authority);
|
|
1552
|
+
const agentIdHash = hashAgentId(agentId);
|
|
1553
|
+
const [genesisPDA] = getGenesisPDA(agentIdHash, this.network);
|
|
1554
|
+
|
|
1555
|
+
const disc = anchorDiscriminator('migrate_v2_to_v3');
|
|
1556
|
+
const data = Buffer.concat([
|
|
1557
|
+
disc,
|
|
1558
|
+
agentIdHash,
|
|
1559
|
+
serializeString(meta.name || ''),
|
|
1560
|
+
serializeString(meta.description || ''),
|
|
1561
|
+
serializeString(meta.category || ''),
|
|
1562
|
+
serializeVecString(meta.capabilities || []),
|
|
1563
|
+
serializeString(meta.metadataUri || ''),
|
|
1564
|
+
]);
|
|
1565
|
+
|
|
1566
|
+
const ix = new TransactionInstruction({
|
|
1567
|
+
programId: this.programIds.IDENTITY,
|
|
1568
|
+
keys: [
|
|
1569
|
+
{ pubkey: genesisPDA, isSigner: false, isWritable: true },
|
|
1570
|
+
{ pubkey: v2AuthKey, isSigner: true, isWritable: true },
|
|
1571
|
+
{ pubkey: SystemProgram.programId, isSigner: false, isWritable: false },
|
|
1572
|
+
],
|
|
1573
|
+
data,
|
|
1574
|
+
});
|
|
1575
|
+
|
|
1576
|
+
const tx = new Transaction().add(ix);
|
|
1577
|
+
tx.feePayer = v2AuthKey;
|
|
1578
|
+
tx.recentBlockhash = (await this.connection.getLatestBlockhash()).blockhash;
|
|
1579
|
+
|
|
1580
|
+
return { transaction: tx, genesisPDA };
|
|
1581
|
+
}
|
|
1582
|
+
|
|
1583
|
+
// ═══════════════════════════════════════════════════
|
|
1584
|
+
// READ — Fetch On-Chain State
|
|
1585
|
+
// ═══════════════════════════════════════════════════
|
|
1586
|
+
|
|
1587
|
+
/**
|
|
1588
|
+
* Fetch Genesis Record data.
|
|
1589
|
+
* @param {string|Buffer} agentIdOrHash
|
|
1590
|
+
* @returns {object|null}
|
|
1591
|
+
*/
|
|
1592
|
+
async getGenesisRecord(agentIdOrHash) {
|
|
1593
|
+
const [pda] = getGenesisPDA(agentIdOrHash, this.network);
|
|
1594
|
+
const acct = await this.connection.getAccountInfo(pda);
|
|
1595
|
+
if (!acct) return null;
|
|
1596
|
+
|
|
1597
|
+
try {
|
|
1598
|
+
const data = acct.data.slice(8); // skip Anchor discriminator
|
|
1599
|
+
let offset = 0;
|
|
1600
|
+
|
|
1601
|
+
const agentIdHash = data.slice(offset, offset + 32); offset += 32;
|
|
1602
|
+
|
|
1603
|
+
// Read strings
|
|
1604
|
+
const readString = () => {
|
|
1605
|
+
const len = data.readUInt32LE(offset); offset += 4;
|
|
1606
|
+
const str = data.slice(offset, offset + len).toString('utf8'); offset += len;
|
|
1607
|
+
return str;
|
|
1608
|
+
};
|
|
1609
|
+
|
|
1610
|
+
const readVecString = () => {
|
|
1611
|
+
const count = data.readUInt32LE(offset); offset += 4;
|
|
1612
|
+
const arr = [];
|
|
1613
|
+
for (let i = 0; i < count; i++) arr.push(readString());
|
|
1614
|
+
return arr;
|
|
1615
|
+
};
|
|
1616
|
+
|
|
1617
|
+
const agentName = readString();
|
|
1618
|
+
const description = readString();
|
|
1619
|
+
const category = readString();
|
|
1620
|
+
const capabilities = readVecString();
|
|
1621
|
+
const metadataUri = readString();
|
|
1622
|
+
const faceImage = readString();
|
|
1623
|
+
const faceMint = new PublicKey(data.slice(offset, offset + 32)); offset += 32;
|
|
1624
|
+
const faceBurnTx = readString();
|
|
1625
|
+
const genesisRecord = Number(data.readBigInt64LE(offset)); offset += 8;
|
|
1626
|
+
// No isActive on current deployed program GTppU4E44BqXTQg...
|
|
1627
|
+
const authority = new PublicKey(data.slice(offset, offset + 32)); offset += 32;
|
|
1628
|
+
|
|
1629
|
+
// Option<Pubkey> — Borsh: 0x00 = None (1 byte only), 0x01 + 32 bytes = Some
|
|
1630
|
+
const hasPending = data[offset] === 1; offset += 1;
|
|
1631
|
+
let pendingAuthority = null;
|
|
1632
|
+
if (hasPending) {
|
|
1633
|
+
pendingAuthority = new PublicKey(data.slice(offset, offset + 32)).toBase58();
|
|
1634
|
+
offset += 32;
|
|
1635
|
+
}
|
|
1636
|
+
|
|
1637
|
+
const reputationScore = Number(data.readBigUInt64LE(offset)); offset += 8;
|
|
1638
|
+
const verificationLevel = data[offset]; offset += 1;
|
|
1639
|
+
const reputationUpdatedAt = Number(data.readBigInt64LE(offset)); offset += 8;
|
|
1640
|
+
const verificationUpdatedAt = Number(data.readBigInt64LE(offset)); offset += 8;
|
|
1641
|
+
const createdAt = Number(data.readBigInt64LE(offset)); offset += 8;
|
|
1642
|
+
const updatedAt = Number(data.readBigInt64LE(offset)); offset += 8;
|
|
1643
|
+
const bump = data[offset]; offset += 1;
|
|
1644
|
+
|
|
1645
|
+
const isBorn = genesisRecord !== 0;
|
|
1646
|
+
|
|
1647
|
+
return {
|
|
1648
|
+
pda: pda.toBase58(),
|
|
1649
|
+
agentIdHash: Buffer.from(agentIdHash).toString('hex'),
|
|
1650
|
+
agentName,
|
|
1651
|
+
description,
|
|
1652
|
+
category,
|
|
1653
|
+
capabilities,
|
|
1654
|
+
metadataUri,
|
|
1655
|
+
faceImage: faceImage || null,
|
|
1656
|
+
faceMint: faceMint.equals(PublicKey.default) ? null : faceMint.toBase58(),
|
|
1657
|
+
faceBurnTx: faceBurnTx || null,
|
|
1658
|
+
genesisRecord,
|
|
1659
|
+
isBorn,
|
|
1660
|
+
authority: authority.toBase58(),
|
|
1661
|
+
pendingAuthority,
|
|
1662
|
+
reputationScore,
|
|
1663
|
+
verificationLevel,
|
|
1664
|
+
reputationUpdatedAt,
|
|
1665
|
+
verificationUpdatedAt,
|
|
1666
|
+
createdAt,
|
|
1667
|
+
updatedAt,
|
|
1668
|
+
bump,
|
|
1669
|
+
};
|
|
1670
|
+
} catch (e) {
|
|
1671
|
+
return { pda: pda.toBase58(), raw: acct.data.toString('hex'), error: e.message };
|
|
1672
|
+
}
|
|
1673
|
+
}
|
|
1674
|
+
|
|
1675
|
+
/**
|
|
1676
|
+
* Check if a name is available.
|
|
1677
|
+
* @param {string} name
|
|
1678
|
+
* @returns {boolean} true if available (not registered)
|
|
1679
|
+
*/
|
|
1680
|
+
async isNameAvailable(name) {
|
|
1681
|
+
const [pda] = getNameRegistryPDA(name, this.network);
|
|
1682
|
+
const acct = await this.connection.getAccountInfo(pda);
|
|
1683
|
+
if (!acct) return true;
|
|
1684
|
+
// Account exists — check if still active
|
|
1685
|
+
try {
|
|
1686
|
+
const data = acct.data.slice(8);
|
|
1687
|
+
// NameRegistry: name (string), name_hash (32), identity (32), authority (32), registered_at (8), is_active (1), bump (1)
|
|
1688
|
+
let offset = 0;
|
|
1689
|
+
const len = data.readUInt32LE(offset); offset += 4 + len;
|
|
1690
|
+
offset += 32; // name_hash
|
|
1691
|
+
offset += 32; // identity
|
|
1692
|
+
offset += 32; // authority
|
|
1693
|
+
offset += 8; // registered_at
|
|
1694
|
+
const isActive = data[offset] === 1;
|
|
1695
|
+
return !isActive; // available if released
|
|
1696
|
+
} catch {
|
|
1697
|
+
return false;
|
|
1698
|
+
}
|
|
1699
|
+
}
|
|
1700
|
+
|
|
1701
|
+
// ═══════════════════════════════════════════════════
|
|
1702
|
+
// UTILITY — PDA Derivation (no RPC)
|
|
1703
|
+
// ═══════════════════════════════════════════════════
|
|
1704
|
+
|
|
1705
|
+
/**
|
|
1706
|
+
* Derive all V3 PDAs for an agent.
|
|
1707
|
+
* @param {string} agentId
|
|
1708
|
+
* @returns {object}
|
|
1709
|
+
*/
|
|
1710
|
+
getV3PDAs(agentId) {
|
|
1711
|
+
const hash = hashAgentId(agentId);
|
|
1712
|
+
const [genesis] = getGenesisPDA(hash, this.network);
|
|
1713
|
+
const [mintTracker] = getV3MintTrackerPDA(genesis, this.network);
|
|
1714
|
+
const [repAuthority] = getV3ReputationAuthorityPDA(this.network);
|
|
1715
|
+
const [valAuthority] = getV3ValidationAuthorityPDA(this.network);
|
|
1716
|
+
|
|
1717
|
+
return {
|
|
1718
|
+
agentIdHash: hash.toString('hex'),
|
|
1719
|
+
genesis: genesis.toBase58(),
|
|
1720
|
+
mintTracker: mintTracker.toBase58(),
|
|
1721
|
+
reputationAuthority: repAuthority.toBase58(),
|
|
1722
|
+
validationAuthority: valAuthority.toBase58(),
|
|
1723
|
+
};
|
|
1724
|
+
}
|
|
1725
|
+
|
|
1726
|
+
/**
|
|
1727
|
+
* Hash an agent ID to 32-byte seed.
|
|
1728
|
+
* @param {string} agentId
|
|
1729
|
+
* @returns {Buffer}
|
|
1730
|
+
*/
|
|
1731
|
+
hashAgentId(agentId) {
|
|
1732
|
+
return hashAgentId(agentId);
|
|
1733
|
+
}
|
|
1734
|
+
|
|
1735
|
+
/**
|
|
1736
|
+
* Check if an agent has a registered identity.
|
|
1737
|
+
* @param {string} agentId
|
|
1738
|
+
* @returns {boolean}
|
|
1739
|
+
*/
|
|
1740
|
+
async hasIdentity(agentId) {
|
|
1741
|
+
const record = await this.getGenesisRecord(agentId);
|
|
1742
|
+
return record !== null && !record.error;
|
|
1743
|
+
}
|
|
1744
|
+
}
|
|
1745
|
+
|
|
1746
|
+
module.exports = {
|
|
1747
|
+
createSATPClient,
|
|
1748
|
+
SATPV3SDK,
|
|
1749
|
+
anchorDiscriminator,
|
|
1750
|
+
serializeString,
|
|
1751
|
+
serializeVecString,
|
|
1752
|
+
};
|
|
1753
|
+
// ═══════════════════════════════════════════════
|
|
1754
|
+
// createSATPClient — Factory for route compatibility
|
|
1755
|
+
// ═══════════════════════════════════════════════
|
|
1756
|
+
|
|
1757
|
+
function createSATPClient(opts = {}) {
|
|
1758
|
+
const sdk = new SATPV3SDK(opts);
|
|
1759
|
+
|
|
1760
|
+
sdk.resolveAgent = function(agentId) {
|
|
1761
|
+
const agentIdHash = hashAgentId(agentId);
|
|
1762
|
+
const [pda] = getGenesisPDA(agentIdHash, sdk.network);
|
|
1763
|
+
return pda;
|
|
1764
|
+
};
|
|
1765
|
+
|
|
1766
|
+
sdk.getNameRegistry = async function(name) {
|
|
1767
|
+
try {
|
|
1768
|
+
const nameHash = hashName(name);
|
|
1769
|
+
const [pda] = getNameRegistryPDA(nameHash, sdk.network);
|
|
1770
|
+
const info = await sdk.connection.getAccountInfo(pda);
|
|
1771
|
+
if (!info || !info.data) return null;
|
|
1772
|
+
return { pda: pda.toBase58(), data: info.data };
|
|
1773
|
+
} catch (e) { return null; }
|
|
1774
|
+
};
|
|
1775
|
+
|
|
1776
|
+
sdk.isNameTaken = async function(name) {
|
|
1777
|
+
const reg = await sdk.getNameRegistry(name);
|
|
1778
|
+
return reg !== null;
|
|
1779
|
+
};
|
|
1780
|
+
|
|
1781
|
+
sdk.getLinkedWallets = async function(agentId) {
|
|
1782
|
+
try {
|
|
1783
|
+
const agentIdHash = hashAgentId(agentId);
|
|
1784
|
+
const [genesisPDA] = getGenesisPDA(agentIdHash, sdk.network);
|
|
1785
|
+
const identityProgramId = sdk.programIds.IDENTITY;
|
|
1786
|
+
const accounts = await sdk.connection.getProgramAccounts(identityProgramId, {
|
|
1787
|
+
filters: [
|
|
1788
|
+
{ memcmp: { offset: 8, bytes: genesisPDA.toBase58() } },
|
|
1789
|
+
{ dataSize: 138 }
|
|
1790
|
+
]
|
|
1791
|
+
});
|
|
1792
|
+
return accounts.map(a => ({
|
|
1793
|
+
pubkey: a.pubkey.toBase58(),
|
|
1794
|
+
data: a.account.data,
|
|
1795
|
+
}));
|
|
1796
|
+
} catch (e) { return []; }
|
|
1797
|
+
};
|
|
1798
|
+
|
|
1799
|
+
return sdk;
|
|
1800
|
+
}
|