@brainai/satp-client 2.0.0 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +435 -51
- package/package.json +24 -4
- 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 +10 -0
- package/src/index.d.ts +266 -0
- package/src/index.js +642 -0
- package/src/pda.js +43 -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/index.js
CHANGED
|
@@ -16,6 +16,8 @@ const {
|
|
|
16
16
|
getReviewsAuthorityPDA,
|
|
17
17
|
getReviewPDA,
|
|
18
18
|
getReviewAttestationPDA,
|
|
19
|
+
getEscrowPDA,
|
|
20
|
+
getReviewV3PDA,
|
|
19
21
|
} = require('./pda');
|
|
20
22
|
const {
|
|
21
23
|
IdentityAccount, IDENTITY_SCHEMA,
|
|
@@ -287,6 +289,468 @@ class SATPSDK {
|
|
|
287
289
|
|
|
288
290
|
// ─── Utility ───────────────────────────────────────────
|
|
289
291
|
|
|
292
|
+
// ─── Escrow ─────────────────────────────────────────────
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Build a createEscrow transaction.
|
|
296
|
+
* @param {PublicKey|string} clientWallet - Client (payer + signer)
|
|
297
|
+
* @param {PublicKey|string} agentWallet - Agent to receive funds on release
|
|
298
|
+
* @param {number} amountLamports - Amount in lamports to escrow
|
|
299
|
+
* @param {string} description - Job description (will be SHA256-hashed for PDA seed)
|
|
300
|
+
* @param {number} deadlineUnix - Unix timestamp deadline
|
|
301
|
+
* @returns {{ transaction: Transaction, escrowPDA: PublicKey, descriptionHash: Buffer }}
|
|
302
|
+
*/
|
|
303
|
+
async buildCreateEscrow(clientWallet, agentWallet, amountLamports, description, deadlineUnix) {
|
|
304
|
+
const clientKey = new PublicKey(clientWallet);
|
|
305
|
+
const agentKey = new PublicKey(agentWallet);
|
|
306
|
+
const descHash = crypto.createHash('sha256').update(description).digest();
|
|
307
|
+
const [escrowPDA] = getEscrowPDA(clientKey, descHash, this.network);
|
|
308
|
+
|
|
309
|
+
const disc = anchorDiscriminator('create_escrow');
|
|
310
|
+
|
|
311
|
+
// Serialize: agent (32) + amount (u64 LE 8) + description_hash (32) + deadline (i64 LE 8)
|
|
312
|
+
const agentBuf = agentKey.toBuffer();
|
|
313
|
+
const amountBuf = Buffer.alloc(8);
|
|
314
|
+
amountBuf.writeBigUInt64LE(BigInt(amountLamports));
|
|
315
|
+
const deadlineBuf = Buffer.alloc(8);
|
|
316
|
+
deadlineBuf.writeBigInt64LE(BigInt(deadlineUnix));
|
|
317
|
+
|
|
318
|
+
const data = Buffer.concat([disc, agentBuf, amountBuf, descHash, deadlineBuf]);
|
|
319
|
+
|
|
320
|
+
const ix = new TransactionInstruction({
|
|
321
|
+
programId: this.programIds.ESCROW,
|
|
322
|
+
keys: [
|
|
323
|
+
{ pubkey: clientKey, isSigner: true, isWritable: true },
|
|
324
|
+
{ pubkey: escrowPDA, isSigner: false, isWritable: true },
|
|
325
|
+
{ pubkey: SystemProgram.programId, isSigner: false, isWritable: false },
|
|
326
|
+
],
|
|
327
|
+
data,
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
const tx = new Transaction().add(ix);
|
|
331
|
+
tx.feePayer = clientKey;
|
|
332
|
+
tx.recentBlockhash = (await this.connection.getLatestBlockhash()).blockhash;
|
|
333
|
+
|
|
334
|
+
return { transaction: tx, escrowPDA, descriptionHash: descHash };
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Build a release transaction (client releases funds to agent).
|
|
339
|
+
* @param {PublicKey|string} clientWallet - Client (signer)
|
|
340
|
+
* @param {PublicKey|string} agentWallet - Agent receiving funds
|
|
341
|
+
* @param {PublicKey|string} escrowPDA - Escrow account PDA
|
|
342
|
+
* @returns {{ transaction: Transaction }}
|
|
343
|
+
*/
|
|
344
|
+
async buildRelease(clientWallet, agentWallet, escrowPDA) {
|
|
345
|
+
const clientKey = new PublicKey(clientWallet);
|
|
346
|
+
const agentKey = new PublicKey(agentWallet);
|
|
347
|
+
const escrowKey = new PublicKey(escrowPDA);
|
|
348
|
+
|
|
349
|
+
const disc = anchorDiscriminator('release');
|
|
350
|
+
|
|
351
|
+
const ix = new TransactionInstruction({
|
|
352
|
+
programId: this.programIds.ESCROW,
|
|
353
|
+
keys: [
|
|
354
|
+
{ pubkey: escrowKey, isSigner: false, isWritable: true },
|
|
355
|
+
{ pubkey: clientKey, isSigner: true, isWritable: false },
|
|
356
|
+
{ pubkey: agentKey, isSigner: false, isWritable: true },
|
|
357
|
+
],
|
|
358
|
+
data: disc,
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
const tx = new Transaction().add(ix);
|
|
362
|
+
tx.feePayer = clientKey;
|
|
363
|
+
tx.recentBlockhash = (await this.connection.getLatestBlockhash()).blockhash;
|
|
364
|
+
|
|
365
|
+
return { transaction: tx };
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* Build a submitWork transaction (agent submits work proof).
|
|
370
|
+
* @param {PublicKey|string} agentWallet - Agent (signer)
|
|
371
|
+
* @param {PublicKey|string} escrowPDA - Escrow account PDA
|
|
372
|
+
* @param {string} workProof - Work proof (will be SHA256-hashed)
|
|
373
|
+
* @returns {{ transaction: Transaction, workHash: Buffer }}
|
|
374
|
+
*/
|
|
375
|
+
async buildSubmitWork(agentWallet, escrowPDA, workProof) {
|
|
376
|
+
const agentKey = new PublicKey(agentWallet);
|
|
377
|
+
const escrowKey = new PublicKey(escrowPDA);
|
|
378
|
+
const workHash = crypto.createHash('sha256').update(workProof).digest();
|
|
379
|
+
|
|
380
|
+
const disc = anchorDiscriminator('submit_work');
|
|
381
|
+
const data = Buffer.concat([disc, workHash]);
|
|
382
|
+
|
|
383
|
+
const ix = new TransactionInstruction({
|
|
384
|
+
programId: this.programIds.ESCROW,
|
|
385
|
+
keys: [
|
|
386
|
+
{ pubkey: escrowKey, isSigner: false, isWritable: true },
|
|
387
|
+
{ pubkey: agentKey, isSigner: true, isWritable: false },
|
|
388
|
+
],
|
|
389
|
+
data,
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
const tx = new Transaction().add(ix);
|
|
393
|
+
tx.feePayer = agentKey;
|
|
394
|
+
tx.recentBlockhash = (await this.connection.getLatestBlockhash()).blockhash;
|
|
395
|
+
|
|
396
|
+
return { transaction: tx, workHash };
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
/**
|
|
400
|
+
* Build a cancel transaction (client cancels after deadline).
|
|
401
|
+
* @param {PublicKey|string} clientWallet - Client (signer)
|
|
402
|
+
* @param {PublicKey|string} escrowPDA - Escrow account PDA
|
|
403
|
+
* @returns {{ transaction: Transaction }}
|
|
404
|
+
*/
|
|
405
|
+
async buildCancel(clientWallet, escrowPDA) {
|
|
406
|
+
const clientKey = new PublicKey(clientWallet);
|
|
407
|
+
const escrowKey = new PublicKey(escrowPDA);
|
|
408
|
+
|
|
409
|
+
const disc = anchorDiscriminator('cancel');
|
|
410
|
+
|
|
411
|
+
const ix = new TransactionInstruction({
|
|
412
|
+
programId: this.programIds.ESCROW,
|
|
413
|
+
keys: [
|
|
414
|
+
{ pubkey: escrowKey, isSigner: false, isWritable: true },
|
|
415
|
+
{ pubkey: clientKey, isSigner: true, isWritable: true },
|
|
416
|
+
],
|
|
417
|
+
data: disc,
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
const tx = new Transaction().add(ix);
|
|
421
|
+
tx.feePayer = clientKey;
|
|
422
|
+
tx.recentBlockhash = (await this.connection.getLatestBlockhash()).blockhash;
|
|
423
|
+
|
|
424
|
+
return { transaction: tx };
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
/**
|
|
428
|
+
* Build a raiseDispute transaction (either party disputes).
|
|
429
|
+
* @param {PublicKey|string} signerWallet - Client or agent (signer)
|
|
430
|
+
* @param {PublicKey|string} escrowPDA - Escrow account PDA
|
|
431
|
+
* @returns {{ transaction: Transaction }}
|
|
432
|
+
*/
|
|
433
|
+
async buildRaiseDispute(signerWallet, escrowPDA) {
|
|
434
|
+
const signerKey = new PublicKey(signerWallet);
|
|
435
|
+
const escrowKey = new PublicKey(escrowPDA);
|
|
436
|
+
|
|
437
|
+
const disc = anchorDiscriminator('raise_dispute');
|
|
438
|
+
|
|
439
|
+
const ix = new TransactionInstruction({
|
|
440
|
+
programId: this.programIds.ESCROW,
|
|
441
|
+
keys: [
|
|
442
|
+
{ pubkey: escrowKey, isSigner: false, isWritable: true },
|
|
443
|
+
{ pubkey: signerKey, isSigner: true, isWritable: false },
|
|
444
|
+
],
|
|
445
|
+
data: disc,
|
|
446
|
+
});
|
|
447
|
+
|
|
448
|
+
const tx = new Transaction().add(ix);
|
|
449
|
+
tx.feePayer = signerKey;
|
|
450
|
+
tx.recentBlockhash = (await this.connection.getLatestBlockhash()).blockhash;
|
|
451
|
+
|
|
452
|
+
return { transaction: tx };
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
/**
|
|
456
|
+
* Build a closeEscrow transaction (returns rent to client).
|
|
457
|
+
* @param {PublicKey|string} clientWallet - Client (signer)
|
|
458
|
+
* @param {PublicKey|string} escrowPDA - Escrow account PDA
|
|
459
|
+
* @returns {{ transaction: Transaction }}
|
|
460
|
+
*/
|
|
461
|
+
async buildCloseEscrow(clientWallet, escrowPDA) {
|
|
462
|
+
const clientKey = new PublicKey(clientWallet);
|
|
463
|
+
const escrowKey = new PublicKey(escrowPDA);
|
|
464
|
+
|
|
465
|
+
const disc = anchorDiscriminator('close_escrow');
|
|
466
|
+
|
|
467
|
+
const ix = new TransactionInstruction({
|
|
468
|
+
programId: this.programIds.ESCROW,
|
|
469
|
+
keys: [
|
|
470
|
+
{ pubkey: escrowKey, isSigner: false, isWritable: true },
|
|
471
|
+
{ pubkey: clientKey, isSigner: true, isWritable: true },
|
|
472
|
+
],
|
|
473
|
+
data: disc,
|
|
474
|
+
});
|
|
475
|
+
|
|
476
|
+
const tx = new Transaction().add(ix);
|
|
477
|
+
tx.feePayer = clientKey;
|
|
478
|
+
tx.recentBlockhash = (await this.connection.getLatestBlockhash()).blockhash;
|
|
479
|
+
|
|
480
|
+
return { transaction: tx };
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
/**
|
|
484
|
+
* Build a resolveDispute transaction.
|
|
485
|
+
* Only the client (arbiter in V1) can resolve. Decides: release to agent or refund to client.
|
|
486
|
+
* @param {PublicKey|string} clientWallet - Client/arbiter (signer)
|
|
487
|
+
* @param {PublicKey|string} agentWallet - Agent account
|
|
488
|
+
* @param {PublicKey|string} escrowPDA - Escrow account PDA
|
|
489
|
+
* @param {boolean} releaseToAgent - true = release to agent, false = refund to client
|
|
490
|
+
* @returns {{ transaction: Transaction }}
|
|
491
|
+
*/
|
|
492
|
+
async buildResolveDispute(clientWallet, agentWallet, escrowPDA, releaseToAgent) {
|
|
493
|
+
const clientKey = new PublicKey(clientWallet);
|
|
494
|
+
const agentKey = new PublicKey(agentWallet);
|
|
495
|
+
const escrowKey = new PublicKey(escrowPDA);
|
|
496
|
+
|
|
497
|
+
const disc = anchorDiscriminator('resolve_dispute');
|
|
498
|
+
const data = Buffer.alloc(8 + 1);
|
|
499
|
+
disc.copy(data, 0);
|
|
500
|
+
data.writeUInt8(releaseToAgent ? 1 : 0, 8);
|
|
501
|
+
|
|
502
|
+
const ix = new TransactionInstruction({
|
|
503
|
+
programId: this.programIds.ESCROW,
|
|
504
|
+
keys: [
|
|
505
|
+
{ pubkey: escrowKey, isSigner: false, isWritable: true },
|
|
506
|
+
{ pubkey: clientKey, isSigner: true, isWritable: false }, // arbiter
|
|
507
|
+
{ pubkey: agentKey, isSigner: false, isWritable: true }, // agent
|
|
508
|
+
{ pubkey: clientKey, isSigner: false, isWritable: true }, // client_wallet
|
|
509
|
+
],
|
|
510
|
+
data,
|
|
511
|
+
});
|
|
512
|
+
|
|
513
|
+
const tx = new Transaction().add(ix);
|
|
514
|
+
tx.feePayer = clientKey;
|
|
515
|
+
tx.recentBlockhash = (await this.connection.getLatestBlockhash()).blockhash;
|
|
516
|
+
|
|
517
|
+
return { transaction: tx };
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
/**
|
|
521
|
+
* Fetch escrow state from on-chain account.
|
|
522
|
+
* @param {PublicKey|string} escrowPDA
|
|
523
|
+
* @returns {object|null} Escrow state or null if not found
|
|
524
|
+
*/
|
|
525
|
+
async getEscrow(escrowPDA) {
|
|
526
|
+
const escrowKey = new PublicKey(escrowPDA);
|
|
527
|
+
const acct = await this.connection.getAccountInfo(escrowKey);
|
|
528
|
+
if (!acct) return null;
|
|
529
|
+
|
|
530
|
+
try {
|
|
531
|
+
// Skip 8-byte Anchor discriminator
|
|
532
|
+
const data = acct.data.slice(8);
|
|
533
|
+
const client = new PublicKey(data.slice(0, 32));
|
|
534
|
+
const agent = new PublicKey(data.slice(32, 64));
|
|
535
|
+
const amount = Number(data.readBigUInt64LE(64));
|
|
536
|
+
const descriptionHash = data.slice(72, 104);
|
|
537
|
+
const deadline = Number(data.readBigInt64LE(104));
|
|
538
|
+
const statusByte = data[112];
|
|
539
|
+
const createdAt = Number(data.readBigInt64LE(113));
|
|
540
|
+
const bump = data[121];
|
|
541
|
+
|
|
542
|
+
const statusMap = ['Active', 'Released', 'Cancelled', 'WorkSubmitted', 'Disputed'];
|
|
543
|
+
const status = statusMap[statusByte] || `Unknown(${statusByte})`;
|
|
544
|
+
|
|
545
|
+
// Optional work_hash (1 byte option flag + 32 bytes)
|
|
546
|
+
let workHash = null;
|
|
547
|
+
if (data[122] === 1) {
|
|
548
|
+
workHash = data.slice(123, 155).toString('hex');
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
return {
|
|
552
|
+
client: client.toBase58(),
|
|
553
|
+
agent: agent.toBase58(),
|
|
554
|
+
amount,
|
|
555
|
+
descriptionHash: descriptionHash.toString('hex'),
|
|
556
|
+
deadline,
|
|
557
|
+
status,
|
|
558
|
+
createdAt,
|
|
559
|
+
bump,
|
|
560
|
+
workHash,
|
|
561
|
+
pda: escrowKey.toBase58(),
|
|
562
|
+
};
|
|
563
|
+
} catch (e) {
|
|
564
|
+
return { pda: escrowKey.toBase58(), raw: acct.data.toString('hex'), error: e.message };
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
// ─── Reviews V3 (Job-Scoped) ─────────────────────────────
|
|
569
|
+
|
|
570
|
+
/**
|
|
571
|
+
* Build a submitReview transaction (Reviews V3 — job-scoped).
|
|
572
|
+
* Reviewer must be a party to the completed/resolved job (poster or accepted_agent).
|
|
573
|
+
* Requires reviewer to have a registered SATP Identity.
|
|
574
|
+
*
|
|
575
|
+
* @param {PublicKey|string} reviewerWallet - Reviewer (signer, must be job party)
|
|
576
|
+
* @param {PublicKey|string} reviewerIdentityPDA - Reviewer's SATP Identity PDA
|
|
577
|
+
* @param {PublicKey|string} jobPDA - Job/Escrow account PDA
|
|
578
|
+
* @param {object} ratings - { rating, quality, reliability, communication } (all 1-5)
|
|
579
|
+
* @param {string} commentUri - URI to off-chain comment (IPFS, Arweave, etc.)
|
|
580
|
+
* @param {Buffer|string} commentHash - 32-byte SHA256 hash of comment content
|
|
581
|
+
* @returns {{ transaction: Transaction, reviewPDA: PublicKey }}
|
|
582
|
+
*/
|
|
583
|
+
async buildSubmitReview(reviewerWallet, reviewerIdentityPDA, jobPDA, ratings, commentUri, commentHash) {
|
|
584
|
+
const reviewerKey = new PublicKey(reviewerWallet);
|
|
585
|
+
const identityPDA = new PublicKey(reviewerIdentityPDA);
|
|
586
|
+
const jobKey = new PublicKey(jobPDA);
|
|
587
|
+
const [reviewPDA] = getReviewV3PDA(jobKey, reviewerKey, this.network);
|
|
588
|
+
|
|
589
|
+
const hashBuf = Buffer.isBuffer(commentHash)
|
|
590
|
+
? commentHash
|
|
591
|
+
: crypto.createHash('sha256').update(commentHash).digest();
|
|
592
|
+
|
|
593
|
+
const disc = anchorDiscriminator('submit_review');
|
|
594
|
+
const uriBytes = Buffer.from(commentUri, 'utf8');
|
|
595
|
+
|
|
596
|
+
// Serialize: rating (u8) + quality (u8) + reliability (u8) + communication (u8)
|
|
597
|
+
// + uri_len (u32 LE) + uri_bytes + hash (32 bytes)
|
|
598
|
+
const data = Buffer.concat([
|
|
599
|
+
disc,
|
|
600
|
+
Buffer.from([ratings.rating]),
|
|
601
|
+
Buffer.from([ratings.quality]),
|
|
602
|
+
Buffer.from([ratings.reliability]),
|
|
603
|
+
Buffer.from([ratings.communication]),
|
|
604
|
+
Buffer.from(new Uint32Array([uriBytes.length]).buffer),
|
|
605
|
+
uriBytes,
|
|
606
|
+
hashBuf,
|
|
607
|
+
]);
|
|
608
|
+
|
|
609
|
+
// Identity program ID (hardcoded in Reviews V3 program)
|
|
610
|
+
const IDENTITY_PROGRAM = new PublicKey('EJtQh4Gyg88zXvSmFpxYkkeZsPwTsjfm4LvjmPQX1FD3');
|
|
611
|
+
// Escrow program ID
|
|
612
|
+
const ESCROW_PROGRAM = this.programIds.ESCROW;
|
|
613
|
+
|
|
614
|
+
const ix = new TransactionInstruction({
|
|
615
|
+
programId: this.programIds.REVIEWS,
|
|
616
|
+
keys: [
|
|
617
|
+
{ pubkey: reviewerKey, isSigner: true, isWritable: true },
|
|
618
|
+
{ pubkey: identityPDA, isSigner: false, isWritable: false },
|
|
619
|
+
{ pubkey: jobKey, isSigner: false, isWritable: false },
|
|
620
|
+
{ pubkey: reviewPDA, isSigner: false, isWritable: true },
|
|
621
|
+
{ pubkey: IDENTITY_PROGRAM, isSigner: false, isWritable: false },
|
|
622
|
+
{ pubkey: ESCROW_PROGRAM, isSigner: false, isWritable: false },
|
|
623
|
+
{ pubkey: SystemProgram.programId, isSigner: false, isWritable: false },
|
|
624
|
+
],
|
|
625
|
+
data,
|
|
626
|
+
});
|
|
627
|
+
|
|
628
|
+
const tx = new Transaction().add(ix);
|
|
629
|
+
tx.feePayer = reviewerKey;
|
|
630
|
+
tx.recentBlockhash = (await this.connection.getLatestBlockhash()).blockhash;
|
|
631
|
+
|
|
632
|
+
return { transaction: tx, reviewPDA };
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
/**
|
|
636
|
+
* Build a respondToReview transaction (Reviews V3).
|
|
637
|
+
* Only the reviewed party can respond, and only once.
|
|
638
|
+
*
|
|
639
|
+
* @param {PublicKey|string} responderWallet - Reviewed party (signer)
|
|
640
|
+
* @param {PublicKey|string} reviewPDA - Review account PDA
|
|
641
|
+
* @param {string} responseUri - URI to off-chain response content
|
|
642
|
+
* @param {Buffer|string} responseHash - 32-byte SHA256 hash of response content
|
|
643
|
+
* @returns {{ transaction: Transaction }}
|
|
644
|
+
*/
|
|
645
|
+
async buildRespondToReview(responderWallet, reviewPDA, responseUri, responseHash) {
|
|
646
|
+
const responderKey = new PublicKey(responderWallet);
|
|
647
|
+
const reviewKey = new PublicKey(reviewPDA);
|
|
648
|
+
|
|
649
|
+
const hashBuf = Buffer.isBuffer(responseHash)
|
|
650
|
+
? responseHash
|
|
651
|
+
: crypto.createHash('sha256').update(responseHash).digest();
|
|
652
|
+
|
|
653
|
+
const disc = anchorDiscriminator('respond_to_review');
|
|
654
|
+
const uriBytes = Buffer.from(responseUri, 'utf8');
|
|
655
|
+
|
|
656
|
+
const data = Buffer.concat([
|
|
657
|
+
disc,
|
|
658
|
+
Buffer.from(new Uint32Array([uriBytes.length]).buffer),
|
|
659
|
+
uriBytes,
|
|
660
|
+
hashBuf,
|
|
661
|
+
]);
|
|
662
|
+
|
|
663
|
+
const ix = new TransactionInstruction({
|
|
664
|
+
programId: this.programIds.REVIEWS,
|
|
665
|
+
keys: [
|
|
666
|
+
{ pubkey: responderKey, isSigner: true, isWritable: true },
|
|
667
|
+
{ pubkey: reviewKey, isSigner: false, isWritable: true },
|
|
668
|
+
],
|
|
669
|
+
data,
|
|
670
|
+
});
|
|
671
|
+
|
|
672
|
+
const tx = new Transaction().add(ix);
|
|
673
|
+
tx.feePayer = responderKey;
|
|
674
|
+
tx.recentBlockhash = (await this.connection.getLatestBlockhash()).blockhash;
|
|
675
|
+
|
|
676
|
+
return { transaction: tx };
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
/**
|
|
680
|
+
* Fetch a review from on-chain.
|
|
681
|
+
* @param {PublicKey|string} reviewPDA
|
|
682
|
+
* @returns {object|null} Review data or null if not found
|
|
683
|
+
*/
|
|
684
|
+
async getReview(reviewPDA) {
|
|
685
|
+
const reviewKey = new PublicKey(reviewPDA);
|
|
686
|
+
const acct = await this.connection.getAccountInfo(reviewKey);
|
|
687
|
+
if (!acct) return null;
|
|
688
|
+
|
|
689
|
+
try {
|
|
690
|
+
const data = acct.data.slice(8); // skip Anchor discriminator
|
|
691
|
+
let offset = 0;
|
|
692
|
+
|
|
693
|
+
const reviewer = new PublicKey(data.slice(offset, offset + 32)); offset += 32;
|
|
694
|
+
const reviewed = new PublicKey(data.slice(offset, offset + 32)); offset += 32;
|
|
695
|
+
const jobId = Number(data.readBigUInt64LE(offset)); offset += 8;
|
|
696
|
+
const jobRef = new PublicKey(data.slice(offset, offset + 32)); offset += 32;
|
|
697
|
+
const rating = data[offset]; offset += 1;
|
|
698
|
+
const categoryQuality = data[offset]; offset += 1;
|
|
699
|
+
const categoryReliability = data[offset]; offset += 1;
|
|
700
|
+
const categoryCommunication = data[offset]; offset += 1;
|
|
701
|
+
|
|
702
|
+
// String: 4-byte length prefix + bytes
|
|
703
|
+
const uriLen = data.readUInt32LE(offset); offset += 4;
|
|
704
|
+
const commentUri = data.slice(offset, offset + uriLen).toString('utf8'); offset += uriLen;
|
|
705
|
+
|
|
706
|
+
const commentHash = data.slice(offset, offset + 32).toString('hex'); offset += 32;
|
|
707
|
+
const timestamp = Number(data.readBigInt64LE(offset)); offset += 8;
|
|
708
|
+
const hasResponse = data[offset] === 1; offset += 1;
|
|
709
|
+
|
|
710
|
+
// Response string
|
|
711
|
+
const resUriLen = data.readUInt32LE(offset); offset += 4;
|
|
712
|
+
const responseUri = data.slice(offset, offset + resUriLen).toString('utf8'); offset += resUriLen;
|
|
713
|
+
|
|
714
|
+
const responseHash = data.slice(offset, offset + 32).toString('hex'); offset += 32;
|
|
715
|
+
const responseTimestamp = Number(data.readBigInt64LE(offset)); offset += 8;
|
|
716
|
+
const bump = data[offset]; offset += 1;
|
|
717
|
+
|
|
718
|
+
return {
|
|
719
|
+
reviewer: reviewer.toBase58(),
|
|
720
|
+
reviewed: reviewed.toBase58(),
|
|
721
|
+
jobId,
|
|
722
|
+
jobRef: jobRef.toBase58(),
|
|
723
|
+
rating,
|
|
724
|
+
categoryQuality,
|
|
725
|
+
categoryReliability,
|
|
726
|
+
categoryCommunication,
|
|
727
|
+
commentUri,
|
|
728
|
+
commentHash,
|
|
729
|
+
timestamp,
|
|
730
|
+
hasResponse,
|
|
731
|
+
responseUri: hasResponse ? responseUri : null,
|
|
732
|
+
responseHash: hasResponse ? responseHash : null,
|
|
733
|
+
responseTimestamp: hasResponse ? responseTimestamp : null,
|
|
734
|
+
bump,
|
|
735
|
+
pda: reviewKey.toBase58(),
|
|
736
|
+
};
|
|
737
|
+
} catch (e) {
|
|
738
|
+
return { pda: reviewKey.toBase58(), raw: acct.data.toString('hex'), error: e.message };
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
/**
|
|
743
|
+
* Derive Review V3 PDA (job-scoped).
|
|
744
|
+
* @param {PublicKey|string} jobPDA
|
|
745
|
+
* @param {PublicKey|string} reviewer
|
|
746
|
+
* @returns {[PublicKey, number]} [pda, bump]
|
|
747
|
+
*/
|
|
748
|
+
getReviewV3PDA(jobPDA, reviewer) {
|
|
749
|
+
return getReviewV3PDA(jobPDA, reviewer, this.network);
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
// ─── Utility ───────────────────────────────────────────
|
|
753
|
+
|
|
290
754
|
/**
|
|
291
755
|
* Derive all PDAs for a wallet without RPC calls.
|
|
292
756
|
*/
|
|
@@ -308,7 +772,109 @@ class SATPSDK {
|
|
|
308
772
|
}
|
|
309
773
|
}
|
|
310
774
|
|
|
775
|
+
// V3 SDK — local extracted SATP v3 scaffold.
|
|
776
|
+
// Do not require the legacy external v3 package here; SATP-EXTRACT-001 must be self-contained.
|
|
777
|
+
const v3sdk = require('./v3-sdk');
|
|
778
|
+
const v3pda = require('./v3-pda');
|
|
779
|
+
const v3Borsh = require('./borsh-reader');
|
|
780
|
+
const { prepareIdentityAttestationRequest } = require('./attestation-request');
|
|
781
|
+
const {
|
|
782
|
+
TRUST_PACKET_SCHEMA_VERSION,
|
|
783
|
+
buildSatpTrustPacket,
|
|
784
|
+
validateSatpTrustPacket,
|
|
785
|
+
} = require('./trust-packet');
|
|
786
|
+
|
|
787
|
+
// Legacy V3 SDK wrapper — keeps string constructor compatibility while using
|
|
788
|
+
// the local extracted SATPV3SDK implementation so offline tests and consumers
|
|
789
|
+
// see the expected programIds, rpcUrl, and SDK methods.
|
|
790
|
+
class SATPV3SDK extends v3sdk.SATPV3SDK {
|
|
791
|
+
constructor(opts = {}) {
|
|
792
|
+
super(opts);
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
function createSATPClient(opts = {}) {
|
|
797
|
+
if (typeof opts === 'string') {
|
|
798
|
+
return v3sdk.createSATPClient(opts);
|
|
799
|
+
}
|
|
800
|
+
return v3sdk.createSATPClient({
|
|
801
|
+
rpcUrl: opts.rpcUrl || opts.url || opts.endpoint,
|
|
802
|
+
network: opts.network,
|
|
803
|
+
commitment: opts.commitment,
|
|
804
|
+
});
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
// Legacy borsh reader — keep for any V2 code paths
|
|
808
|
+
const borshReader = require('./borsh-reader');
|
|
809
|
+
|
|
810
|
+
|
|
811
|
+
// Fixed genesis deserializer — matches actual on-chain struct (no isActive field)
|
|
812
|
+
function _deserializeGenesisFixed(data) {
|
|
813
|
+
if (!data || data.length < 8) return null;
|
|
814
|
+
try {
|
|
815
|
+
const { PublicKey } = require('@solana/web3.js');
|
|
816
|
+
let offset = 8; // skip discriminator
|
|
817
|
+
const agentIdHashBytes = data.slice(offset, offset + 32); offset += 32;
|
|
818
|
+
const readString = () => {
|
|
819
|
+
const len = data.readUInt32LE(offset); offset += 4;
|
|
820
|
+
const str = data.slice(offset, offset + len).toString('utf8'); offset += len;
|
|
821
|
+
return str;
|
|
822
|
+
};
|
|
823
|
+
const readVecString = () => {
|
|
824
|
+
const count = data.readUInt32LE(offset); offset += 4;
|
|
825
|
+
const arr = [];
|
|
826
|
+
for (let i = 0; i < count; i++) arr.push(readString());
|
|
827
|
+
return arr;
|
|
828
|
+
};
|
|
829
|
+
const agentName = readString();
|
|
830
|
+
const description = readString();
|
|
831
|
+
const category = readString();
|
|
832
|
+
const capabilities = readVecString();
|
|
833
|
+
const metadataUri = readString();
|
|
834
|
+
const faceImage = readString();
|
|
835
|
+
const faceMint = new PublicKey(data.slice(offset, offset + 32)); offset += 32;
|
|
836
|
+
const faceBurnTx = readString();
|
|
837
|
+
const genesisRecord = Number(data.readBigInt64LE(offset)); offset += 8;
|
|
838
|
+
// NOTE: No isActive field in deployed program (SDK bug — has phantom isActive)
|
|
839
|
+
const authority = new PublicKey(data.slice(offset, offset + 32)); offset += 32;
|
|
840
|
+
const hasPending = data[offset]; offset += 1;
|
|
841
|
+
let pendingAuthority = null;
|
|
842
|
+
if (hasPending === 1) {
|
|
843
|
+
pendingAuthority = new PublicKey(data.slice(offset, offset + 32)).toBase58();
|
|
844
|
+
offset += 32;
|
|
845
|
+
}
|
|
846
|
+
const reputationScore = Number(data.readBigUInt64LE(offset)); offset += 8;
|
|
847
|
+
const verificationLevel = data[offset]; offset += 1;
|
|
848
|
+
const reputationUpdatedAt = Number(data.readBigInt64LE(offset)); offset += 8;
|
|
849
|
+
const verificationUpdatedAt = Number(data.readBigInt64LE(offset)); offset += 8;
|
|
850
|
+
const createdAt = Number(data.readBigInt64LE(offset)); offset += 8;
|
|
851
|
+
const updatedAt = Number(data.readBigInt64LE(offset)); offset += 8;
|
|
852
|
+
const bump = data[offset]; offset += 1;
|
|
853
|
+
return {
|
|
854
|
+
agentIdHash: Array.from(agentIdHashBytes),
|
|
855
|
+
agentName, description, category, capabilities, metadataUri, faceImage,
|
|
856
|
+
faceMint: faceMint.toBase58(),
|
|
857
|
+
faceBurnTx,
|
|
858
|
+
genesisRecord,
|
|
859
|
+
isBorn: genesisRecord > 0,
|
|
860
|
+
authority: authority.toBase58(),
|
|
861
|
+
pendingAuthority,
|
|
862
|
+
reputationScore,
|
|
863
|
+
verificationLevel,
|
|
864
|
+
verificationLabel: ['Unverified','Registered','Verified','Established','Trusted','Sovereign'][verificationLevel] || 'Unknown',
|
|
865
|
+
reputationPct: (reputationScore / 10000).toFixed(2),
|
|
866
|
+
reputationUpdatedAt, verificationUpdatedAt,
|
|
867
|
+
createdAt: createdAt > 0 ? new Date(createdAt * 1000).toISOString() : null,
|
|
868
|
+
updatedAt: updatedAt > 0 ? new Date(updatedAt * 1000).toISOString() : null,
|
|
869
|
+
bump,
|
|
870
|
+
};
|
|
871
|
+
} catch (e) {
|
|
872
|
+
return { error: e.message, raw: data.toString('hex').slice(0, 200) };
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
|
|
311
876
|
module.exports = {
|
|
877
|
+
// V2 SDK (backward compatible — legacy, kept for escrow V2 / old paths)
|
|
312
878
|
SATPSDK,
|
|
313
879
|
getProgramIds,
|
|
314
880
|
getIdentityPDA,
|
|
@@ -319,5 +885,81 @@ module.exports = {
|
|
|
319
885
|
getReviewsAuthorityPDA,
|
|
320
886
|
getReviewPDA,
|
|
321
887
|
getReviewAttestationPDA,
|
|
888
|
+
getEscrowPDA,
|
|
889
|
+
getReviewV3PDA,
|
|
322
890
|
anchorDiscriminator,
|
|
891
|
+
|
|
892
|
+
// V3 SDK (local extracted scaffold)
|
|
893
|
+
SATPV3SDK,
|
|
894
|
+
createSATPClient,
|
|
895
|
+
SatpV3Client: v3sdk.SATPV3SDK,
|
|
896
|
+
SatpV3Builders: v3sdk,
|
|
897
|
+
|
|
898
|
+
// V3 PDA derivation (local extracted scaffold)
|
|
899
|
+
PROGRAM_IDS: v3pda.getV3ProgramIds('devnet'),
|
|
900
|
+
getV3ProgramIds: v3pda.getV3ProgramIds,
|
|
901
|
+
hashAgentId: v3pda.hashAgentId,
|
|
902
|
+
agentIdHash: v3pda.hashAgentId,
|
|
903
|
+
hashName: v3pda.hashName,
|
|
904
|
+
getGenesisPDA: v3pda.getGenesisPDA,
|
|
905
|
+
deriveGenesisPda: v3pda.getGenesisPDA,
|
|
906
|
+
getV3ReputationAuthorityPDA: v3pda.getV3ReputationAuthorityPDA,
|
|
907
|
+
deriveReputationAuthorityPda: v3pda.getV3ReputationAuthorityPDA,
|
|
908
|
+
getV3ValidationAuthorityPDA: v3pda.getV3ValidationAuthorityPDA,
|
|
909
|
+
deriveValidationAuthorityPda: v3pda.getV3ValidationAuthorityPDA,
|
|
910
|
+
getV3MintTrackerPDA: v3pda.getV3MintTrackerPDA,
|
|
911
|
+
deriveMintTrackerPda: v3pda.getV3MintTrackerPDA,
|
|
912
|
+
getNameRegistryPDA: v3pda.getNameRegistryPDA,
|
|
913
|
+
deriveNameRegistryPda: v3pda.getNameRegistryPDA,
|
|
914
|
+
getLinkedWalletPDA: v3pda.getLinkedWalletPDA,
|
|
915
|
+
deriveLinkedWalletPda: v3pda.getLinkedWalletPDA,
|
|
916
|
+
getV3ReviewPDA: v3pda.getV3ReviewPDA,
|
|
917
|
+
deriveReviewPda: v3pda.getV3ReviewPDA,
|
|
918
|
+
getV3ReviewCounterPDA: v3pda.getV3ReviewCounterPDA,
|
|
919
|
+
deriveReviewCounterPda: v3pda.getV3ReviewCounterPDA,
|
|
920
|
+
getV3AttestationPDA: v3pda.getV3AttestationPDA,
|
|
921
|
+
deriveAttestationPda: v3pda.getV3AttestationPDA,
|
|
922
|
+
getV3EscrowPDA: v3pda.getV3EscrowPDA,
|
|
923
|
+
deriveEscrowPda: v3pda.getV3EscrowPDA,
|
|
924
|
+
deriveReviewAttestationPda: getReviewAttestationPDA,
|
|
925
|
+
prepareIdentityAttestationRequest,
|
|
926
|
+
TRUST_PACKET_SCHEMA_VERSION,
|
|
927
|
+
buildSatpTrustPacket,
|
|
928
|
+
validateSatpTrustPacket,
|
|
929
|
+
|
|
930
|
+
// V3 Deserialization (local extracted scaffold)
|
|
931
|
+
// NOTE: v3sdk.deserializeGenesis has isActive field mismatch with deployed program
|
|
932
|
+
// Using corrected manual parser until SDK v3.6+ fixes struct alignment
|
|
933
|
+
deserializeGenesis: _deserializeGenesisFixed,
|
|
934
|
+
deserializeGenesisRecord: _deserializeGenesisFixed, // alias for old name
|
|
935
|
+
deserializeLinkedWallet: v3Borsh.deserializeLinkedWallet,
|
|
936
|
+
deserializeMintTracker: v3Borsh.deserializeMintTracker,
|
|
937
|
+
deserializeNameRegistry: v3Borsh.deserializeNameRegistry,
|
|
938
|
+
deserializeReview: v3Borsh.deserializeReview,
|
|
939
|
+
deserializeReviewCounter: v3Borsh.deserializeReviewCounter,
|
|
940
|
+
deserializeAttestation: v3Borsh.deserializeAttestation,
|
|
941
|
+
deserializeEscrowV3: v3Borsh.deserializeEscrowV3,
|
|
942
|
+
tryDeserialize: v3Borsh.deserializeAccount,
|
|
943
|
+
|
|
944
|
+
// V3 Utilities
|
|
945
|
+
isBorn: v3sdk.isBorn,
|
|
946
|
+
trustTier: (score) => score >= 400 ? 'Sovereign' : score >= 200 ? 'Trusted' : score >= 100 ? 'Established' : score >= 50 ? 'Verified' : 'Unverified',
|
|
947
|
+
reputationPct: (score) => (Number(score || 0) / 10000).toFixed(2),
|
|
948
|
+
resolveAgent: (agentId, network = 'devnet') => v3pda.getGenesisPDA(agentId, network)[0],
|
|
949
|
+
verificationLabel: (level) => ['Unverified','Registered','Verified','Established','Trusted','Sovereign'][Number(level)] || 'Unknown',
|
|
950
|
+
attestationTypeLabel: (type) => String(type || '').replace(/_/g, ' '),
|
|
951
|
+
escrowStatusLabel: (status) => String(status || '').replace(/_/g, ' '),
|
|
952
|
+
isAttestationValid: (att) => !!att && !att.revoked && (!att.expiresAt || Number(att.expiresAt) > Math.floor(Date.now() / 1000)),
|
|
953
|
+
isEscrowExpired: (escrow) => !!escrow && !!escrow.expiresAt && Number(escrow.expiresAt) <= Math.floor(Date.now() / 1000),
|
|
954
|
+
escrowRemaining: (escrow) => escrow && escrow.expiresAt ? Math.max(0, Number(escrow.expiresAt) - Math.floor(Date.now() / 1000)) : null,
|
|
955
|
+
EscrowStatus: v3sdk.EscrowStatus,
|
|
956
|
+
|
|
957
|
+
// Legacy borsh (V2 compat only — prefer V3 deserializers above)
|
|
958
|
+
BorshReader: borshReader.BorshReader,
|
|
959
|
+
deserializeAccount: borshReader.deserializeAccount,
|
|
960
|
+
deserializeBatch: borshReader.deserializeBatch,
|
|
961
|
+
getAccountDiscriminator: borshReader.getAccountDiscriminator,
|
|
962
|
+
accountDiscriminator: borshReader.accountDiscriminator,
|
|
963
|
+
isAccountType: borshReader.isAccountType,
|
|
964
|
+
DISCRIMINATORS: borshReader.DISCRIMINATORS,
|
|
323
965
|
};
|