@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.
@@ -0,0 +1,587 @@
1
+ /**
2
+ * BorshReader — Zero-dependency Borsh deserialization for SATP V3 accounts.
3
+ *
4
+ * Decodes raw on-chain account data (Buffer) into typed JavaScript objects.
5
+ * Handles the 8-byte Anchor discriminator automatically.
6
+ *
7
+ * Supports all 8 V3 account types:
8
+ * - GenesisRecord (Identity V3)
9
+ * - LinkedWallet (Identity V3)
10
+ * - MintTracker (Identity V3)
11
+ * - NameRegistry (Identity V3)
12
+ * - Review (Reviews V3)
13
+ * - ReviewCounter (Reviews V3)
14
+ * - Attestation (Attestations V3)
15
+ * - EscrowV3 (Escrow V3)
16
+ *
17
+ * @module borsh-reader
18
+ */
19
+
20
+ const { PublicKey } = require('@solana/web3.js');
21
+
22
+ // ═══════════════════════════════════════════════════
23
+ // BorshReader — streaming Borsh deserializer
24
+ // ═══════════════════════════════════════════════════
25
+
26
+ class BorshReader {
27
+ /**
28
+ * @param {Buffer} buf - Raw account data buffer
29
+ * @param {number} [offset=0] - Starting offset
30
+ */
31
+ constructor(buf, offset = 0) {
32
+ this.buf = buf;
33
+ this.offset = offset;
34
+ }
35
+
36
+ /** Read n raw bytes, advance offset. */
37
+ readBytes(n) {
38
+ const slice = this.buf.slice(this.offset, this.offset + n);
39
+ this.offset += n;
40
+ return slice;
41
+ }
42
+
43
+ /** Read u8 */
44
+ readU8() {
45
+ const val = this.buf[this.offset];
46
+ this.offset += 1;
47
+ return val;
48
+ }
49
+
50
+ /** Read u16 LE */
51
+ readU16() {
52
+ const val = this.buf.readUInt16LE(this.offset);
53
+ this.offset += 2;
54
+ return val;
55
+ }
56
+
57
+ /** Read u32 LE */
58
+ readU32() {
59
+ const val = this.buf.readUInt32LE(this.offset);
60
+ this.offset += 4;
61
+ return val;
62
+ }
63
+
64
+ /** Read u64 LE → number (safe for values < 2^53) */
65
+ readU64() {
66
+ return Number(this.buf.readBigUInt64LE(this.offset));
67
+ // Note: offset advanced after return won't work — fix:
68
+ }
69
+
70
+ /** Read u64 LE → number, advances offset */
71
+ readU64Num() {
72
+ const val = Number(this.buf.readBigUInt64LE(this.offset));
73
+ this.offset += 8;
74
+ return val;
75
+ }
76
+
77
+ /** Read u64 LE → BigInt */
78
+ readU64BigInt() {
79
+ const val = this.buf.readBigUInt64LE(this.offset);
80
+ this.offset += 8;
81
+ return val;
82
+ }
83
+
84
+ /** Read i64 LE → number */
85
+ readI64() {
86
+ const val = Number(this.buf.readBigInt64LE(this.offset));
87
+ this.offset += 8;
88
+ return val;
89
+ }
90
+
91
+ /** Read i64 LE → BigInt */
92
+ readI64BigInt() {
93
+ const val = this.buf.readBigInt64LE(this.offset);
94
+ this.offset += 8;
95
+ return val;
96
+ }
97
+
98
+ /** Read bool (1 byte, 0x01 = true) */
99
+ readBool() {
100
+ const val = this.buf[this.offset] === 1;
101
+ this.offset += 1;
102
+ return val;
103
+ }
104
+
105
+ /** Read [u8; 32] as Buffer */
106
+ readFixedBytes32() {
107
+ return Buffer.from(this.readBytes(32));
108
+ }
109
+
110
+ /** Read Pubkey (32 bytes) → PublicKey */
111
+ readPubkey() {
112
+ return new PublicKey(this.readBytes(32));
113
+ }
114
+
115
+ /** Read Pubkey (32 bytes) → base58 string */
116
+ readPubkeyBase58() {
117
+ return new PublicKey(this.readBytes(32)).toBase58();
118
+ }
119
+
120
+ /** Read Borsh String (4-byte LE length + UTF-8 bytes) */
121
+ readString() {
122
+ const len = this.readU32();
123
+ const str = this.buf.slice(this.offset, this.offset + len).toString('utf8');
124
+ this.offset += len;
125
+ return str;
126
+ }
127
+
128
+ /** Read Vec<String> (4-byte LE count, then count × String) */
129
+ readVecString() {
130
+ const count = this.readU32();
131
+ const arr = [];
132
+ for (let i = 0; i < count; i++) {
133
+ arr.push(this.readString());
134
+ }
135
+ return arr;
136
+ }
137
+
138
+ /** Read Option<T> — returns null for None, or calls reader fn for Some */
139
+ readOption(readerFn) {
140
+ const tag = this.readU8();
141
+ if (tag === 0) return null;
142
+ return readerFn.call(this);
143
+ }
144
+
145
+ /** Read Option<Pubkey> → base58 string | null */
146
+ readOptionPubkey() {
147
+ return this.readOption(function () {
148
+ return this.readPubkeyBase58();
149
+ });
150
+ }
151
+
152
+ /** Read Option<i64> → number | null */
153
+ readOptionI64() {
154
+ return this.readOption(function () {
155
+ return this.readI64();
156
+ });
157
+ }
158
+
159
+ /** Read Option<[u8; 32]> → hex string | null */
160
+ readOptionBytes32Hex() {
161
+ return this.readOption(function () {
162
+ return this.readFixedBytes32().toString('hex');
163
+ });
164
+ }
165
+
166
+ /** Skip 8-byte Anchor discriminator */
167
+ skipDiscriminator() {
168
+ this.offset += 8;
169
+ return this;
170
+ }
171
+
172
+ /** Get remaining unread bytes */
173
+ remaining() {
174
+ return this.buf.length - this.offset;
175
+ }
176
+ }
177
+
178
+ // ═══════════════════════════════════════════════════
179
+ // Account Deserializers
180
+ // ═══════════════════════════════════════════════════
181
+
182
+ /**
183
+ * Deserialize a GenesisRecord from raw account data.
184
+ * @param {Buffer} data - Raw account data (with 8-byte discriminator)
185
+ * @returns {object} Parsed GenesisRecord
186
+ */
187
+ function deserializeGenesisRecord(data) {
188
+ const r = new BorshReader(data).skipDiscriminator();
189
+
190
+ const agentIdHash = r.readFixedBytes32();
191
+ const agentName = r.readString();
192
+ const description = r.readString();
193
+ const category = r.readString();
194
+ const capabilities = r.readVecString();
195
+ const metadataUri = r.readString();
196
+ const faceImage = r.readString();
197
+ const faceMint = r.readPubkey();
198
+ const faceBurnTx = r.readString();
199
+ const genesisRecord = r.readI64();
200
+ const isActive = r.readBool();
201
+ const authority = r.readPubkeyBase58();
202
+ const pendingAuthority = r.readOptionPubkey();
203
+ const reputationScore = r.readU64Num();
204
+ const verificationLevel = r.readU8();
205
+ const reputationUpdatedAt = r.readI64();
206
+ const verificationUpdatedAt = r.readI64();
207
+ const createdAt = r.readI64();
208
+ const updatedAt = r.readI64();
209
+ const bump = r.readU8();
210
+
211
+ return {
212
+ agentIdHash: agentIdHash.toString('hex'),
213
+ agentName,
214
+ description,
215
+ category,
216
+ capabilities,
217
+ metadataUri,
218
+ faceImage: faceImage || null,
219
+ faceMint: faceMint.equals(PublicKey.default) ? null : faceMint.toBase58(),
220
+ faceBurnTx: faceBurnTx || null,
221
+ genesisRecord,
222
+ isBorn: genesisRecord !== 0,
223
+ isActive,
224
+ authority,
225
+ pendingAuthority,
226
+ reputationScore,
227
+ verificationLevel,
228
+ reputationUpdatedAt,
229
+ verificationUpdatedAt,
230
+ createdAt,
231
+ updatedAt,
232
+ bump,
233
+ };
234
+ }
235
+
236
+ /**
237
+ * Deserialize a LinkedWallet from raw account data.
238
+ * @param {Buffer} data - Raw account data (with 8-byte discriminator)
239
+ * @returns {object} Parsed LinkedWallet
240
+ */
241
+ function deserializeLinkedWallet(data) {
242
+ const r = new BorshReader(data).skipDiscriminator();
243
+
244
+ return {
245
+ identity: r.readPubkeyBase58(),
246
+ wallet: r.readPubkeyBase58(),
247
+ chain: r.readString(),
248
+ label: r.readString(),
249
+ verifiedAt: r.readI64(),
250
+ isActive: r.readBool(),
251
+ bump: r.readU8(),
252
+ };
253
+ }
254
+
255
+ /**
256
+ * Deserialize a MintTracker from raw account data.
257
+ * @param {Buffer} data - Raw account data (with 8-byte discriminator)
258
+ * @returns {object} Parsed MintTracker
259
+ */
260
+ function deserializeMintTracker(data) {
261
+ const r = new BorshReader(data).skipDiscriminator();
262
+
263
+ return {
264
+ identity: r.readPubkeyBase58(),
265
+ mintCount: r.readU8(),
266
+ lastMintTimestamp: r.readI64(),
267
+ bump: r.readU8(),
268
+ };
269
+ }
270
+
271
+ /**
272
+ * Deserialize a NameRegistry from raw account data.
273
+ * @param {Buffer} data - Raw account data (with 8-byte discriminator)
274
+ * @returns {object} Parsed NameRegistry
275
+ */
276
+ function deserializeNameRegistry(data) {
277
+ const r = new BorshReader(data).skipDiscriminator();
278
+
279
+ return {
280
+ name: r.readString(),
281
+ nameHash: r.readFixedBytes32().toString('hex'),
282
+ identity: r.readPubkeyBase58(),
283
+ authority: r.readPubkeyBase58(),
284
+ registeredAt: r.readI64(),
285
+ isActive: r.readBool(),
286
+ bump: r.readU8(),
287
+ };
288
+ }
289
+
290
+ /**
291
+ * Deserialize a Review from raw account data.
292
+ * @param {Buffer} data - Raw account data (with 8-byte discriminator)
293
+ * @returns {object} Parsed Review
294
+ */
295
+ function deserializeReview(data) {
296
+ const r = new BorshReader(data).skipDiscriminator();
297
+
298
+ return {
299
+ agentId: r.readString(),
300
+ agentIdHash: r.readFixedBytes32().toString('hex'),
301
+ reviewer: r.readPubkeyBase58(),
302
+ rating: r.readU8(),
303
+ reviewText: r.readString(),
304
+ metadata: r.readString(),
305
+ createdAt: r.readI64(),
306
+ updatedAt: r.readI64(),
307
+ isActive: r.readBool(),
308
+ bump: r.readU8(),
309
+ };
310
+ }
311
+
312
+ /**
313
+ * Deserialize a ReviewCounter from raw account data.
314
+ * @param {Buffer} data - Raw account data (with 8-byte discriminator)
315
+ * @returns {object} Parsed ReviewCounter
316
+ */
317
+ function deserializeReviewCounter(data) {
318
+ const r = new BorshReader(data).skipDiscriminator();
319
+
320
+ return {
321
+ agentId: r.readString(),
322
+ agentIdHash: r.readFixedBytes32().toString('hex'),
323
+ count: r.readU64Num(),
324
+ bump: r.readU8(),
325
+ };
326
+ }
327
+
328
+ /**
329
+ * Deserialize an Attestation from raw account data.
330
+ * @param {Buffer} data - Raw account data (with 8-byte discriminator)
331
+ * @returns {object} Parsed Attestation
332
+ */
333
+ function deserializeAttestation(data) {
334
+ const r = new BorshReader(data).skipDiscriminator();
335
+
336
+ const agentId = r.readString();
337
+ const agentIdHash = r.readFixedBytes32().toString('hex');
338
+ const attestationType = r.readString();
339
+ const issuer = r.readPubkeyBase58();
340
+ const proofData = r.readString();
341
+ const verified = r.readBool();
342
+ const createdAt = r.readI64();
343
+ const expiresAt = r.readOptionI64();
344
+ const isRevoked = r.readBool();
345
+ const bump = r.readU8();
346
+
347
+ // Compute validity: not revoked, verified, and not expired
348
+ const now = Math.floor(Date.now() / 1000);
349
+ const isExpired = expiresAt !== null && expiresAt < now;
350
+ const isValid = verified && !isRevoked && !isExpired;
351
+
352
+ return {
353
+ agentId,
354
+ agentIdHash,
355
+ attestationType,
356
+ issuer,
357
+ proofData,
358
+ verified,
359
+ createdAt,
360
+ expiresAt,
361
+ isRevoked,
362
+ isExpired,
363
+ isValid,
364
+ bump,
365
+ };
366
+ }
367
+
368
+ /**
369
+ * Deserialize an EscrowV3 from raw account data.
370
+ * @param {Buffer} data - Raw account data (with 8-byte discriminator)
371
+ * @returns {object} Parsed EscrowV3
372
+ */
373
+ function deserializeEscrowV3(data) {
374
+ const r = new BorshReader(data).skipDiscriminator();
375
+
376
+ const ESCROW_STATUS_MAP = [
377
+ 'Active', 'WorkSubmitted', 'Released',
378
+ 'Cancelled', 'Disputed', 'Resolved',
379
+ ];
380
+
381
+ const client = r.readPubkeyBase58();
382
+ const agent = r.readPubkeyBase58();
383
+ const agentIdHash = r.readFixedBytes32().toString('hex');
384
+ const amount = r.readU64Num();
385
+ const releasedAmount = r.readU64Num();
386
+ const descriptionHash = r.readFixedBytes32().toString('hex');
387
+ const deadline = r.readI64();
388
+ const nonce = r.readU64Num();
389
+ const statusByte = r.readU8();
390
+ const minVerificationLevel = r.readU8();
391
+ const requireBorn = r.readBool();
392
+ const createdAt = r.readI64();
393
+ const arbiter = r.readPubkeyBase58();
394
+ const workHash = r.readOptionBytes32Hex();
395
+ const workSubmittedAt = r.readOptionI64();
396
+ const disputeReasonHash = r.readOptionBytes32Hex();
397
+ const disputedAt = r.readOptionI64();
398
+ const disputedBy = r.readOptionPubkey();
399
+ const bump = r.readU8();
400
+
401
+ return {
402
+ client,
403
+ agent,
404
+ agentIdHash,
405
+ amount,
406
+ releasedAmount,
407
+ remaining: amount - releasedAmount,
408
+ descriptionHash,
409
+ deadline,
410
+ nonce,
411
+ status: ESCROW_STATUS_MAP[statusByte] || `Unknown(${statusByte})`,
412
+ statusCode: statusByte,
413
+ minVerificationLevel,
414
+ requireBorn,
415
+ createdAt,
416
+ arbiter,
417
+ workHash,
418
+ workSubmittedAt,
419
+ disputeReasonHash,
420
+ disputedAt,
421
+ disputedBy,
422
+ bump,
423
+ };
424
+ }
425
+
426
+ // ═══════════════════════════════════════════════════
427
+ // Auto-detect Account Type
428
+ // ═══════════════════════════════════════════════════
429
+
430
+ const crypto = require('crypto');
431
+
432
+ /** Compute Anchor account discriminator: SHA256("account:<AccountName>")[0..8] */
433
+ function accountDiscriminator(accountName) {
434
+ return crypto.createHash('sha256')
435
+ .update(`account:${accountName}`)
436
+ .digest()
437
+ .slice(0, 8);
438
+ }
439
+
440
+ // Pre-compute discriminators for all V3 account types
441
+ const DISCRIMINATORS = {
442
+ GenesisRecord: accountDiscriminator('GenesisRecord'),
443
+ LinkedWallet: accountDiscriminator('LinkedWallet'),
444
+ MintTracker: accountDiscriminator('MintTracker'),
445
+ NameRegistry: accountDiscriminator('NameRegistry'),
446
+ Review: accountDiscriminator('Review'),
447
+ ReviewCounter: accountDiscriminator('ReviewCounter'),
448
+ Attestation: accountDiscriminator('Attestation'),
449
+ EscrowV3: accountDiscriminator('EscrowV3'),
450
+ };
451
+
452
+ const DESERIALIZERS = {
453
+ GenesisRecord: deserializeGenesisRecord,
454
+ LinkedWallet: deserializeLinkedWallet,
455
+ MintTracker: deserializeMintTracker,
456
+ NameRegistry: deserializeNameRegistry,
457
+ Review: deserializeReview,
458
+ ReviewCounter: deserializeReviewCounter,
459
+ Attestation: deserializeAttestation,
460
+ EscrowV3: deserializeEscrowV3,
461
+ };
462
+
463
+ /**
464
+ * Auto-detect and deserialize any SATP V3 account from raw data.
465
+ * Matches the 8-byte Anchor discriminator to determine account type.
466
+ *
467
+ * @param {Buffer} data - Raw account data (must include 8-byte discriminator)
468
+ * @returns {{ type: string, data: object }} Account type name + parsed data
469
+ * @throws {Error} If discriminator doesn't match any known type
470
+ */
471
+ function deserializeAccount(data) {
472
+ if (!Buffer.isBuffer(data) || data.length < 8) {
473
+ throw new Error('Invalid account data: must be a Buffer with at least 8 bytes');
474
+ }
475
+
476
+ const disc = data.slice(0, 8);
477
+
478
+ for (const [name, expected] of Object.entries(DISCRIMINATORS)) {
479
+ if (disc.equals(expected)) {
480
+ return {
481
+ type: name,
482
+ data: DESERIALIZERS[name](data),
483
+ };
484
+ }
485
+ }
486
+
487
+ throw new Error(
488
+ `Unknown account discriminator: ${disc.toString('hex')}. ` +
489
+ `Known types: ${Object.keys(DISCRIMINATORS).join(', ')}`
490
+ );
491
+ }
492
+
493
+ /**
494
+ * Get the Anchor discriminator for a known account type.
495
+ * @param {string} accountName - e.g. 'GenesisRecord', 'EscrowV3'
496
+ * @returns {Buffer} 8-byte discriminator
497
+ */
498
+ function getAccountDiscriminator(accountName) {
499
+ if (DISCRIMINATORS[accountName]) {
500
+ return Buffer.from(DISCRIMINATORS[accountName]);
501
+ }
502
+ return accountDiscriminator(accountName);
503
+ }
504
+
505
+ /**
506
+ * Check if raw data matches a specific account type.
507
+ * @param {Buffer} data - Raw account data
508
+ * @param {string} accountName - Expected account type name
509
+ * @returns {boolean}
510
+ */
511
+ function isAccountType(data, accountName) {
512
+ if (!Buffer.isBuffer(data) || data.length < 8) return false;
513
+ const expected = DISCRIMINATORS[accountName];
514
+ if (!expected) return false;
515
+ return data.slice(0, 8).equals(expected);
516
+ }
517
+
518
+ // ═══════════════════════════════════════════════════
519
+ // Batch Deserializer (for getProgramAccounts results)
520
+ // ═══════════════════════════════════════════════════
521
+
522
+ /**
523
+ * Deserialize multiple accounts from getProgramAccounts result.
524
+ * Skips accounts that fail to deserialize (logs warning).
525
+ *
526
+ * @param {{ pubkey: PublicKey, account: { data: Buffer } }[]} accounts - getProgramAccounts result
527
+ * @param {string} [expectedType] - If provided, only deserialize this type (faster, no auto-detect)
528
+ * @returns {{ pubkey: string, type: string, data: object }[]}
529
+ */
530
+ function deserializeBatch(accounts, expectedType) {
531
+ const results = [];
532
+
533
+ for (const { pubkey, account } of accounts) {
534
+ try {
535
+ let type, parsed;
536
+
537
+ if (expectedType && DESERIALIZERS[expectedType]) {
538
+ // Direct deserialization — skip discriminator check for speed
539
+ type = expectedType;
540
+ parsed = DESERIALIZERS[expectedType](account.data);
541
+ } else {
542
+ // Auto-detect
543
+ const result = deserializeAccount(account.data);
544
+ type = result.type;
545
+ parsed = result.data;
546
+ }
547
+
548
+ results.push({
549
+ pubkey: pubkey.toBase58(),
550
+ type,
551
+ data: parsed,
552
+ });
553
+ } catch (e) {
554
+ // Skip malformed accounts
555
+ if (process.env.SATP_DEBUG) {
556
+ console.warn(`[BorshReader] Failed to deserialize ${pubkey.toBase58()}: ${e.message}`);
557
+ }
558
+ }
559
+ }
560
+
561
+ return results;
562
+ }
563
+
564
+ module.exports = {
565
+ // Core reader
566
+ BorshReader,
567
+
568
+ // Individual deserializers
569
+ deserializeGenesisRecord,
570
+ deserializeLinkedWallet,
571
+ deserializeMintTracker,
572
+ deserializeNameRegistry,
573
+ deserializeReview,
574
+ deserializeReviewCounter,
575
+ deserializeAttestation,
576
+ deserializeEscrowV3,
577
+
578
+ // Auto-detect
579
+ deserializeAccount,
580
+ deserializeBatch,
581
+
582
+ // Discriminator utilities
583
+ getAccountDiscriminator,
584
+ accountDiscriminator,
585
+ isAccountType,
586
+ DISCRIMINATORS,
587
+ };
@@ -0,0 +1,77 @@
1
+ const { PublicKey } = require('@solana/web3.js');
2
+
3
+ // SATP v2 Program IDs — Devnet
4
+ const DEVNET_PROGRAM_IDS = {
5
+ IDENTITY: new PublicKey('EJtQh4Gyg88zXvSmFpxYkkeZsPwTsjfm4LvjmPQX1FD3'),
6
+ REVIEWS: new PublicKey('D8HsSpK3JtAN7tVcA1yfgxScju7KcG6skEfaShSKojki'),
7
+ REPUTATION: new PublicKey('4y4W2Mdfpu91C4iVowiDyJTmdKSjo8bmSDQrX2c84WQF'),
8
+ ATTESTATIONS: new PublicKey('9xT3eNcndkmnqZtJqDQ1ggckHK7Dxo5EsAt5mHqsPBhP'),
9
+ VALIDATION: new PublicKey('8jLaqodAzfM7oCxP7aedFeszeNjnJ5ik56dzhDU2HQgc'),
10
+ ESCROW: new PublicKey('UpJ7jmUzHkQ7EdBKiBv3zq8Dr1fVh6GVWKa7nYtwQ22'),
11
+ };
12
+
13
+ // SATP v2 Program IDs — Mainnet
14
+ const MAINNET_PROGRAM_IDS = {
15
+ IDENTITY: new PublicKey('97yL33fcu6iWT2TdERS5HeqrMSGiUnxuy6nUcTrKieSq'),
16
+ REVIEWS: new PublicKey('Ge1sD2qwmH8QaaKCPZzZERvsFXNVMvKbAgTp2p17yjLK'),
17
+ REPUTATION: new PublicKey('C9ogv8TBrvFy4pLKDoGQg9B73Q5rKPPsQ4kzkcDk6Jd'),
18
+ ATTESTATIONS: new PublicKey('ENvaD19QzwWWMJFu5r5xJ9SmHqWN6GvyzxACRejqbdug'),
19
+ VALIDATION: new PublicKey('9p795d2j3eGqzborG2AncucWBaU6PieKxmhKVroV3LNh'),
20
+ ESCROW: null,
21
+ };
22
+
23
+ const MAINNET_RPC = 'https://api.mainnet-beta.solana.com';
24
+ const DEVNET_RPC = 'https://api.devnet.solana.com';
25
+
26
+ // PDA seeds (must match on-chain programs)
27
+ const IDENTITY_SEED = 'identity';
28
+ const REPUTATION_AUTHORITY_SEED = 'reputation_authority';
29
+ const VALIDATION_AUTHORITY_SEED = 'validation_authority';
30
+ const REVIEW_COUNTER_SEED = 'review_counter';
31
+ const MINT_TRACKER_SEED = 'mint_tracker';
32
+ const REVIEWS_AUTHORITY_SEED = 'reviews_authority';
33
+ const ATTESTATION_SEED = 'attestation';
34
+ const REVIEW_SEED = 'review';
35
+ const ESCROW_SEED = 'escrow';
36
+
37
+ /**
38
+ * Get program IDs for a given network.
39
+ * @param {'mainnet'|'devnet'} network
40
+ * @returns {object} Program ID map
41
+ */
42
+ function getProgramIds(network = 'devnet') {
43
+ if (network !== 'devnet' && network !== 'mainnet') {
44
+ throw new Error('Invalid network: expected devnet or mainnet');
45
+ }
46
+ return network === 'mainnet' ? MAINNET_PROGRAM_IDS : DEVNET_PROGRAM_IDS;
47
+ }
48
+
49
+ /**
50
+ * Get RPC URL for a given network.
51
+ * @param {'mainnet'|'devnet'} network
52
+ * @returns {string} RPC URL
53
+ */
54
+ function getRpcUrl(network = 'devnet') {
55
+ if (network !== 'devnet' && network !== 'mainnet') {
56
+ throw new Error('Invalid network: expected devnet or mainnet');
57
+ }
58
+ return network === 'mainnet' ? MAINNET_RPC : DEVNET_RPC;
59
+ }
60
+
61
+ module.exports = {
62
+ DEVNET_PROGRAM_IDS,
63
+ MAINNET_PROGRAM_IDS,
64
+ getProgramIds,
65
+ getRpcUrl,
66
+ MAINNET_RPC,
67
+ DEVNET_RPC,
68
+ IDENTITY_SEED,
69
+ REPUTATION_AUTHORITY_SEED,
70
+ VALIDATION_AUTHORITY_SEED,
71
+ REVIEW_COUNTER_SEED,
72
+ MINT_TRACKER_SEED,
73
+ REVIEWS_AUTHORITY_SEED,
74
+ ATTESTATION_SEED,
75
+ REVIEW_SEED,
76
+ ESCROW_SEED,
77
+ };