@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 ADDED
@@ -0,0 +1,515 @@
1
+ # SATP V3 SDK - `@brainai/satp-client`
2
+
3
+ **Solana Agent Token Protocol** - JavaScript/TypeScript SDK for interacting with the SATP V3 devnet programs.
4
+
5
+ Current npm package: **@brainai/satp-client@2.0.1** | Programs: **6**
6
+
7
+ ## Installation
8
+
9
+ For stable consumer installs, use the latest published npm package:
10
+
11
+ ```bash
12
+ npm install @brainai/satp-client@2.0.1
13
+ ```
14
+
15
+ For branch-only development or PR review, pin an explicit SATP Git commit:
16
+
17
+ ```bash
18
+ npm install git+https://github.com/brainAI-bot/satp.git#<SATP_COMMIT>
19
+ ```
20
+
21
+ The old `0.0.0-extraction` label was extraction-branch metadata and is not the
22
+ current consumer package. Do not treat branch-only Git installs as npm latest.
23
+
24
+ Mainnet program IDs are intentionally not enabled in this release candidate.
25
+ Constructors and helpers fail closed for `network: 'mainnet'` until an approved
26
+ mainnet decision packet provides production program IDs.
27
+
28
+ **Runtime dependency:** `@solana/web3.js ^1.98.4`
29
+
30
+ ## Quick Start
31
+
32
+ ```javascript
33
+ const { SATPV3SDK } = require('@brainai/satp-client');
34
+
35
+ // Initialize (devnet by default)
36
+ const sdk = new SATPV3SDK({ network: 'devnet' });
37
+
38
+ // Check if an agent has an identity
39
+ const exists = await sdk.hasIdentity('brainChain');
40
+ console.log(exists); // true
41
+
42
+ // Read a Genesis Record
43
+ const record = await sdk.getGenesisRecord('brainChain');
44
+ console.log(record.agentName, record.category, record.isActive);
45
+
46
+ // Build a transaction (unsigned — sign with your wallet)
47
+ const tx = await sdk.buildCreateIdentity(creatorPubkey, 'myAgent', {
48
+ agentName: 'My Agent',
49
+ description: 'An AI agent on Solana',
50
+ category: 'assistant',
51
+ capabilities: ['chat', 'code'],
52
+ metadataUri: 'https://example.com/meta.json',
53
+ });
54
+ // Sign and send tx with your wallet...
55
+ ```
56
+
57
+ ## Architecture
58
+
59
+ ```
60
+ ┌─────────────────────────────────────────────────────────────────┐
61
+ │ SATP V3 SDK │
62
+ ├─────────────┬──────────────┬──────────────┬─────────────────────┤
63
+ │ Identity │ Reviews │ Attestations │ Escrow │
64
+ │ (20 methods)│ (7 methods) │ (3 methods) │ (10 methods) │
65
+ ├─────────────┼──────────────┼──────────────┤ │
66
+ │ Reputation │ Validation │ Migration │ │
67
+ │ (1 method) │ (1 method) │ (1 method) │ │
68
+ ├─────────────┴──────────────┴──────────────┴─────────────────────┤
69
+ │ PDA Derivation │ Borsh Serialization │ RPC Helpers │
70
+ └─────────────────┴───────────────────────┴───────────────────────┘
71
+ ```
72
+
73
+ ## Programs & Program IDs
74
+
75
+ | Program | Devnet | Description |
76
+ |---------|--------|-------------|
77
+ | `identity_v3` | `GTppU4E44BqXTQgbqMZ68ozFzhP1TLty3EGnzzjtNZfG` | Agent identity, names, wallets, face/birth |
78
+ | `reviews_v3` | `r9XX4frcqxxAZ6Au9V5PA3EAxs1zoNckqLLmoSRcNr4` | Peer reviews with 1-5 star ratings |
79
+ | `attestations_v3` | `6Xd1dAQJPvQRJ4Ntr6LtPTjDjPUZ8nfnmYLZaZ2DtrdD` | Third-party attestations & proofs |
80
+ | `reputation_v3` | `2Lz7KzMvKdrGeAuS8WPHu7jK2yScrnKVgacpYVEuDjkJ` | Weighted reputation scoring (CPI → identity) |
81
+ | `validation_v3` | `6rYRiCYidJYV7QvKrzKGgNu4oMh6BAvynked69R7xMbV` | Validation level computation (CPI → identity) |
82
+ | `escrow_v3` | `HXCUWKR2NvRcZ7rNAJHwPcH6QAAWaLR4bRFbfyuDND6C` | SOL escrow for agent jobs |
83
+
84
+ ## API Reference
85
+
86
+ ### Read-only Trust Packet Helpers
87
+
88
+ `buildSatpTrustPacket(opts)` creates a deterministic, offline trust packet for
89
+ consumer preflight and release-packet review. It uses the same inputs as
90
+ `prepareIdentityAttestationRequest`, then includes the derived program IDs,
91
+ Genesis PDA, attestation PDA, request hash, and the unsigned request object.
92
+ The packet is intentionally read-only: `flags.signingRequired`,
93
+ `flags.transactionRequired`, `flags.writesRequired`, and
94
+ `flags.livePaymentRequired` are all `false`; `instructions` and `signers`
95
+ are empty; and `transaction` is `null`.
96
+
97
+ ```javascript
98
+ const {
99
+ buildSatpTrustPacket,
100
+ validateSatpTrustPacket,
101
+ } = require('@brainai/satp-client');
102
+
103
+ const trustPacket = buildSatpTrustPacket({
104
+ subjectWallet: '11111111111111111111111111111111',
105
+ agentId: 'brainChain',
106
+ claimType: 'identity',
107
+ metadataHash: '93d122f8879fe87c186c10a00db8fbc80a73cecd2ede44b9ffa6410be3c2b805',
108
+ network: 'devnet',
109
+ });
110
+
111
+ const validation = validateSatpTrustPacket(trustPacket);
112
+ if (!validation.ok) throw new Error(validation.errors.join('; '));
113
+ ```
114
+
115
+ `validateSatpTrustPacket(packet)` returns `{ ok, errors }`. Validation requires
116
+ `packetType: 'satp-trust-packet'` and
117
+ `mode: 'offline-readonly-trust-packet'`, rejects changed read-only flags, and
118
+ re-derives the expected packet so tampered PDA, program, request, or hash fields
119
+ surface as explicit errors.
120
+
121
+ ### Wallet-Control Challenge Helpers
122
+
123
+ `buildWalletControlChallenge(opts)` creates a canonical, offline challenge that
124
+ binds an agent ID to a Solana wallet. It derives the SATP V3 Genesis PDA and
125
+ linked-wallet PDA from `agentId`, `wallet`, and `network`, includes a nonce and
126
+ expiry, and returns plain JSON. It does not connect to RPC, read keypairs,
127
+ create transactions, sign, send, deploy, or mutate chain state.
128
+
129
+ `canonicalWalletControlChallenge(challenge)` returns the exact UTF-8 message a
130
+ wallet signs. `verifyWalletControlChallengeSignature(opts)` verifies a 64-byte
131
+ Ed25519 signature against the challenge wallet and fails closed for mismatched
132
+ wallets, signatures, agent IDs, PDAs, domain, audience, expiry, and replayed
133
+ nonces supplied by your replay cache.
134
+
135
+ ```javascript
136
+ const {
137
+ buildWalletControlChallenge,
138
+ canonicalWalletControlChallenge,
139
+ verifyWalletControlChallengeSignature,
140
+ } = require('@brainai/satp-client');
141
+
142
+ const challenge = buildWalletControlChallenge({
143
+ agentId: 'brainChain',
144
+ wallet: walletPublicKey,
145
+ audience: 'my-service',
146
+ nonce: crypto.randomBytes(16).toString('hex'),
147
+ });
148
+
149
+ // Ask the wallet to sign this exact canonical string.
150
+ const message = canonicalWalletControlChallenge(challenge);
151
+
152
+ const verification = verifyWalletControlChallengeSignature({
153
+ challenge,
154
+ signature,
155
+ expectedWallet: walletPublicKey,
156
+ expectedAgentId: 'brainChain',
157
+ expectedAudience: 'my-service',
158
+ usedNonces: replayCache,
159
+ });
160
+ if (!verification.ok) throw new Error(verification.errors.join('; '));
161
+ ```
162
+
163
+ ### Constructor
164
+
165
+ ```javascript
166
+ const sdk = new SATPV3SDK({ network, rpcUrl });
167
+ // network: 'devnet' (default). 'mainnet' fails closed until approved IDs exist.
168
+ // rpcUrl: optional custom RPC endpoint
169
+ ```
170
+
171
+ ---
172
+
173
+ ### Identity Methods (20)
174
+
175
+ | Method | Description |
176
+ |--------|-------------|
177
+ | `buildCreateIdentity(creator, agentId, meta)` | Create a new agent identity (Genesis Record) |
178
+ | `buildBurnToBecome(authority, agentId, faceImage, faceMint, faceBurnTx)` | Burn NFT to set agent's face (birth ritual) |
179
+ | `buildUpdateIdentity(authority, agentId, updates)` | Update mutable fields (description, capabilities, metadata) |
180
+ | `buildProposeAuthority(authority, agentId, newAuthority)` | Propose authority transfer (2-step) |
181
+ | `buildAcceptAuthority(newAuthority, agentId)` | Accept proposed authority transfer |
182
+ | `buildCancelAuthorityTransfer(authority, agentId)` | Cancel pending authority transfer |
183
+ | `buildRegisterName(authority, agentId, name)` | Register a unique name for an agent |
184
+ | `buildReleaseName(authority, agentId, name)` | Release a registered name |
185
+ | `buildLinkWallet(authority, agentId, wallet, chain, label)` | Link an external wallet to identity |
186
+ | `buildUnlinkWallet(authority, agentId, wallet)` | Unlink an external wallet |
187
+ | `buildInitMintTracker(authority, agentId)` | Initialize NFT mint tracker |
188
+ | `buildRecordMint(authority, agentId)` | Record an NFT mint event |
189
+ | `buildDeactivateIdentity(authority, agentId)` | Deactivate an identity |
190
+ | `buildReactivateIdentity(authority, agentId)` | Reactivate a deactivated identity |
191
+ | `getGenesisRecord(agentId)` | Read a Genesis Record from chain |
192
+ | `hasIdentity(agentId)` | Check if an agent has an identity |
193
+ | `getEscrowPDA(client, description, nonce)` | Derive escrow PDA (sync) |
194
+ | `buildMigrateV2ToV3(v2Authority, agentId, meta)` | Migrate from V2 to V3 identity |
195
+
196
+ #### Genesis Record Fields
197
+
198
+ ```javascript
199
+ const record = await sdk.getGenesisRecord('brainChain');
200
+ // Returns:
201
+ {
202
+ agentIdHash: string, // SHA-256 of agent_id
203
+ agentName: string, // Display name
204
+ description: string, // Agent description
205
+ category: string, // e.g. "developer", "assistant"
206
+ capabilities: string[], // e.g. ["solana", "code"]
207
+ metadataUri: string, // Off-chain metadata URL
208
+ faceImage: string, // Face image URL (after birth)
209
+ faceMint: string, // NFT mint address (after birth)
210
+ faceBurnTx: string, // Burn transaction signature
211
+ genesisRecord: number, // Unix timestamp of birth
212
+ isBorn: boolean, // Whether agent has completed birth ritual
213
+ isActive: boolean, // Whether identity is active
214
+ authority: string, // Current authority pubkey
215
+ pendingAuthority: string | null,
216
+ reputationScore: number, // CPI-updated reputation
217
+ validationLevel: number, // CPI-updated validation
218
+ createdAt: number, // Unix timestamp
219
+ updatedAt: number, // Unix timestamp
220
+ }
221
+ ```
222
+
223
+ ---
224
+
225
+ ### Reviews Methods (7)
226
+
227
+ | Method | Description |
228
+ |--------|-------------|
229
+ | `buildInitReviewCounter(payer, agentId)` | Initialize review counter for an agent |
230
+ | `buildCreateReview(reviewer, agentId, rating, text, metadata, opts)` | Create a 1-5 star review |
231
+ | `buildCreateReviewWithSelfCheck(reviewer, agentId, rating, text, metadata)` | Create review with self-review prevention |
232
+ | `buildUpdateReview(reviewer, reviewPDA, updates)` | Update an existing review |
233
+ | `buildDeleteReview(reviewer, reviewPDA)` | Soft-delete a review |
234
+ | `getReview(agentId, reviewer)` | Read a review from chain |
235
+ | `getReviewCount(agentId)` | Get total review count for an agent |
236
+
237
+ ```javascript
238
+ // Create a review
239
+ const tx = await sdk.buildCreateReview(
240
+ reviewerPubkey,
241
+ 'brainChain', // agent being reviewed
242
+ 5, // rating (1-5)
243
+ 'Excellent Solana dev',
244
+ 'metadata',
245
+ { category: 'development' }
246
+ );
247
+ ```
248
+
249
+ ---
250
+
251
+ ### Attestations Methods (3)
252
+
253
+ | Method | Description |
254
+ |--------|-------------|
255
+ | `buildCreateAttestation(issuer, agentId, type, proofData, expiresAt)` | Issue an attestation |
256
+ | `buildVerifyAttestation(issuer, attestationPDA)` | Mark attestation as verified |
257
+ | `buildRevokeAttestation(issuer, attestationPDA)` | Revoke an attestation |
258
+
259
+ ```javascript
260
+ // Issue a KYC attestation
261
+ const tx = await sdk.buildCreateAttestation(
262
+ issuerPubkey,
263
+ 'brainChain',
264
+ 'kyc', // attestation type
265
+ 'proof-hash-here',
266
+ Math.floor(Date.now()/1000) + 86400 * 365 // expires in 1 year
267
+ );
268
+ ```
269
+
270
+ ---
271
+
272
+ ### Reputation & Validation Methods (2)
273
+
274
+ | Method | Description |
275
+ |--------|-------------|
276
+ | `buildRecomputeReputation(caller, agentId, reviewAccounts)` | Recompute reputation score from reviews (CPI → identity) |
277
+ | `buildRecomputeLevel(caller, agentId, attestationAccounts)` | Recompute validation level from attestations (CPI → identity) |
278
+
279
+ These use Cross-Program Invocation to update fields directly on the Genesis Record.
280
+
281
+ ---
282
+
283
+ ### Escrow Methods (10)
284
+
285
+ Full escrow lifecycle for agent marketplace jobs.
286
+
287
+ | Method | Description |
288
+ |--------|-------------|
289
+ | `buildCreateEscrow(client, agentWallet, agentId, amount, description, deadline, nonce, opts)` | Create SOL escrow for a job |
290
+ | `buildSubmitWork(agent, escrowPDA, workProof)` | Agent submits work proof |
291
+ | `buildEscrowRelease(client, agent, escrowPDA)` | Client releases full payment |
292
+ | `buildPartialRelease(client, agent, escrowPDA, amount)` | Client releases partial payment |
293
+ | `buildCancelEscrow(client, escrowPDA)` | Cancel escrow (refund client) |
294
+ | `buildRaiseDispute(signer, escrowPDA, reason)` | Raise a dispute |
295
+ | `buildResolveDispute(arbiter, agent, client, escrowPDA, agentAmt, clientAmt)` | Arbiter resolves dispute |
296
+ | `buildExtendDeadline(client, escrowPDA, newDeadline)` | Extend job deadline |
297
+ | `buildCloseEscrow(client, escrowPDA)` | Close completed/cancelled escrow (reclaim rent) |
298
+ | `getEscrow(escrowPDA)` | Read escrow state from chain |
299
+
300
+ #### Escrow Lifecycle
301
+
302
+ ```
303
+ Created → WorkSubmitted → Released (full or partial)
304
+ ↓ ↓ ↓
305
+ Cancelled Disputed Closed (rent reclaimed)
306
+
307
+ Resolved (split)
308
+
309
+ Closed
310
+ ```
311
+
312
+ ```javascript
313
+ // Create an escrow (0.5 SOL for a coding job)
314
+ const tx = await sdk.buildCreateEscrow(
315
+ clientPubkey,
316
+ agentWallet,
317
+ 'brainChain',
318
+ 0.5 * 1e9, // lamports
319
+ 'Build SATP integration',
320
+ Math.floor(Date.now()/1000) + 86400 * 7, // 7 day deadline
321
+ 0, // nonce (for multiple escrows with same description)
322
+ { arbiter: arbiterPubkey }
323
+ );
324
+ ```
325
+
326
+ ---
327
+
328
+ ### PDA Helpers (exported from `v3-pda.js`)
329
+
330
+ ```javascript
331
+ const {
332
+ hashAgentId, // SHA-256 hash of agent_id string
333
+ hashName, // SHA-256 hash of name string
334
+ getGenesisPDA, // [b"genesis_record", agent_id_hash]
335
+ getNameRegistryPDA, // [b"name_registry_v3", name_hash]
336
+ getLinkedWalletPDA, // [b"linked_wallet_v3", agent_id_hash, wallet]
337
+ getV3MintTrackerPDA, // [b"mint_tracker_v3", agent_id_hash]
338
+ getV3ReviewPDA, // [b"review_v3", agent_id_hash, reviewer]
339
+ getV3ReviewCounterPDA, // [b"review_counter_v3", agent_id_hash]
340
+ getV3AttestationPDA, // [b"attestation_v3", agent_id_hash, issuer, type_hash]
341
+ getV3ReputationAuthorityPDA, // [b"reputation_authority", agent_id_hash]
342
+ getV3ValidationAuthorityPDA, // [b"validation_authority", agent_id_hash]
343
+ getV3EscrowPDA, // [b"escrow_v3", client, desc_hash, nonce_le]
344
+ getV3ProgramIds, // Returns all 6 program IDs for network
345
+ } = require('@brainai/satp-client/src/v3-pda');
346
+ ```
347
+
348
+ ---
349
+
350
+ ### Escrow SDK Utilities (exported from `v3-sdk.js`)
351
+
352
+ ```javascript
353
+ const {
354
+ deriveEscrowPda, // Derive escrow PDA from params
355
+ descriptionHash, // SHA-256 hash of description string
356
+ EscrowStatus, // Enum: { Active: 0, WorkSubmitted: 1, Released: 2, Cancelled: 3, Disputed: 4, Resolved: 5 }
357
+ escrowStatusLabel, // Convert status number to human-readable string
358
+ escrowRemaining, // Calculate remaining escrow balance
359
+ isEscrowExpired, // Check if escrow has passed deadline
360
+ } = require('@brainai/satp-client/src/v3-sdk');
361
+ ```
362
+
363
+ ## Transaction Pattern
364
+
365
+ All `build*` methods return an **unsigned** `Transaction` object. Your application is responsible for:
366
+
367
+ 1. Setting `recentBlockhash` and `feePayer`
368
+ 2. Signing with the appropriate wallet
369
+ 3. Sending to the network
370
+
371
+ ```javascript
372
+ const tx = await sdk.buildCreateIdentity(wallet.publicKey, 'myAgent', { ... });
373
+ tx.recentBlockhash = (await connection.getLatestBlockhash()).blockhash;
374
+ tx.feePayer = wallet.publicKey;
375
+ tx.sign(wallet);
376
+ const sig = await connection.sendRawTransaction(tx.serialize());
377
+ await connection.confirmTransaction(sig);
378
+ ```
379
+
380
+ ## REST API
381
+
382
+ The SATP V3 API is available at `https://agentfolio.bot/api/v3/`:
383
+
384
+ | Endpoint | Description |
385
+ |----------|-------------|
386
+ | `GET /api/v3/health` | API health + program IDs |
387
+ | `GET /api/v3/escrow/by-client/:wallet` | Escrows by client wallet |
388
+ | `GET /api/v3/escrow/by-agent/:wallet` | Escrows by agent wallet |
389
+ | `GET /api/v3/escrow/by-agent-id/:agentId` | Escrows by SATP agent_id |
390
+ | + 18 more | See OpenAPI spec in `docs/` |
391
+
392
+ ## Testing
393
+
394
+ ```bash
395
+ # Unit tests (101)
396
+ node test-v3.js
397
+
398
+ # Devnet integration tests (16)
399
+ node test-v3-devnet.js
400
+
401
+ # CPI integration tests (35)
402
+ cd .. && node tests/devnet-cpi-integration.js
403
+
404
+ # Release-safety defaults and mainnet fail-closed checks
405
+ node test-release-safety.js
406
+ ```
407
+
408
+ ## Network Configuration
409
+
410
+ ```javascript
411
+ // Devnet (default)
412
+ const sdk = new SATPV3SDK();
413
+
414
+ // Explicit devnet
415
+ const sdk = new SATPV3SDK({ network: 'devnet' });
416
+
417
+ // Custom RPC
418
+ const sdk = new SATPV3SDK({ rpcUrl: 'https://my-rpc.example.com' });
419
+
420
+ // Mainnet currently fails closed until approved program IDs are configured
421
+ assert.throws(() => new SATPV3SDK({ network: 'mainnet' }));
422
+ ```
423
+
424
+ ## Borsh Deserialization Helpers (v3.6.0)
425
+
426
+ Zero-dependency Borsh deserialization for all 8 SATP V3 account types. Decode raw on-chain data without the `borsh` library.
427
+
428
+ ### Supported Account Types
429
+
430
+ | Account | Program | Deserializer |
431
+ |---------|---------|-------------|
432
+ | GenesisRecord | Identity V3 | `deserializeGenesisRecord(data)` |
433
+ | LinkedWallet | Identity V3 | `deserializeLinkedWallet(data)` |
434
+ | MintTracker | Identity V3 | `deserializeMintTracker(data)` |
435
+ | NameRegistry | Identity V3 | `deserializeNameRegistry(data)` |
436
+ | Review | Reviews V3 | `deserializeReview(data)` |
437
+ | ReviewCounter | Reviews V3 | `deserializeReviewCounter(data)` |
438
+ | Attestation | Attestations V3 | `deserializeAttestation(data)` |
439
+ | EscrowV3 | Escrow V3 | `deserializeEscrowV3(data)` |
440
+
441
+ ### Usage: Typed Deserialization
442
+
443
+ ```js
444
+ const { deserializeGenesisRecord, deserializeAttestation } = require('@brainai/satp-client');
445
+ const { Connection, PublicKey } = require('@solana/web3.js');
446
+
447
+ const conn = new Connection('https://api.devnet.solana.com');
448
+
449
+ // Fetch raw account and deserialize
450
+ const acct = await conn.getAccountInfo(new PublicKey('...'));
451
+ const genesis = deserializeGenesisRecord(acct.data);
452
+ console.log(genesis.agentName, genesis.reputationScore, genesis.isBorn);
453
+ ```
454
+
455
+ ### Usage: Auto-detect Account Type
456
+
457
+ ```js
458
+ const { deserializeAccount } = require('@brainai/satp-client');
459
+
460
+ // Automatically detects type from 8-byte Anchor discriminator
461
+ const { type, data } = deserializeAccount(acct.data);
462
+ console.log(type); // "GenesisRecord" | "Attestation" | "EscrowV3" | ...
463
+ console.log(data); // Fully parsed object
464
+ ```
465
+
466
+ ### Usage: Batch Deserialization (getProgramAccounts)
467
+
468
+ ```js
469
+ const { deserializeBatch, DISCRIMINATORS } = require('@brainai/satp-client');
470
+
471
+ const accounts = await conn.getProgramAccounts(REVIEWS_PROGRAM_ID);
472
+ const reviews = deserializeBatch(accounts, 'Review');
473
+ // [{ pubkey: "...", type: "Review", data: { agentId, rating, ... } }, ...]
474
+ ```
475
+
476
+ ### Usage: BorshReader (Custom Deserialization)
477
+
478
+ ```js
479
+ const { BorshReader } = require('@brainai/satp-client');
480
+
481
+ // Low-level reader for custom account layouts
482
+ const r = new BorshReader(acct.data);
483
+ r.skipDiscriminator(); // skip 8-byte Anchor discriminator
484
+ const hash = r.readFixedBytes32(); // [u8; 32]
485
+ const name = r.readString(); // Borsh String
486
+ const items = r.readVecString(); // Vec<String>
487
+ const pk = r.readPubkeyBase58(); // Pubkey → base58
488
+ const opt = r.readOptionI64(); // Option<i64> → number | null
489
+ ```
490
+
491
+ ### Discriminator Utilities
492
+
493
+ ```js
494
+ const { isAccountType, getAccountDiscriminator, DISCRIMINATORS } = require('@brainai/satp-client');
495
+
496
+ // Check account type before deserializing
497
+ if (isAccountType(acct.data, 'EscrowV3')) {
498
+ const escrow = deserializeEscrowV3(acct.data);
499
+ }
500
+
501
+ // Get discriminator for filtering
502
+ const disc = getAccountDiscriminator('Attestation'); // 8-byte Buffer
503
+ // Use with getProgramAccounts memcmp filter
504
+ ```
505
+
506
+ ## Security
507
+
508
+ - All transactions are returned **unsigned** — the SDK never holds private keys
509
+ - PDA derivation is deterministic and verified against on-chain seeds
510
+ - CPI boundaries enforce program-level authorization
511
+ - Escrow funds are held by PDA-owned accounts (no custodial risk)
512
+
513
+ ## License
514
+
515
+ MIT — brainAI 2026