@wzrd_sol/sdk 0.1.2 → 0.2.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/dist/accounts.d.ts +11 -0
- package/dist/accounts.js +40 -1
- package/dist/accounts.test.d.ts +1 -0
- package/dist/accounts.test.js +47 -0
- package/dist/agent-auth.d.ts +89 -0
- package/dist/agent-auth.js +147 -0
- package/dist/agent-loop.d.ts +87 -0
- package/dist/agent-loop.js +388 -0
- package/dist/constants.d.ts +2 -0
- package/dist/constants.js +3 -0
- package/dist/index.d.ts +13 -6
- package/dist/index.js +12 -5
- package/dist/instructions.d.ts +69 -0
- package/dist/instructions.js +200 -2
- package/dist/instructions.test.js +2 -2
- package/dist/model-selector.d.ts +141 -0
- package/dist/model-selector.js +247 -0
- package/dist/pda.d.ts +4 -0
- package/dist/pda.js +10 -1
- package/dist/stream.d.ts +88 -0
- package/dist/stream.js +220 -0
- package/dist/stream.test.d.ts +7 -0
- package/dist/stream.test.js +296 -0
- package/package.json +9 -3
package/dist/stream.d.ts
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Instruction builders for the wzrd-stream vLOFI claim system.
|
|
3
|
+
*
|
|
4
|
+
* Builds publish_stream_root, claim_stream, and claim_stream_sponsored
|
|
5
|
+
* TransactionInstructions for the vLOFI streaming distribution pipeline.
|
|
6
|
+
*
|
|
7
|
+
* Unlike CCM global claims (Token-2022), stream claims use standard SPL
|
|
8
|
+
* token program for vLOFI minting.
|
|
9
|
+
*/
|
|
10
|
+
import { PublicKey, TransactionInstruction } from '@solana/web3.js';
|
|
11
|
+
/**
|
|
12
|
+
* Build a `publish_stream_root` TransactionInstruction.
|
|
13
|
+
*
|
|
14
|
+
* Publishes a new merkle root for vLOFI stream distribution.
|
|
15
|
+
*
|
|
16
|
+
* Accounts (order must match PublishStreamRoot struct):
|
|
17
|
+
* 0. payer (signer, writable) - admin or publisher
|
|
18
|
+
* 1. protocol_state (readonly)
|
|
19
|
+
* 2. stream_root_config (writable) - PDA: seeds=[b"stream_root", vlofiMint]
|
|
20
|
+
* 3. vlofi_mint (readonly)
|
|
21
|
+
* 4. system_program (readonly)
|
|
22
|
+
*
|
|
23
|
+
* Data layout: [8 disc][8 root_seq LE][32 root][32 dataset_hash] = 80 bytes
|
|
24
|
+
*
|
|
25
|
+
* @param payer - Admin or authorized publisher (signer)
|
|
26
|
+
* @param vlofiMint - vLOFI mint address
|
|
27
|
+
* @param rootSeq - Monotonically increasing root sequence number
|
|
28
|
+
* @param root - 32-byte merkle root hash
|
|
29
|
+
* @param datasetHash - 32-byte dataset hash for auditability
|
|
30
|
+
* @param programId - Program ID (defaults to mainnet)
|
|
31
|
+
*/
|
|
32
|
+
export declare function createPublishStreamRootIx(payer: PublicKey, vlofiMint: PublicKey, rootSeq: number | bigint, root: Uint8Array | Buffer, datasetHash: Uint8Array | Buffer, programId?: PublicKey): TransactionInstruction;
|
|
33
|
+
/**
|
|
34
|
+
* Build a `claim_stream` TransactionInstruction (self-signed).
|
|
35
|
+
*
|
|
36
|
+
* Claims vLOFI tokens from the stream distribution merkle tree.
|
|
37
|
+
* The claimer signs the transaction themselves.
|
|
38
|
+
*
|
|
39
|
+
* Accounts (order must match ClaimStream struct):
|
|
40
|
+
* 0. claimer (signer, writable)
|
|
41
|
+
* 1. protocol_state (readonly)
|
|
42
|
+
* 2. stream_root_config (readonly)
|
|
43
|
+
* 3. claim_state_stream (writable) - PDA: seeds=[b"claim_stream", vlofiMint, claimer]
|
|
44
|
+
* 4. vlofi_mint (writable) - for mint_to
|
|
45
|
+
* 5. claimer_vlofi_ata (writable) - ATA(claimer, vlofiMint, TOKEN_PROGRAM)
|
|
46
|
+
* 6. token_program (readonly) - standard SPL Token
|
|
47
|
+
* 7. system_program (readonly)
|
|
48
|
+
*
|
|
49
|
+
* Data layout: [8 disc][8 root_seq LE][8 cumulative_total LE][4 proof_len LE][proof_len * 32 proof_nodes]
|
|
50
|
+
*
|
|
51
|
+
* @param claimer - Wallet claiming vLOFI (signer)
|
|
52
|
+
* @param vlofiMint - vLOFI mint address
|
|
53
|
+
* @param rootSeq - Root sequence number to claim against
|
|
54
|
+
* @param cumulativeTotal - Cumulative vLOFI amount entitled (native units)
|
|
55
|
+
* @param proofHex - Hex-encoded 32-byte merkle proof sibling hashes
|
|
56
|
+
* @param programId - Program ID (defaults to mainnet)
|
|
57
|
+
* @returns Array of instructions: idempotent ATA create + claim IX
|
|
58
|
+
*/
|
|
59
|
+
export declare function createClaimStreamIx(claimer: PublicKey, vlofiMint: PublicKey, rootSeq: number | bigint, cumulativeTotal: bigint | number, proofHex: string[], programId?: PublicKey): TransactionInstruction[];
|
|
60
|
+
/**
|
|
61
|
+
* Build a `claim_stream_sponsored` TransactionInstruction (gasless relay).
|
|
62
|
+
*
|
|
63
|
+
* A relayer pays for the transaction while vLOFI goes to the claimer.
|
|
64
|
+
* The claimer does NOT need to sign.
|
|
65
|
+
*
|
|
66
|
+
* Accounts (order must match ClaimStreamSponsored struct):
|
|
67
|
+
* 0. payer (signer, writable) - relayer
|
|
68
|
+
* 1. claimer (readonly, NOT signer)
|
|
69
|
+
* 2. protocol_state (readonly)
|
|
70
|
+
* 3. stream_root_config (readonly)
|
|
71
|
+
* 4. claim_state_stream (writable)
|
|
72
|
+
* 5. vlofi_mint (writable)
|
|
73
|
+
* 6. claimer_vlofi_ata (writable)
|
|
74
|
+
* 7. token_program (readonly)
|
|
75
|
+
* 8. system_program (readonly)
|
|
76
|
+
*
|
|
77
|
+
* Data layout: same as claim_stream
|
|
78
|
+
*
|
|
79
|
+
* @param payer - Relayer wallet (signer, pays fees)
|
|
80
|
+
* @param claimer - Wallet receiving vLOFI (NOT a signer)
|
|
81
|
+
* @param vlofiMint - vLOFI mint address
|
|
82
|
+
* @param rootSeq - Root sequence number to claim against
|
|
83
|
+
* @param cumulativeTotal - Cumulative vLOFI amount entitled (native units)
|
|
84
|
+
* @param proofHex - Hex-encoded 32-byte merkle proof sibling hashes
|
|
85
|
+
* @param programId - Program ID (defaults to mainnet)
|
|
86
|
+
* @returns Array of instructions: idempotent ATA create + sponsored claim IX
|
|
87
|
+
*/
|
|
88
|
+
export declare function createClaimStreamSponsoredIx(payer: PublicKey, claimer: PublicKey, vlofiMint: PublicKey, rootSeq: number | bigint, cumulativeTotal: bigint | number, proofHex: string[], programId?: PublicKey): TransactionInstruction[];
|
package/dist/stream.js
ADDED
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Instruction builders for the wzrd-stream vLOFI claim system.
|
|
3
|
+
*
|
|
4
|
+
* Builds publish_stream_root, claim_stream, and claim_stream_sponsored
|
|
5
|
+
* TransactionInstructions for the vLOFI streaming distribution pipeline.
|
|
6
|
+
*
|
|
7
|
+
* Unlike CCM global claims (Token-2022), stream claims use standard SPL
|
|
8
|
+
* token program for vLOFI minting.
|
|
9
|
+
*/
|
|
10
|
+
import { SystemProgram, TransactionInstruction, } from '@solana/web3.js';
|
|
11
|
+
import { PROGRAM_ID, TOKEN_PROGRAM_ID, } from './constants.js';
|
|
12
|
+
import { getProtocolStatePDA, getStreamRootConfigPDA, getClaimStateStreamPDA, getAta, } from './pda.js';
|
|
13
|
+
import { createAtaIdempotentIx } from './instructions.js';
|
|
14
|
+
// ── Discriminators (pre-computed, matching on-chain program) ─────────
|
|
15
|
+
/** publish_stream_root discriminator: SHA-256("global:publish_stream_root")[0..8] */
|
|
16
|
+
const PUBLISH_STREAM_ROOT_DISC = Buffer.from([
|
|
17
|
+
0x2f, 0xfa, 0x4d, 0xc7, 0x3e, 0x90, 0x82, 0x4c,
|
|
18
|
+
]);
|
|
19
|
+
/** claim_stream discriminator: SHA-256("global:claim_stream")[0..8] */
|
|
20
|
+
const CLAIM_STREAM_DISC = Buffer.from([
|
|
21
|
+
0x9d, 0xf7, 0xa4, 0xe2, 0xf0, 0x9e, 0xb7, 0x24,
|
|
22
|
+
]);
|
|
23
|
+
/** claim_stream_sponsored discriminator: SHA-256("global:claim_stream_sponsored")[0..8] */
|
|
24
|
+
const CLAIM_STREAM_SPONSORED_DISC = Buffer.from([
|
|
25
|
+
0xeb, 0xb1, 0x48, 0xf6, 0xff, 0xf3, 0xea, 0xbc,
|
|
26
|
+
]);
|
|
27
|
+
// ── publish_stream_root ─────────────────────────────────────────────
|
|
28
|
+
/**
|
|
29
|
+
* Build a `publish_stream_root` TransactionInstruction.
|
|
30
|
+
*
|
|
31
|
+
* Publishes a new merkle root for vLOFI stream distribution.
|
|
32
|
+
*
|
|
33
|
+
* Accounts (order must match PublishStreamRoot struct):
|
|
34
|
+
* 0. payer (signer, writable) - admin or publisher
|
|
35
|
+
* 1. protocol_state (readonly)
|
|
36
|
+
* 2. stream_root_config (writable) - PDA: seeds=[b"stream_root", vlofiMint]
|
|
37
|
+
* 3. vlofi_mint (readonly)
|
|
38
|
+
* 4. system_program (readonly)
|
|
39
|
+
*
|
|
40
|
+
* Data layout: [8 disc][8 root_seq LE][32 root][32 dataset_hash] = 80 bytes
|
|
41
|
+
*
|
|
42
|
+
* @param payer - Admin or authorized publisher (signer)
|
|
43
|
+
* @param vlofiMint - vLOFI mint address
|
|
44
|
+
* @param rootSeq - Monotonically increasing root sequence number
|
|
45
|
+
* @param root - 32-byte merkle root hash
|
|
46
|
+
* @param datasetHash - 32-byte dataset hash for auditability
|
|
47
|
+
* @param programId - Program ID (defaults to mainnet)
|
|
48
|
+
*/
|
|
49
|
+
export function createPublishStreamRootIx(payer, vlofiMint, rootSeq, root, datasetHash, programId = PROGRAM_ID) {
|
|
50
|
+
if (root.length !== 32) {
|
|
51
|
+
throw new Error(`root must be 32 bytes, got ${root.length}`);
|
|
52
|
+
}
|
|
53
|
+
if (datasetHash.length !== 32) {
|
|
54
|
+
throw new Error(`datasetHash must be 32 bytes, got ${datasetHash.length}`);
|
|
55
|
+
}
|
|
56
|
+
const protocolState = getProtocolStatePDA(programId);
|
|
57
|
+
const streamRootConfig = getStreamRootConfigPDA(vlofiMint, programId);
|
|
58
|
+
// Data: [8 disc][8 root_seq LE][32 root][32 dataset_hash] = 80 bytes
|
|
59
|
+
const data = Buffer.alloc(80);
|
|
60
|
+
let offset = 0;
|
|
61
|
+
PUBLISH_STREAM_ROOT_DISC.copy(data, offset);
|
|
62
|
+
offset += 8;
|
|
63
|
+
data.writeBigUInt64LE(BigInt(rootSeq), offset);
|
|
64
|
+
offset += 8;
|
|
65
|
+
Buffer.from(root).copy(data, offset);
|
|
66
|
+
offset += 32;
|
|
67
|
+
Buffer.from(datasetHash).copy(data, offset);
|
|
68
|
+
return new TransactionInstruction({
|
|
69
|
+
programId,
|
|
70
|
+
keys: [
|
|
71
|
+
{ pubkey: payer, isSigner: true, isWritable: true },
|
|
72
|
+
{ pubkey: protocolState, isSigner: false, isWritable: false },
|
|
73
|
+
{ pubkey: streamRootConfig, isSigner: false, isWritable: true },
|
|
74
|
+
{ pubkey: vlofiMint, isSigner: false, isWritable: false },
|
|
75
|
+
{ pubkey: SystemProgram.programId, isSigner: false, isWritable: false },
|
|
76
|
+
],
|
|
77
|
+
data,
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
// ── claim_stream (self-signed) ──────────────────────────────────────
|
|
81
|
+
/**
|
|
82
|
+
* Build a `claim_stream` TransactionInstruction (self-signed).
|
|
83
|
+
*
|
|
84
|
+
* Claims vLOFI tokens from the stream distribution merkle tree.
|
|
85
|
+
* The claimer signs the transaction themselves.
|
|
86
|
+
*
|
|
87
|
+
* Accounts (order must match ClaimStream struct):
|
|
88
|
+
* 0. claimer (signer, writable)
|
|
89
|
+
* 1. protocol_state (readonly)
|
|
90
|
+
* 2. stream_root_config (readonly)
|
|
91
|
+
* 3. claim_state_stream (writable) - PDA: seeds=[b"claim_stream", vlofiMint, claimer]
|
|
92
|
+
* 4. vlofi_mint (writable) - for mint_to
|
|
93
|
+
* 5. claimer_vlofi_ata (writable) - ATA(claimer, vlofiMint, TOKEN_PROGRAM)
|
|
94
|
+
* 6. token_program (readonly) - standard SPL Token
|
|
95
|
+
* 7. system_program (readonly)
|
|
96
|
+
*
|
|
97
|
+
* Data layout: [8 disc][8 root_seq LE][8 cumulative_total LE][4 proof_len LE][proof_len * 32 proof_nodes]
|
|
98
|
+
*
|
|
99
|
+
* @param claimer - Wallet claiming vLOFI (signer)
|
|
100
|
+
* @param vlofiMint - vLOFI mint address
|
|
101
|
+
* @param rootSeq - Root sequence number to claim against
|
|
102
|
+
* @param cumulativeTotal - Cumulative vLOFI amount entitled (native units)
|
|
103
|
+
* @param proofHex - Hex-encoded 32-byte merkle proof sibling hashes
|
|
104
|
+
* @param programId - Program ID (defaults to mainnet)
|
|
105
|
+
* @returns Array of instructions: idempotent ATA create + claim IX
|
|
106
|
+
*/
|
|
107
|
+
export function createClaimStreamIx(claimer, vlofiMint, rootSeq, cumulativeTotal, proofHex, programId = PROGRAM_ID) {
|
|
108
|
+
const protocolState = getProtocolStatePDA(programId);
|
|
109
|
+
const streamRootConfig = getStreamRootConfigPDA(vlofiMint, programId);
|
|
110
|
+
const claimStateStream = getClaimStateStreamPDA(vlofiMint, claimer, programId);
|
|
111
|
+
const claimerVlofiAta = getAta(claimer, vlofiMint, TOKEN_PROGRAM_ID);
|
|
112
|
+
// Prepend idempotent ATA creation for claimer's vLOFI account
|
|
113
|
+
const ixs = [
|
|
114
|
+
createAtaIdempotentIx(claimer, claimerVlofiAta, claimer, vlofiMint, TOKEN_PROGRAM_ID),
|
|
115
|
+
];
|
|
116
|
+
const proofBytes = proofHex.map((h) => Buffer.from(h, 'hex'));
|
|
117
|
+
// Data: [8 disc][8 root_seq LE][8 cumulative_total LE][4 proof_len LE][N * 32 proof]
|
|
118
|
+
const dataLen = 8 + 8 + 8 + 4 + proofBytes.length * 32;
|
|
119
|
+
const data = Buffer.alloc(dataLen);
|
|
120
|
+
let offset = 0;
|
|
121
|
+
CLAIM_STREAM_DISC.copy(data, offset);
|
|
122
|
+
offset += 8;
|
|
123
|
+
data.writeBigUInt64LE(BigInt(rootSeq), offset);
|
|
124
|
+
offset += 8;
|
|
125
|
+
data.writeBigUInt64LE(BigInt(cumulativeTotal), offset);
|
|
126
|
+
offset += 8;
|
|
127
|
+
data.writeUInt32LE(proofBytes.length, offset);
|
|
128
|
+
offset += 4;
|
|
129
|
+
for (const node of proofBytes) {
|
|
130
|
+
node.copy(data, offset);
|
|
131
|
+
offset += 32;
|
|
132
|
+
}
|
|
133
|
+
ixs.push(new TransactionInstruction({
|
|
134
|
+
programId,
|
|
135
|
+
keys: [
|
|
136
|
+
{ pubkey: claimer, isSigner: true, isWritable: true },
|
|
137
|
+
{ pubkey: protocolState, isSigner: false, isWritable: false },
|
|
138
|
+
{ pubkey: streamRootConfig, isSigner: false, isWritable: false },
|
|
139
|
+
{ pubkey: claimStateStream, isSigner: false, isWritable: true },
|
|
140
|
+
{ pubkey: vlofiMint, isSigner: false, isWritable: true },
|
|
141
|
+
{ pubkey: claimerVlofiAta, isSigner: false, isWritable: true },
|
|
142
|
+
{ pubkey: TOKEN_PROGRAM_ID, isSigner: false, isWritable: false },
|
|
143
|
+
{ pubkey: SystemProgram.programId, isSigner: false, isWritable: false },
|
|
144
|
+
],
|
|
145
|
+
data,
|
|
146
|
+
}));
|
|
147
|
+
return ixs;
|
|
148
|
+
}
|
|
149
|
+
// ── claim_stream_sponsored (gasless relay) ──────────────────────────
|
|
150
|
+
/**
|
|
151
|
+
* Build a `claim_stream_sponsored` TransactionInstruction (gasless relay).
|
|
152
|
+
*
|
|
153
|
+
* A relayer pays for the transaction while vLOFI goes to the claimer.
|
|
154
|
+
* The claimer does NOT need to sign.
|
|
155
|
+
*
|
|
156
|
+
* Accounts (order must match ClaimStreamSponsored struct):
|
|
157
|
+
* 0. payer (signer, writable) - relayer
|
|
158
|
+
* 1. claimer (readonly, NOT signer)
|
|
159
|
+
* 2. protocol_state (readonly)
|
|
160
|
+
* 3. stream_root_config (readonly)
|
|
161
|
+
* 4. claim_state_stream (writable)
|
|
162
|
+
* 5. vlofi_mint (writable)
|
|
163
|
+
* 6. claimer_vlofi_ata (writable)
|
|
164
|
+
* 7. token_program (readonly)
|
|
165
|
+
* 8. system_program (readonly)
|
|
166
|
+
*
|
|
167
|
+
* Data layout: same as claim_stream
|
|
168
|
+
*
|
|
169
|
+
* @param payer - Relayer wallet (signer, pays fees)
|
|
170
|
+
* @param claimer - Wallet receiving vLOFI (NOT a signer)
|
|
171
|
+
* @param vlofiMint - vLOFI mint address
|
|
172
|
+
* @param rootSeq - Root sequence number to claim against
|
|
173
|
+
* @param cumulativeTotal - Cumulative vLOFI amount entitled (native units)
|
|
174
|
+
* @param proofHex - Hex-encoded 32-byte merkle proof sibling hashes
|
|
175
|
+
* @param programId - Program ID (defaults to mainnet)
|
|
176
|
+
* @returns Array of instructions: idempotent ATA create + sponsored claim IX
|
|
177
|
+
*/
|
|
178
|
+
export function createClaimStreamSponsoredIx(payer, claimer, vlofiMint, rootSeq, cumulativeTotal, proofHex, programId = PROGRAM_ID) {
|
|
179
|
+
const protocolState = getProtocolStatePDA(programId);
|
|
180
|
+
const streamRootConfig = getStreamRootConfigPDA(vlofiMint, programId);
|
|
181
|
+
const claimStateStream = getClaimStateStreamPDA(vlofiMint, claimer, programId);
|
|
182
|
+
const claimerVlofiAta = getAta(claimer, vlofiMint, TOKEN_PROGRAM_ID);
|
|
183
|
+
// Relayer pays for ATA creation if needed
|
|
184
|
+
const ixs = [
|
|
185
|
+
createAtaIdempotentIx(payer, claimerVlofiAta, claimer, vlofiMint, TOKEN_PROGRAM_ID),
|
|
186
|
+
];
|
|
187
|
+
const proofBytes = proofHex.map((h) => Buffer.from(h, 'hex'));
|
|
188
|
+
// Data: [8 disc][8 root_seq LE][8 cumulative_total LE][4 proof_len LE][N * 32 proof]
|
|
189
|
+
const dataLen = 8 + 8 + 8 + 4 + proofBytes.length * 32;
|
|
190
|
+
const data = Buffer.alloc(dataLen);
|
|
191
|
+
let offset = 0;
|
|
192
|
+
CLAIM_STREAM_SPONSORED_DISC.copy(data, offset);
|
|
193
|
+
offset += 8;
|
|
194
|
+
data.writeBigUInt64LE(BigInt(rootSeq), offset);
|
|
195
|
+
offset += 8;
|
|
196
|
+
data.writeBigUInt64LE(BigInt(cumulativeTotal), offset);
|
|
197
|
+
offset += 8;
|
|
198
|
+
data.writeUInt32LE(proofBytes.length, offset);
|
|
199
|
+
offset += 4;
|
|
200
|
+
for (const node of proofBytes) {
|
|
201
|
+
node.copy(data, offset);
|
|
202
|
+
offset += 32;
|
|
203
|
+
}
|
|
204
|
+
ixs.push(new TransactionInstruction({
|
|
205
|
+
programId,
|
|
206
|
+
keys: [
|
|
207
|
+
{ pubkey: payer, isSigner: true, isWritable: true },
|
|
208
|
+
{ pubkey: claimer, isSigner: false, isWritable: false },
|
|
209
|
+
{ pubkey: protocolState, isSigner: false, isWritable: false },
|
|
210
|
+
{ pubkey: streamRootConfig, isSigner: false, isWritable: false },
|
|
211
|
+
{ pubkey: claimStateStream, isSigner: false, isWritable: true },
|
|
212
|
+
{ pubkey: vlofiMint, isSigner: false, isWritable: true },
|
|
213
|
+
{ pubkey: claimerVlofiAta, isSigner: false, isWritable: true },
|
|
214
|
+
{ pubkey: TOKEN_PROGRAM_ID, isSigner: false, isWritable: false },
|
|
215
|
+
{ pubkey: SystemProgram.programId, isSigner: false, isWritable: false },
|
|
216
|
+
],
|
|
217
|
+
data,
|
|
218
|
+
}));
|
|
219
|
+
return ixs;
|
|
220
|
+
}
|
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit tests for stream (vLOFI) instruction builders and PDA derivation.
|
|
3
|
+
*
|
|
4
|
+
* Tests discriminator bytes, data layout encoding, PDA derivation
|
|
5
|
+
* determinism, and account ordering without requiring an RPC Connection.
|
|
6
|
+
*/
|
|
7
|
+
import { describe, it, expect } from 'vitest';
|
|
8
|
+
import { PublicKey, SystemProgram } from '@solana/web3.js';
|
|
9
|
+
import { createPublishStreamRootIx, createClaimStreamIx, createClaimStreamSponsoredIx, } from './stream.js';
|
|
10
|
+
import { PROGRAM_ID, TOKEN_PROGRAM_ID, } from './constants.js';
|
|
11
|
+
import { getProtocolStatePDA, getStreamRootConfigPDA, getClaimStateStreamPDA, getClaimStatePDA, getAta, } from './pda.js';
|
|
12
|
+
// ── Test fixtures ────────────────────────────────────────
|
|
13
|
+
const VLOFI_MINT = new PublicKey('E9Kt33axpCy3ve2PCY9BSrbPhcR9wdDsWQECAahzw2dS');
|
|
14
|
+
const CLAIMER = new PublicKey('Dxk8mAb3C7AM8JN6tAJfVuSja5yidhZM5sEKW3SRX2BM');
|
|
15
|
+
const PAYER = new PublicKey('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA');
|
|
16
|
+
const DUMMY_ROOT = Buffer.alloc(32, 0xab);
|
|
17
|
+
const DUMMY_DATASET_HASH = Buffer.alloc(32, 0xcd);
|
|
18
|
+
// Two 32-byte hex proof nodes for testing
|
|
19
|
+
const PROOF_HEX = [
|
|
20
|
+
'aa'.repeat(32),
|
|
21
|
+
'bb'.repeat(32),
|
|
22
|
+
];
|
|
23
|
+
// ── Discriminators ──────────────────────────────────────
|
|
24
|
+
describe('stream discriminators', () => {
|
|
25
|
+
it('publish_stream_root disc matches spec', () => {
|
|
26
|
+
const ix = createPublishStreamRootIx(PAYER, VLOFI_MINT, 1, DUMMY_ROOT, DUMMY_DATASET_HASH);
|
|
27
|
+
const disc = ix.data.subarray(0, 8);
|
|
28
|
+
expect(Buffer.from(disc)).toEqual(Buffer.from([0x2f, 0xfa, 0x4d, 0xc7, 0x3e, 0x90, 0x82, 0x4c]));
|
|
29
|
+
});
|
|
30
|
+
it('claim_stream disc matches spec', () => {
|
|
31
|
+
const ixs = createClaimStreamIx(CLAIMER, VLOFI_MINT, 1, 1000n, PROOF_HEX);
|
|
32
|
+
// Second IX is the claim (first is ATA create)
|
|
33
|
+
const claimIx = ixs[1];
|
|
34
|
+
const disc = claimIx.data.subarray(0, 8);
|
|
35
|
+
expect(Buffer.from(disc)).toEqual(Buffer.from([0x9d, 0xf7, 0xa4, 0xe2, 0xf0, 0x9e, 0xb7, 0x24]));
|
|
36
|
+
});
|
|
37
|
+
it('claim_stream_sponsored disc matches spec', () => {
|
|
38
|
+
const ixs = createClaimStreamSponsoredIx(PAYER, CLAIMER, VLOFI_MINT, 1, 1000n, PROOF_HEX);
|
|
39
|
+
const claimIx = ixs[1];
|
|
40
|
+
const disc = claimIx.data.subarray(0, 8);
|
|
41
|
+
expect(Buffer.from(disc)).toEqual(Buffer.from([0xeb, 0xb1, 0x48, 0xf6, 0xff, 0xf3, 0xea, 0xbc]));
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
// ── publish_stream_root ─────────────────────────────────
|
|
45
|
+
describe('createPublishStreamRootIx', () => {
|
|
46
|
+
const ix = createPublishStreamRootIx(PAYER, VLOFI_MINT, 42, DUMMY_ROOT, DUMMY_DATASET_HASH);
|
|
47
|
+
it('uses PROGRAM_ID', () => {
|
|
48
|
+
expect(ix.programId.equals(PROGRAM_ID)).toBe(true);
|
|
49
|
+
});
|
|
50
|
+
it('has exactly 5 account keys', () => {
|
|
51
|
+
expect(ix.keys.length).toBe(5);
|
|
52
|
+
});
|
|
53
|
+
it('data is exactly 80 bytes', () => {
|
|
54
|
+
expect(ix.data.length).toBe(80);
|
|
55
|
+
});
|
|
56
|
+
it('encodes root_seq as LE u64 at offset 8', () => {
|
|
57
|
+
const rootSeq = ix.data.readBigUInt64LE(8);
|
|
58
|
+
expect(rootSeq).toBe(42n);
|
|
59
|
+
});
|
|
60
|
+
it('encodes root at offset 16', () => {
|
|
61
|
+
const root = ix.data.subarray(16, 48);
|
|
62
|
+
expect(Buffer.from(root)).toEqual(DUMMY_ROOT);
|
|
63
|
+
});
|
|
64
|
+
it('encodes dataset_hash at offset 48', () => {
|
|
65
|
+
const hash = ix.data.subarray(48, 80);
|
|
66
|
+
expect(Buffer.from(hash)).toEqual(DUMMY_DATASET_HASH);
|
|
67
|
+
});
|
|
68
|
+
it('sets payer as signer + writable', () => {
|
|
69
|
+
expect(ix.keys[0].pubkey.equals(PAYER)).toBe(true);
|
|
70
|
+
expect(ix.keys[0].isSigner).toBe(true);
|
|
71
|
+
expect(ix.keys[0].isWritable).toBe(true);
|
|
72
|
+
});
|
|
73
|
+
it('sets protocol_state as readonly', () => {
|
|
74
|
+
const protocolState = getProtocolStatePDA(PROGRAM_ID);
|
|
75
|
+
expect(ix.keys[1].pubkey.equals(protocolState)).toBe(true);
|
|
76
|
+
expect(ix.keys[1].isSigner).toBe(false);
|
|
77
|
+
expect(ix.keys[1].isWritable).toBe(false);
|
|
78
|
+
});
|
|
79
|
+
it('sets stream_root_config as writable', () => {
|
|
80
|
+
const streamRootConfig = getStreamRootConfigPDA(VLOFI_MINT, PROGRAM_ID);
|
|
81
|
+
expect(ix.keys[2].pubkey.equals(streamRootConfig)).toBe(true);
|
|
82
|
+
expect(ix.keys[2].isSigner).toBe(false);
|
|
83
|
+
expect(ix.keys[2].isWritable).toBe(true);
|
|
84
|
+
});
|
|
85
|
+
it('sets vlofi_mint as readonly', () => {
|
|
86
|
+
expect(ix.keys[3].pubkey.equals(VLOFI_MINT)).toBe(true);
|
|
87
|
+
expect(ix.keys[3].isSigner).toBe(false);
|
|
88
|
+
expect(ix.keys[3].isWritable).toBe(false);
|
|
89
|
+
});
|
|
90
|
+
it('sets system_program as readonly', () => {
|
|
91
|
+
expect(ix.keys[4].pubkey.equals(SystemProgram.programId)).toBe(true);
|
|
92
|
+
expect(ix.keys[4].isSigner).toBe(false);
|
|
93
|
+
expect(ix.keys[4].isWritable).toBe(false);
|
|
94
|
+
});
|
|
95
|
+
it('rejects root with wrong length', () => {
|
|
96
|
+
expect(() => createPublishStreamRootIx(PAYER, VLOFI_MINT, 1, Buffer.alloc(16), DUMMY_DATASET_HASH)).toThrow('root must be 32 bytes');
|
|
97
|
+
});
|
|
98
|
+
it('rejects datasetHash with wrong length', () => {
|
|
99
|
+
expect(() => createPublishStreamRootIx(PAYER, VLOFI_MINT, 1, DUMMY_ROOT, Buffer.alloc(64))).toThrow('datasetHash must be 32 bytes');
|
|
100
|
+
});
|
|
101
|
+
it('accepts bigint for rootSeq', () => {
|
|
102
|
+
const bigIx = createPublishStreamRootIx(PAYER, VLOFI_MINT, 9007199254740993n, DUMMY_ROOT, DUMMY_DATASET_HASH);
|
|
103
|
+
expect(bigIx.data.readBigUInt64LE(8)).toBe(9007199254740993n);
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
// ── claim_stream (self-signed) ──────────────────────────
|
|
107
|
+
describe('createClaimStreamIx', () => {
|
|
108
|
+
const ixs = createClaimStreamIx(CLAIMER, VLOFI_MINT, 7, 500000n, PROOF_HEX);
|
|
109
|
+
it('returns 2 instructions (ATA create + claim)', () => {
|
|
110
|
+
expect(ixs.length).toBe(2);
|
|
111
|
+
});
|
|
112
|
+
it('first instruction is ATA idempotent create', () => {
|
|
113
|
+
// ATA create uses the associated token program
|
|
114
|
+
const ataIx = ixs[0];
|
|
115
|
+
expect(ataIx.data.length).toBe(1);
|
|
116
|
+
expect(ataIx.data[0]).toBe(1); // CreateIdempotent
|
|
117
|
+
});
|
|
118
|
+
const claimIx = ixs[1];
|
|
119
|
+
it('claim instruction uses PROGRAM_ID', () => {
|
|
120
|
+
expect(claimIx.programId.equals(PROGRAM_ID)).toBe(true);
|
|
121
|
+
});
|
|
122
|
+
it('has exactly 8 account keys', () => {
|
|
123
|
+
expect(claimIx.keys.length).toBe(8);
|
|
124
|
+
});
|
|
125
|
+
it('data length = 28 + proof_len * 32', () => {
|
|
126
|
+
// 8 disc + 8 root_seq + 8 cumulative + 4 proof_len + 2*32 proof = 92
|
|
127
|
+
expect(claimIx.data.length).toBe(28 + 2 * 32);
|
|
128
|
+
});
|
|
129
|
+
it('encodes root_seq as LE u64 at offset 8', () => {
|
|
130
|
+
expect(claimIx.data.readBigUInt64LE(8)).toBe(7n);
|
|
131
|
+
});
|
|
132
|
+
it('encodes cumulative_total as LE u64 at offset 16', () => {
|
|
133
|
+
expect(claimIx.data.readBigUInt64LE(16)).toBe(500000n);
|
|
134
|
+
});
|
|
135
|
+
it('encodes proof_len as LE u32 at offset 24', () => {
|
|
136
|
+
expect(claimIx.data.readUInt32LE(24)).toBe(2);
|
|
137
|
+
});
|
|
138
|
+
it('encodes proof nodes starting at offset 28', () => {
|
|
139
|
+
const node0 = claimIx.data.subarray(28, 60);
|
|
140
|
+
const node1 = claimIx.data.subarray(60, 92);
|
|
141
|
+
expect(Buffer.from(node0)).toEqual(Buffer.from(PROOF_HEX[0], 'hex'));
|
|
142
|
+
expect(Buffer.from(node1)).toEqual(Buffer.from(PROOF_HEX[1], 'hex'));
|
|
143
|
+
});
|
|
144
|
+
it('sets claimer as signer + writable (account 0)', () => {
|
|
145
|
+
expect(claimIx.keys[0].pubkey.equals(CLAIMER)).toBe(true);
|
|
146
|
+
expect(claimIx.keys[0].isSigner).toBe(true);
|
|
147
|
+
expect(claimIx.keys[0].isWritable).toBe(true);
|
|
148
|
+
});
|
|
149
|
+
it('sets protocol_state as readonly (account 1)', () => {
|
|
150
|
+
const protocolState = getProtocolStatePDA(PROGRAM_ID);
|
|
151
|
+
expect(claimIx.keys[1].pubkey.equals(protocolState)).toBe(true);
|
|
152
|
+
expect(claimIx.keys[1].isWritable).toBe(false);
|
|
153
|
+
});
|
|
154
|
+
it('sets stream_root_config as readonly (account 2)', () => {
|
|
155
|
+
const streamRootConfig = getStreamRootConfigPDA(VLOFI_MINT, PROGRAM_ID);
|
|
156
|
+
expect(claimIx.keys[2].pubkey.equals(streamRootConfig)).toBe(true);
|
|
157
|
+
expect(claimIx.keys[2].isWritable).toBe(false);
|
|
158
|
+
});
|
|
159
|
+
it('sets claim_state_stream as writable (account 3)', () => {
|
|
160
|
+
const claimState = getClaimStateStreamPDA(VLOFI_MINT, CLAIMER, PROGRAM_ID);
|
|
161
|
+
expect(claimIx.keys[3].pubkey.equals(claimState)).toBe(true);
|
|
162
|
+
expect(claimIx.keys[3].isWritable).toBe(true);
|
|
163
|
+
});
|
|
164
|
+
it('sets vlofi_mint as writable (account 4)', () => {
|
|
165
|
+
expect(claimIx.keys[4].pubkey.equals(VLOFI_MINT)).toBe(true);
|
|
166
|
+
expect(claimIx.keys[4].isWritable).toBe(true);
|
|
167
|
+
});
|
|
168
|
+
it('sets claimer_vlofi_ata as writable (account 5)', () => {
|
|
169
|
+
const ata = getAta(CLAIMER, VLOFI_MINT, TOKEN_PROGRAM_ID);
|
|
170
|
+
expect(claimIx.keys[5].pubkey.equals(ata)).toBe(true);
|
|
171
|
+
expect(claimIx.keys[5].isWritable).toBe(true);
|
|
172
|
+
});
|
|
173
|
+
it('uses standard SPL token_program (account 6)', () => {
|
|
174
|
+
expect(claimIx.keys[6].pubkey.equals(TOKEN_PROGRAM_ID)).toBe(true);
|
|
175
|
+
expect(claimIx.keys[6].isWritable).toBe(false);
|
|
176
|
+
});
|
|
177
|
+
it('uses system_program (account 7)', () => {
|
|
178
|
+
expect(claimIx.keys[7].pubkey.equals(SystemProgram.programId)).toBe(true);
|
|
179
|
+
expect(claimIx.keys[7].isWritable).toBe(false);
|
|
180
|
+
});
|
|
181
|
+
it('handles empty proof', () => {
|
|
182
|
+
const emptyIxs = createClaimStreamIx(CLAIMER, VLOFI_MINT, 1, 0n, []);
|
|
183
|
+
const claimData = emptyIxs[1].data;
|
|
184
|
+
expect(claimData.length).toBe(28); // 8+8+8+4, zero proof nodes
|
|
185
|
+
expect(claimData.readUInt32LE(24)).toBe(0);
|
|
186
|
+
});
|
|
187
|
+
it('accepts number for cumulativeTotal', () => {
|
|
188
|
+
const numIxs = createClaimStreamIx(CLAIMER, VLOFI_MINT, 1, 12345, PROOF_HEX);
|
|
189
|
+
expect(numIxs[1].data.readBigUInt64LE(16)).toBe(12345n);
|
|
190
|
+
});
|
|
191
|
+
});
|
|
192
|
+
// ── claim_stream_sponsored (gasless relay) ──────────────
|
|
193
|
+
describe('createClaimStreamSponsoredIx', () => {
|
|
194
|
+
const ixs = createClaimStreamSponsoredIx(PAYER, CLAIMER, VLOFI_MINT, 10, 1000000n, PROOF_HEX);
|
|
195
|
+
it('returns 2 instructions (ATA create + sponsored claim)', () => {
|
|
196
|
+
expect(ixs.length).toBe(2);
|
|
197
|
+
});
|
|
198
|
+
const claimIx = ixs[1];
|
|
199
|
+
it('has exactly 9 account keys', () => {
|
|
200
|
+
expect(claimIx.keys.length).toBe(9);
|
|
201
|
+
});
|
|
202
|
+
it('sets payer as signer + writable (account 0)', () => {
|
|
203
|
+
expect(claimIx.keys[0].pubkey.equals(PAYER)).toBe(true);
|
|
204
|
+
expect(claimIx.keys[0].isSigner).toBe(true);
|
|
205
|
+
expect(claimIx.keys[0].isWritable).toBe(true);
|
|
206
|
+
});
|
|
207
|
+
it('sets claimer as NOT signer, readonly (account 1)', () => {
|
|
208
|
+
expect(claimIx.keys[1].pubkey.equals(CLAIMER)).toBe(true);
|
|
209
|
+
expect(claimIx.keys[1].isSigner).toBe(false);
|
|
210
|
+
expect(claimIx.keys[1].isWritable).toBe(false);
|
|
211
|
+
});
|
|
212
|
+
it('sets protocol_state as readonly (account 2)', () => {
|
|
213
|
+
const protocolState = getProtocolStatePDA(PROGRAM_ID);
|
|
214
|
+
expect(claimIx.keys[2].pubkey.equals(protocolState)).toBe(true);
|
|
215
|
+
expect(claimIx.keys[2].isWritable).toBe(false);
|
|
216
|
+
});
|
|
217
|
+
it('sets stream_root_config as readonly (account 3)', () => {
|
|
218
|
+
const streamRootConfig = getStreamRootConfigPDA(VLOFI_MINT, PROGRAM_ID);
|
|
219
|
+
expect(claimIx.keys[3].pubkey.equals(streamRootConfig)).toBe(true);
|
|
220
|
+
expect(claimIx.keys[3].isWritable).toBe(false);
|
|
221
|
+
});
|
|
222
|
+
it('sets claim_state_stream as writable (account 4)', () => {
|
|
223
|
+
const claimState = getClaimStateStreamPDA(VLOFI_MINT, CLAIMER, PROGRAM_ID);
|
|
224
|
+
expect(claimIx.keys[4].pubkey.equals(claimState)).toBe(true);
|
|
225
|
+
expect(claimIx.keys[4].isWritable).toBe(true);
|
|
226
|
+
});
|
|
227
|
+
it('sets vlofi_mint as writable (account 5)', () => {
|
|
228
|
+
expect(claimIx.keys[5].pubkey.equals(VLOFI_MINT)).toBe(true);
|
|
229
|
+
expect(claimIx.keys[5].isWritable).toBe(true);
|
|
230
|
+
});
|
|
231
|
+
it('sets claimer_vlofi_ata as writable (account 6)', () => {
|
|
232
|
+
const ata = getAta(CLAIMER, VLOFI_MINT, TOKEN_PROGRAM_ID);
|
|
233
|
+
expect(claimIx.keys[6].pubkey.equals(ata)).toBe(true);
|
|
234
|
+
expect(claimIx.keys[6].isWritable).toBe(true);
|
|
235
|
+
});
|
|
236
|
+
it('uses standard SPL token_program (account 7)', () => {
|
|
237
|
+
expect(claimIx.keys[7].pubkey.equals(TOKEN_PROGRAM_ID)).toBe(true);
|
|
238
|
+
expect(claimIx.keys[7].isWritable).toBe(false);
|
|
239
|
+
});
|
|
240
|
+
it('uses system_program (account 8)', () => {
|
|
241
|
+
expect(claimIx.keys[8].pubkey.equals(SystemProgram.programId)).toBe(true);
|
|
242
|
+
expect(claimIx.keys[8].isWritable).toBe(false);
|
|
243
|
+
});
|
|
244
|
+
it('data layout matches claim_stream layout', () => {
|
|
245
|
+
// Same layout: [8 disc][8 root_seq][8 cumulative][4 proof_len][N*32]
|
|
246
|
+
expect(claimIx.data.readBigUInt64LE(8)).toBe(10n); // root_seq
|
|
247
|
+
expect(claimIx.data.readBigUInt64LE(16)).toBe(1000000n); // cumulative_total
|
|
248
|
+
expect(claimIx.data.readUInt32LE(24)).toBe(2); // proof_len
|
|
249
|
+
});
|
|
250
|
+
it('ATA create instruction uses payer (relayer) as fee payer', () => {
|
|
251
|
+
const ataIx = ixs[0];
|
|
252
|
+
expect(ataIx.keys[0].pubkey.equals(PAYER)).toBe(true);
|
|
253
|
+
expect(ataIx.keys[0].isSigner).toBe(true);
|
|
254
|
+
});
|
|
255
|
+
});
|
|
256
|
+
// ── Stream PDA derivation ───────────────────────────────
|
|
257
|
+
describe('Stream PDA derivation', () => {
|
|
258
|
+
it('getStreamRootConfigPDA is deterministic', () => {
|
|
259
|
+
const a = getStreamRootConfigPDA(VLOFI_MINT, PROGRAM_ID);
|
|
260
|
+
const b = getStreamRootConfigPDA(VLOFI_MINT, PROGRAM_ID);
|
|
261
|
+
expect(a.equals(b)).toBe(true);
|
|
262
|
+
});
|
|
263
|
+
it('getStreamRootConfigPDA is off-curve (valid PDA)', () => {
|
|
264
|
+
const pda = getStreamRootConfigPDA(VLOFI_MINT, PROGRAM_ID);
|
|
265
|
+
expect(PublicKey.isOnCurve(pda.toBuffer())).toBe(false);
|
|
266
|
+
});
|
|
267
|
+
it('getStreamRootConfigPDA differs by mint', () => {
|
|
268
|
+
const mint2 = new PublicKey('Dxk8mAb3C7AM8JN6tAJfVuSja5yidhZM5sEKW3SRX2BM');
|
|
269
|
+
const a = getStreamRootConfigPDA(VLOFI_MINT, PROGRAM_ID);
|
|
270
|
+
const b = getStreamRootConfigPDA(mint2, PROGRAM_ID);
|
|
271
|
+
// Same mint should give same PDA
|
|
272
|
+
if (VLOFI_MINT.equals(mint2)) {
|
|
273
|
+
expect(a.equals(b)).toBe(true);
|
|
274
|
+
}
|
|
275
|
+
});
|
|
276
|
+
it('getClaimStateStreamPDA is deterministic', () => {
|
|
277
|
+
const a = getClaimStateStreamPDA(VLOFI_MINT, CLAIMER, PROGRAM_ID);
|
|
278
|
+
const b = getClaimStateStreamPDA(VLOFI_MINT, CLAIMER, PROGRAM_ID);
|
|
279
|
+
expect(a.equals(b)).toBe(true);
|
|
280
|
+
});
|
|
281
|
+
it('getClaimStateStreamPDA is off-curve (valid PDA)', () => {
|
|
282
|
+
const pda = getClaimStateStreamPDA(VLOFI_MINT, CLAIMER, PROGRAM_ID);
|
|
283
|
+
expect(PublicKey.isOnCurve(pda.toBuffer())).toBe(false);
|
|
284
|
+
});
|
|
285
|
+
it('getClaimStateStreamPDA differs by claimer', () => {
|
|
286
|
+
const claimer2 = new PublicKey('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA');
|
|
287
|
+
const a = getClaimStateStreamPDA(VLOFI_MINT, CLAIMER, PROGRAM_ID);
|
|
288
|
+
const b = getClaimStateStreamPDA(VLOFI_MINT, claimer2, PROGRAM_ID);
|
|
289
|
+
expect(a.equals(b)).toBe(false);
|
|
290
|
+
});
|
|
291
|
+
it('getClaimStateStreamPDA differs from global claim PDA (different seed)', () => {
|
|
292
|
+
const streamPda = getClaimStateStreamPDA(VLOFI_MINT, CLAIMER, PROGRAM_ID);
|
|
293
|
+
const globalPda = getClaimStatePDA(VLOFI_MINT, CLAIMER, PROGRAM_ID);
|
|
294
|
+
expect(streamPda.equals(globalPda)).toBe(false);
|
|
295
|
+
});
|
|
296
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wzrd_sol/sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "TypeScript SDK for the WZRD Liquid Attention Protocol — deposit, settle, claim on Solana",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -8,7 +8,9 @@
|
|
|
8
8
|
"exports": {
|
|
9
9
|
".": {
|
|
10
10
|
"import": "./dist/index.js",
|
|
11
|
-
"
|
|
11
|
+
"require": "./dist/index.js",
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"default": "./dist/index.js"
|
|
12
14
|
}
|
|
13
15
|
},
|
|
14
16
|
"files": [
|
|
@@ -43,7 +45,11 @@
|
|
|
43
45
|
"access": "public"
|
|
44
46
|
},
|
|
45
47
|
"peerDependencies": {
|
|
46
|
-
"@solana/web3.js": "^1.90.0"
|
|
48
|
+
"@solana/web3.js": "^1.90.0",
|
|
49
|
+
"tweetnacl": "^1.0.0"
|
|
50
|
+
},
|
|
51
|
+
"dependencies": {
|
|
52
|
+
"tweetnacl": "^1.0.3"
|
|
47
53
|
},
|
|
48
54
|
"devDependencies": {
|
|
49
55
|
"@solana/web3.js": "^1.95.0",
|