@brainai/satp-client 2.0.0 → 2.0.2

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,117 @@ 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
+ const {
787
+ DECISIONS,
788
+ DEFAULT_POLICY,
789
+ REASON_CODES,
790
+ evaluateRuntimePolicy,
791
+ } = require('./runtime-policy-adapter');
792
+ const walletControlChallenge = require('./wallet-control-challenge');
793
+ const x402Discovery = require('./x402-discovery');
794
+
795
+ // Legacy V3 SDK wrapper — keeps string constructor compatibility while using
796
+ // the local extracted SATPV3SDK implementation so offline tests and consumers
797
+ // see the expected programIds, rpcUrl, and SDK methods.
798
+ class SATPV3SDK extends v3sdk.SATPV3SDK {
799
+ constructor(opts = {}) {
800
+ super(opts);
801
+ }
802
+ }
803
+
804
+ function createSATPClient(opts = {}) {
805
+ if (typeof opts === 'string') {
806
+ return v3sdk.createSATPClient(opts);
807
+ }
808
+ return v3sdk.createSATPClient({
809
+ rpcUrl: opts.rpcUrl || opts.url || opts.endpoint,
810
+ network: opts.network,
811
+ commitment: opts.commitment,
812
+ });
813
+ }
814
+
815
+ // Legacy borsh reader — keep for any V2 code paths
816
+ const borshReader = require('./borsh-reader');
817
+
818
+
819
+ // Fixed genesis deserializer — matches actual on-chain struct (no isActive field)
820
+ function _deserializeGenesisFixed(data) {
821
+ if (!data || data.length < 8) return null;
822
+ try {
823
+ const { PublicKey } = require('@solana/web3.js');
824
+ let offset = 8; // skip discriminator
825
+ const agentIdHashBytes = data.slice(offset, offset + 32); offset += 32;
826
+ const readString = () => {
827
+ const len = data.readUInt32LE(offset); offset += 4;
828
+ const str = data.slice(offset, offset + len).toString('utf8'); offset += len;
829
+ return str;
830
+ };
831
+ const readVecString = () => {
832
+ const count = data.readUInt32LE(offset); offset += 4;
833
+ const arr = [];
834
+ for (let i = 0; i < count; i++) arr.push(readString());
835
+ return arr;
836
+ };
837
+ const agentName = readString();
838
+ const description = readString();
839
+ const category = readString();
840
+ const capabilities = readVecString();
841
+ const metadataUri = readString();
842
+ const faceImage = readString();
843
+ const faceMint = new PublicKey(data.slice(offset, offset + 32)); offset += 32;
844
+ const faceBurnTx = readString();
845
+ const genesisRecord = Number(data.readBigInt64LE(offset)); offset += 8;
846
+ // NOTE: No isActive field in deployed program (SDK bug — has phantom isActive)
847
+ const authority = new PublicKey(data.slice(offset, offset + 32)); offset += 32;
848
+ const hasPending = data[offset]; offset += 1;
849
+ let pendingAuthority = null;
850
+ if (hasPending === 1) {
851
+ pendingAuthority = new PublicKey(data.slice(offset, offset + 32)).toBase58();
852
+ offset += 32;
853
+ }
854
+ const reputationScore = Number(data.readBigUInt64LE(offset)); offset += 8;
855
+ const verificationLevel = data[offset]; offset += 1;
856
+ const reputationUpdatedAt = Number(data.readBigInt64LE(offset)); offset += 8;
857
+ const verificationUpdatedAt = Number(data.readBigInt64LE(offset)); offset += 8;
858
+ const createdAt = Number(data.readBigInt64LE(offset)); offset += 8;
859
+ const updatedAt = Number(data.readBigInt64LE(offset)); offset += 8;
860
+ const bump = data[offset]; offset += 1;
861
+ return {
862
+ agentIdHash: Array.from(agentIdHashBytes),
863
+ agentName, description, category, capabilities, metadataUri, faceImage,
864
+ faceMint: faceMint.toBase58(),
865
+ faceBurnTx,
866
+ genesisRecord,
867
+ isBorn: genesisRecord > 0,
868
+ authority: authority.toBase58(),
869
+ pendingAuthority,
870
+ reputationScore,
871
+ verificationLevel,
872
+ verificationLabel: ['Unverified','Registered','Verified','Established','Trusted','Sovereign'][verificationLevel] || 'Unknown',
873
+ reputationPct: (reputationScore / 10000).toFixed(2),
874
+ reputationUpdatedAt, verificationUpdatedAt,
875
+ createdAt: createdAt > 0 ? new Date(createdAt * 1000).toISOString() : null,
876
+ updatedAt: updatedAt > 0 ? new Date(updatedAt * 1000).toISOString() : null,
877
+ bump,
878
+ };
879
+ } catch (e) {
880
+ return { error: e.message, raw: data.toString('hex').slice(0, 200) };
881
+ }
882
+ }
883
+
311
884
  module.exports = {
885
+ // V2 SDK (backward compatible — legacy, kept for escrow V2 / old paths)
312
886
  SATPSDK,
313
887
  getProgramIds,
314
888
  getIdentityPDA,
@@ -319,5 +893,103 @@ module.exports = {
319
893
  getReviewsAuthorityPDA,
320
894
  getReviewPDA,
321
895
  getReviewAttestationPDA,
896
+ getEscrowPDA,
897
+ getReviewV3PDA,
322
898
  anchorDiscriminator,
899
+
900
+ // V3 SDK (local extracted scaffold)
901
+ SATPV3SDK,
902
+ createSATPClient,
903
+ SatpV3Client: v3sdk.SATPV3SDK,
904
+ SatpV3Builders: v3sdk,
905
+
906
+ // V3 PDA derivation (local extracted scaffold)
907
+ PROGRAM_IDS: v3pda.getV3ProgramIds('devnet'),
908
+ getV3ProgramIds: v3pda.getV3ProgramIds,
909
+ hashAgentId: v3pda.hashAgentId,
910
+ agentIdHash: v3pda.hashAgentId,
911
+ hashName: v3pda.hashName,
912
+ getGenesisPDA: v3pda.getGenesisPDA,
913
+ deriveGenesisPda: v3pda.getGenesisPDA,
914
+ getV3ReputationAuthorityPDA: v3pda.getV3ReputationAuthorityPDA,
915
+ deriveReputationAuthorityPda: v3pda.getV3ReputationAuthorityPDA,
916
+ getV3ValidationAuthorityPDA: v3pda.getV3ValidationAuthorityPDA,
917
+ deriveValidationAuthorityPda: v3pda.getV3ValidationAuthorityPDA,
918
+ getV3MintTrackerPDA: v3pda.getV3MintTrackerPDA,
919
+ deriveMintTrackerPda: v3pda.getV3MintTrackerPDA,
920
+ getNameRegistryPDA: v3pda.getNameRegistryPDA,
921
+ deriveNameRegistryPda: v3pda.getNameRegistryPDA,
922
+ getLinkedWalletPDA: v3pda.getLinkedWalletPDA,
923
+ deriveLinkedWalletPda: v3pda.getLinkedWalletPDA,
924
+ getV3ReviewPDA: v3pda.getV3ReviewPDA,
925
+ deriveReviewPda: v3pda.getV3ReviewPDA,
926
+ getV3ReviewCounterPDA: v3pda.getV3ReviewCounterPDA,
927
+ deriveReviewCounterPda: v3pda.getV3ReviewCounterPDA,
928
+ getV3AttestationPDA: v3pda.getV3AttestationPDA,
929
+ deriveAttestationPda: v3pda.getV3AttestationPDA,
930
+ getV3EscrowPDA: v3pda.getV3EscrowPDA,
931
+ deriveEscrowPda: v3pda.getV3EscrowPDA,
932
+ deriveReviewAttestationPda: getReviewAttestationPDA,
933
+ prepareIdentityAttestationRequest,
934
+ TRUST_PACKET_SCHEMA_VERSION,
935
+ buildSatpTrustPacket,
936
+ validateSatpTrustPacket,
937
+ WALLET_CONTROL_CHALLENGE_SCHEMA_VERSION: walletControlChallenge.WALLET_CONTROL_CHALLENGE_SCHEMA_VERSION,
938
+ WALLET_CONTROL_CHALLENGE_TYPE: walletControlChallenge.WALLET_CONTROL_CHALLENGE_TYPE,
939
+ DEFAULT_WALLET_CONTROL_DOMAIN: walletControlChallenge.DEFAULT_WALLET_CONTROL_DOMAIN,
940
+ DEFAULT_WALLET_CONTROL_AUDIENCE: walletControlChallenge.DEFAULT_WALLET_CONTROL_AUDIENCE,
941
+ buildWalletControlChallenge: walletControlChallenge.buildWalletControlChallenge,
942
+ canonicalWalletControlChallenge: walletControlChallenge.canonicalWalletControlChallenge,
943
+ hashWalletControlChallenge: walletControlChallenge.hashWalletControlChallenge,
944
+ deriveWalletControlChallengePdas: walletControlChallenge.deriveWalletControlChallengePdas,
945
+ verifyWalletControlChallengeSignature: walletControlChallenge.verifyWalletControlChallengeSignature,
946
+ X402_PAYMENT_IS_NOT_ACTION_AUTHORIZATION: x402Discovery.X402_PAYMENT_IS_NOT_ACTION_AUTHORIZATION,
947
+ X402_DISCOVERY_SCHEMA_VERSION: x402Discovery.X402_DISCOVERY_SCHEMA_VERSION,
948
+ RUNTIME_POLICY_ACTION_DESCRIPTOR_SCHEMA_VERSION: x402Discovery.RUNTIME_POLICY_ACTION_DESCRIPTOR_SCHEMA_VERSION,
949
+ parseX402DiscoveryMetadata: x402Discovery.parseX402DiscoveryMetadata,
950
+ buildX402EvidenceLookup: x402Discovery.buildX402EvidenceLookup,
951
+ buildRuntimePolicyActionDescriptorFromX402Discovery: x402Discovery.buildRuntimePolicyActionDescriptorFromX402Discovery,
952
+ buildRuntimePolicyActionDescriptorFromX402: x402Discovery.buildRuntimePolicyActionDescriptorFromX402,
953
+
954
+ // Runtime policy adapter (offline/local guardrail helper)
955
+ DECISIONS,
956
+ DEFAULT_POLICY,
957
+ REASON_CODES,
958
+ evaluateRuntimePolicy,
959
+
960
+ // V3 Deserialization (local extracted scaffold)
961
+ // NOTE: v3sdk.deserializeGenesis has isActive field mismatch with deployed program
962
+ // Using corrected manual parser until SDK v3.6+ fixes struct alignment
963
+ deserializeGenesis: _deserializeGenesisFixed,
964
+ deserializeGenesisRecord: _deserializeGenesisFixed, // alias for old name
965
+ deserializeLinkedWallet: v3Borsh.deserializeLinkedWallet,
966
+ deserializeMintTracker: v3Borsh.deserializeMintTracker,
967
+ deserializeNameRegistry: v3Borsh.deserializeNameRegistry,
968
+ deserializeReview: v3Borsh.deserializeReview,
969
+ deserializeReviewCounter: v3Borsh.deserializeReviewCounter,
970
+ deserializeAttestation: v3Borsh.deserializeAttestation,
971
+ deserializeEscrowV3: v3Borsh.deserializeEscrowV3,
972
+ tryDeserialize: v3Borsh.deserializeAccount,
973
+
974
+ // V3 Utilities
975
+ isBorn: v3sdk.isBorn,
976
+ trustTier: (score) => score >= 400 ? 'Sovereign' : score >= 200 ? 'Trusted' : score >= 100 ? 'Established' : score >= 50 ? 'Verified' : 'Unverified',
977
+ reputationPct: (score) => (Number(score || 0) / 10000).toFixed(2),
978
+ resolveAgent: (agentId, network = 'devnet') => v3pda.getGenesisPDA(agentId, network)[0],
979
+ verificationLabel: (level) => ['Unverified','Registered','Verified','Established','Trusted','Sovereign'][Number(level)] || 'Unknown',
980
+ attestationTypeLabel: (type) => String(type || '').replace(/_/g, ' '),
981
+ escrowStatusLabel: (status) => String(status || '').replace(/_/g, ' '),
982
+ isAttestationValid: (att) => !!att && !att.revoked && (!att.expiresAt || Number(att.expiresAt) > Math.floor(Date.now() / 1000)),
983
+ isEscrowExpired: (escrow) => !!escrow && !!escrow.expiresAt && Number(escrow.expiresAt) <= Math.floor(Date.now() / 1000),
984
+ escrowRemaining: (escrow) => escrow && escrow.expiresAt ? Math.max(0, Number(escrow.expiresAt) - Math.floor(Date.now() / 1000)) : null,
985
+ EscrowStatus: v3sdk.EscrowStatus,
986
+
987
+ // Legacy borsh (V2 compat only — prefer V3 deserializers above)
988
+ BorshReader: borshReader.BorshReader,
989
+ deserializeAccount: borshReader.deserializeAccount,
990
+ deserializeBatch: borshReader.deserializeBatch,
991
+ getAccountDiscriminator: borshReader.getAccountDiscriminator,
992
+ accountDiscriminator: borshReader.accountDiscriminator,
993
+ isAccountType: borshReader.isAccountType,
994
+ DISCRIMINATORS: borshReader.DISCRIMINATORS,
323
995
  };