@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/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
  };