@thru/thru-sdk 0.1.15 → 0.1.19
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 +77 -0
- package/dist/{chunk-FDZQR6ZZ.js → chunk-PH7P5EEU.js} +168 -65
- package/dist/chunk-PH7P5EEU.js.map +1 -0
- package/dist/client.d.ts +16 -3
- package/dist/client.js +19 -3
- package/dist/client.js.map +1 -1
- package/dist/metafile-esm.json +1 -1
- package/dist/sdk.d.ts +4 -3
- package/dist/sdk.js +1 -1
- package/dist/{transactions-X2KKrGw6.d.ts → transactions-BzD9hYlc.d.ts} +552 -88
- package/package.json +11 -4
- package/thru-ts-client-sdk/__tests__/helpers/test-utils.ts +228 -0
- package/thru-ts-client-sdk/core/__tests__/bound-client.test.ts +354 -0
- package/thru-ts-client-sdk/core/__tests__/client.test.ts +53 -0
- package/thru-ts-client-sdk/core/bound-client.ts +23 -2
- package/thru-ts-client-sdk/defaults.ts +10 -1
- package/thru-ts-client-sdk/modules/__tests__/accounts.test.ts +406 -0
- package/thru-ts-client-sdk/modules/__tests__/blocks.test.ts +199 -0
- package/thru-ts-client-sdk/modules/__tests__/events.test.ts +74 -0
- package/thru-ts-client-sdk/modules/__tests__/height.test.ts +39 -0
- package/thru-ts-client-sdk/modules/__tests__/helpers.test.ts +288 -0
- package/thru-ts-client-sdk/modules/__tests__/keys.test.ts +55 -0
- package/thru-ts-client-sdk/modules/__tests__/proofs.test.ts +119 -0
- package/thru-ts-client-sdk/modules/__tests__/streaming.test.ts +152 -0
- package/thru-ts-client-sdk/modules/__tests__/transactions.test.ts +730 -0
- package/thru-ts-client-sdk/modules/__tests__/version.test.ts +40 -0
- package/thru-ts-client-sdk/modules/accounts.ts +14 -18
- package/thru-ts-client-sdk/modules/streaming.ts +102 -3
- package/thru-ts-client-sdk/modules/transactions.ts +136 -36
- package/thru-ts-client-sdk/modules/version.ts +10 -0
- package/thru-ts-client-sdk/proto/thru/common/v1/consensus_pb.ts +2 -3
- package/thru-ts-client-sdk/proto/thru/common/v1/errors_pb.ts +7 -8
- package/thru-ts-client-sdk/proto/thru/common/v1/filters_pb.ts +58 -10
- package/thru-ts-client-sdk/proto/thru/common/v1/pagination_pb.ts +12 -13
- package/thru-ts-client-sdk/proto/thru/core/v1/account_pb.ts +27 -26
- package/thru-ts-client-sdk/proto/thru/core/v1/block_pb.ts +7 -8
- package/thru-ts-client-sdk/proto/thru/core/v1/state_pb.ts +4 -5
- package/thru-ts-client-sdk/proto/thru/core/v1/transaction_pb.ts +213 -12
- package/thru-ts-client-sdk/proto/thru/core/v1/types_pb.ts +2 -3
- package/thru-ts-client-sdk/proto/thru/services/v1/command_service_pb.ts +68 -3
- package/thru-ts-client-sdk/proto/thru/services/v1/query_service_pb.ts +84 -78
- package/thru-ts-client-sdk/proto/thru/services/v1/streaming_service_pb.ts +24 -25
- package/thru-ts-client-sdk/sdk.ts +5 -3
- package/thru-ts-client-sdk/test-scripts/counter.ts +353 -100
- package/thru-ts-client-sdk/transactions/Transaction.ts +10 -10
- package/thru-ts-client-sdk/transactions/TransactionBuilder.ts +12 -7
- package/thru-ts-client-sdk/transactions/__tests__/TransactionBuilder.test.ts +411 -0
- package/thru-ts-client-sdk/transactions/__tests__/utils.test.ts +214 -0
- package/thru-ts-client-sdk/transactions/types.ts +14 -4
- package/vitest.config.ts +31 -0
- package/dist/chunk-FDZQR6ZZ.js.map +0 -1
- package/thru-ts-client-sdk/transactions/__tests__/transaction.test.ts +0 -95
|
@@ -0,0 +1,411 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { TransactionBuilder } from "../TransactionBuilder";
|
|
3
|
+
import { Transaction } from "../Transaction";
|
|
4
|
+
import { generateTestPubkey, generateTestAddress } from "../../__tests__/helpers/test-utils";
|
|
5
|
+
import type { BuildTransactionParams } from "../types";
|
|
6
|
+
|
|
7
|
+
describe("TransactionBuilder", () => {
|
|
8
|
+
const createBuilder = () => new TransactionBuilder();
|
|
9
|
+
|
|
10
|
+
const createMinimalParams = (): BuildTransactionParams => {
|
|
11
|
+
return {
|
|
12
|
+
feePayer: {
|
|
13
|
+
publicKey: generateTestPubkey(0x01),
|
|
14
|
+
},
|
|
15
|
+
program: generateTestPubkey(0x02),
|
|
16
|
+
header: {
|
|
17
|
+
fee: 1n,
|
|
18
|
+
nonce: 2n,
|
|
19
|
+
startSlot: 3n,
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const createPrivateKey = (): Uint8Array => {
|
|
25
|
+
// Ed25519 private key is 32 bytes (seed), but we need to generate a valid one
|
|
26
|
+
// For testing, we'll use a 32-byte array
|
|
27
|
+
const privateKey = new Uint8Array(32);
|
|
28
|
+
privateKey.fill(0x42);
|
|
29
|
+
return privateKey;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
describe("build", () => {
|
|
33
|
+
it("should build transaction with minimal params", () => {
|
|
34
|
+
const builder = createBuilder();
|
|
35
|
+
const params = createMinimalParams();
|
|
36
|
+
|
|
37
|
+
const transaction = builder.build(params);
|
|
38
|
+
|
|
39
|
+
expect(transaction).toBeInstanceOf(Transaction);
|
|
40
|
+
expect(transaction.feePayer).toEqual(params.feePayer.publicKey);
|
|
41
|
+
expect(transaction.program).toEqual(params.program);
|
|
42
|
+
expect(transaction.fee).toBe(params.header.fee);
|
|
43
|
+
expect(transaction.nonce).toBe(params.header.nonce);
|
|
44
|
+
expect(transaction.startSlot).toBe(params.header.startSlot);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it("should build transaction with all header fields", () => {
|
|
48
|
+
const builder = createBuilder();
|
|
49
|
+
const params: BuildTransactionParams = {
|
|
50
|
+
...createMinimalParams(),
|
|
51
|
+
header: {
|
|
52
|
+
fee: 10n,
|
|
53
|
+
nonce: 20n,
|
|
54
|
+
startSlot: 30n,
|
|
55
|
+
expiryAfter: 100,
|
|
56
|
+
computeUnits: 300_000_000,
|
|
57
|
+
stateUnits: 10_000,
|
|
58
|
+
memoryUnits: 10_000,
|
|
59
|
+
flags: 1,
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const transaction = builder.build(params);
|
|
64
|
+
|
|
65
|
+
expect(transaction.fee).toBe(10n);
|
|
66
|
+
expect(transaction.nonce).toBe(20n);
|
|
67
|
+
expect(transaction.startSlot).toBe(30n);
|
|
68
|
+
expect(transaction.expiryAfter).toBe(100);
|
|
69
|
+
expect(transaction.requestedComputeUnits).toBe(300_000_000);
|
|
70
|
+
expect(transaction.requestedStateUnits).toBe(10_000);
|
|
71
|
+
expect(transaction.requestedMemoryUnits).toBe(10_000);
|
|
72
|
+
expect(transaction.flags).toBe(1);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it("should accept program as Uint8Array", () => {
|
|
76
|
+
const builder = createBuilder();
|
|
77
|
+
const program = generateTestPubkey(0x02);
|
|
78
|
+
const params: BuildTransactionParams = {
|
|
79
|
+
...createMinimalParams(),
|
|
80
|
+
program,
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const transaction = builder.build(params);
|
|
84
|
+
|
|
85
|
+
expect(transaction.program).toEqual(program);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it("should accept program as ta- address string", () => {
|
|
89
|
+
const builder = createBuilder();
|
|
90
|
+
const programAddress = generateTestAddress(0x02);
|
|
91
|
+
const params: BuildTransactionParams = {
|
|
92
|
+
...createMinimalParams(),
|
|
93
|
+
program: programAddress,
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
const transaction = builder.build(params);
|
|
97
|
+
|
|
98
|
+
expect(transaction.program.length).toBe(32);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it("should accept program as hex string", () => {
|
|
102
|
+
const builder = createBuilder();
|
|
103
|
+
const programBytes = generateTestPubkey(0x02);
|
|
104
|
+
const hexString = Array.from(programBytes)
|
|
105
|
+
.map(b => b.toString(16).padStart(2, "0"))
|
|
106
|
+
.join("");
|
|
107
|
+
const params: BuildTransactionParams = {
|
|
108
|
+
...createMinimalParams(),
|
|
109
|
+
program: hexString,
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
const transaction = builder.build(params);
|
|
113
|
+
|
|
114
|
+
expect(transaction.program).toEqual(programBytes);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it("should normalize and sort accounts", () => {
|
|
118
|
+
const builder = createBuilder();
|
|
119
|
+
const account1 = generateTestPubkey(0x03);
|
|
120
|
+
const account2 = generateTestPubkey(0x04);
|
|
121
|
+
const account3 = generateTestPubkey(0x02);
|
|
122
|
+
|
|
123
|
+
const params: BuildTransactionParams = {
|
|
124
|
+
...createMinimalParams(),
|
|
125
|
+
accounts: {
|
|
126
|
+
readWriteAccounts: [account1, account2, account3],
|
|
127
|
+
},
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
const transaction = builder.build(params);
|
|
131
|
+
|
|
132
|
+
expect(transaction.readWriteAccounts).toHaveLength(3);
|
|
133
|
+
// Should be sorted
|
|
134
|
+
expect(transaction.readWriteAccounts[0]).toEqual(account3); // 0x02
|
|
135
|
+
expect(transaction.readWriteAccounts[1]).toEqual(account1); // 0x03
|
|
136
|
+
expect(transaction.readWriteAccounts[2]).toEqual(account2); // 0x04
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it("should deduplicate accounts", () => {
|
|
140
|
+
const builder = createBuilder();
|
|
141
|
+
const account1 = generateTestPubkey(0x03);
|
|
142
|
+
const account1Duplicate = new Uint8Array(account1);
|
|
143
|
+
|
|
144
|
+
const params: BuildTransactionParams = {
|
|
145
|
+
...createMinimalParams(),
|
|
146
|
+
accounts: {
|
|
147
|
+
readWriteAccounts: [account1, account1Duplicate],
|
|
148
|
+
},
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
const transaction = builder.build(params);
|
|
152
|
+
|
|
153
|
+
expect(transaction.readWriteAccounts).toHaveLength(1);
|
|
154
|
+
expect(transaction.readWriteAccounts[0]).toEqual(account1);
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it("should handle read-only accounts", () => {
|
|
158
|
+
const builder = createBuilder();
|
|
159
|
+
const readOnlyAccount = generateTestPubkey(0x05);
|
|
160
|
+
|
|
161
|
+
const params: BuildTransactionParams = {
|
|
162
|
+
...createMinimalParams(),
|
|
163
|
+
accounts: {
|
|
164
|
+
readOnlyAccounts: [readOnlyAccount],
|
|
165
|
+
},
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
const transaction = builder.build(params);
|
|
169
|
+
|
|
170
|
+
expect(transaction.readOnlyAccounts).toHaveLength(1);
|
|
171
|
+
expect(transaction.readOnlyAccounts[0]).toEqual(readOnlyAccount);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it("should handle both read-write and read-only accounts", () => {
|
|
175
|
+
const builder = createBuilder();
|
|
176
|
+
const rwAccount = generateTestPubkey(0x03);
|
|
177
|
+
const roAccount = generateTestPubkey(0x04);
|
|
178
|
+
|
|
179
|
+
const params: BuildTransactionParams = {
|
|
180
|
+
...createMinimalParams(),
|
|
181
|
+
accounts: {
|
|
182
|
+
readWriteAccounts: [rwAccount],
|
|
183
|
+
readOnlyAccounts: [roAccount],
|
|
184
|
+
},
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
const transaction = builder.build(params);
|
|
188
|
+
|
|
189
|
+
expect(transaction.readWriteAccounts).toHaveLength(1);
|
|
190
|
+
expect(transaction.readOnlyAccounts).toHaveLength(1);
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
it("should handle instruction data as Uint8Array", () => {
|
|
194
|
+
const builder = createBuilder();
|
|
195
|
+
const instructionData = new Uint8Array([0x01, 0x02, 0x03]);
|
|
196
|
+
|
|
197
|
+
const params: BuildTransactionParams = {
|
|
198
|
+
...createMinimalParams(),
|
|
199
|
+
instructionData,
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
const transaction = builder.build(params);
|
|
203
|
+
|
|
204
|
+
expect(transaction.instructionData).toEqual(instructionData);
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
it("should handle instruction data as hex string", () => {
|
|
208
|
+
const builder = createBuilder();
|
|
209
|
+
const instructionBytes = new Uint8Array([0x01, 0x02, 0x03]);
|
|
210
|
+
const hexString = Array.from(instructionBytes)
|
|
211
|
+
.map(b => b.toString(16).padStart(2, "0"))
|
|
212
|
+
.join("");
|
|
213
|
+
|
|
214
|
+
const params: BuildTransactionParams = {
|
|
215
|
+
...createMinimalParams(),
|
|
216
|
+
instructionData: hexString,
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
const transaction = builder.build(params);
|
|
220
|
+
|
|
221
|
+
expect(transaction.instructionData).toEqual(instructionBytes);
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
it("should set fee payer proof flag when proof is provided", () => {
|
|
225
|
+
const builder = createBuilder();
|
|
226
|
+
const proof = new Uint8Array(64);
|
|
227
|
+
proof.fill(0x42);
|
|
228
|
+
|
|
229
|
+
const params: BuildTransactionParams = {
|
|
230
|
+
...createMinimalParams(),
|
|
231
|
+
header: {
|
|
232
|
+
fee: 1n,
|
|
233
|
+
nonce: 2n,
|
|
234
|
+
startSlot: 3n,
|
|
235
|
+
flags: 0,
|
|
236
|
+
},
|
|
237
|
+
proofs: {
|
|
238
|
+
feePayerStateProof: proof,
|
|
239
|
+
},
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
const transaction = builder.build(params);
|
|
243
|
+
|
|
244
|
+
// Flag should be set (FLAG_HAS_FEE_PAYER_PROOF = 1 << 0 = 1)
|
|
245
|
+
expect(transaction.flags & 1).toBe(1);
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
it("should not set fee payer proof flag when proof is not provided", () => {
|
|
249
|
+
const builder = createBuilder();
|
|
250
|
+
const params: BuildTransactionParams = {
|
|
251
|
+
...createMinimalParams(),
|
|
252
|
+
header: {
|
|
253
|
+
fee: 1n,
|
|
254
|
+
nonce: 2n,
|
|
255
|
+
startSlot: 3n,
|
|
256
|
+
flags: 0,
|
|
257
|
+
},
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
const transaction = builder.build(params);
|
|
261
|
+
|
|
262
|
+
expect(transaction.flags & 1).toBe(0);
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
it("should preserve custom flags when proof is provided", () => {
|
|
266
|
+
const builder = createBuilder();
|
|
267
|
+
const proof = new Uint8Array(64);
|
|
268
|
+
proof.fill(0x42);
|
|
269
|
+
|
|
270
|
+
const params: BuildTransactionParams = {
|
|
271
|
+
...createMinimalParams(),
|
|
272
|
+
header: {
|
|
273
|
+
fee: 1n,
|
|
274
|
+
nonce: 2n,
|
|
275
|
+
startSlot: 3n,
|
|
276
|
+
flags: 2, // Custom flag
|
|
277
|
+
},
|
|
278
|
+
proofs: {
|
|
279
|
+
feePayerStateProof: proof,
|
|
280
|
+
},
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
const transaction = builder.build(params);
|
|
284
|
+
|
|
285
|
+
// Should have both custom flag (2) and proof flag (1) = 3
|
|
286
|
+
expect(transaction.flags).toBe(3);
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
it("should store fee payer state proof", () => {
|
|
290
|
+
const builder = createBuilder();
|
|
291
|
+
const proof = new Uint8Array(64);
|
|
292
|
+
proof.fill(0x42);
|
|
293
|
+
|
|
294
|
+
const params: BuildTransactionParams = {
|
|
295
|
+
...createMinimalParams(),
|
|
296
|
+
proofs: {
|
|
297
|
+
feePayerStateProof: proof,
|
|
298
|
+
},
|
|
299
|
+
};
|
|
300
|
+
|
|
301
|
+
const transaction = builder.build(params);
|
|
302
|
+
|
|
303
|
+
expect(transaction.feePayerStateProof).toEqual(proof);
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
it("should store fee payer account meta raw", () => {
|
|
307
|
+
const builder = createBuilder();
|
|
308
|
+
const metaRaw = new Uint8Array(32);
|
|
309
|
+
metaRaw.fill(0x43);
|
|
310
|
+
|
|
311
|
+
const params: BuildTransactionParams = {
|
|
312
|
+
...createMinimalParams(),
|
|
313
|
+
proofs: {
|
|
314
|
+
feePayerAccountMetaRaw: metaRaw,
|
|
315
|
+
},
|
|
316
|
+
};
|
|
317
|
+
|
|
318
|
+
const transaction = builder.build(params);
|
|
319
|
+
|
|
320
|
+
expect(transaction.feePayerAccountMetaRaw).toEqual(metaRaw);
|
|
321
|
+
});
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
describe("buildAndSign", () => {
|
|
325
|
+
it("should build and sign transaction", async () => {
|
|
326
|
+
const builder = createBuilder();
|
|
327
|
+
const privateKey = createPrivateKey();
|
|
328
|
+
const publicKey = generateTestPubkey(0x01);
|
|
329
|
+
const params: BuildTransactionParams = {
|
|
330
|
+
feePayer: {
|
|
331
|
+
publicKey,
|
|
332
|
+
privateKey,
|
|
333
|
+
},
|
|
334
|
+
program: generateTestPubkey(0x02),
|
|
335
|
+
header: {
|
|
336
|
+
fee: 1n,
|
|
337
|
+
nonce: 2n,
|
|
338
|
+
startSlot: 3n,
|
|
339
|
+
},
|
|
340
|
+
};
|
|
341
|
+
|
|
342
|
+
const result = await builder.buildAndSign(params);
|
|
343
|
+
|
|
344
|
+
expect(result.transaction).toBeInstanceOf(Transaction);
|
|
345
|
+
expect(result.signature).toBeInstanceOf(Uint8Array);
|
|
346
|
+
expect(result.signature.length).toBe(64);
|
|
347
|
+
expect(result.rawTransaction).toBeInstanceOf(Uint8Array);
|
|
348
|
+
expect(result.rawTransaction.length).toBeGreaterThan(0);
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
it("should throw error when private key is missing", async () => {
|
|
352
|
+
const builder = createBuilder();
|
|
353
|
+
const params: BuildTransactionParams = {
|
|
354
|
+
...createMinimalParams(),
|
|
355
|
+
// No privateKey
|
|
356
|
+
};
|
|
357
|
+
|
|
358
|
+
await expect(builder.buildAndSign(params)).rejects.toThrow(
|
|
359
|
+
"Fee payer private key is required to sign the transaction"
|
|
360
|
+
);
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
it("should include signature in raw transaction", async () => {
|
|
364
|
+
const builder = createBuilder();
|
|
365
|
+
const privateKey = createPrivateKey();
|
|
366
|
+
const publicKey = generateTestPubkey(0x01);
|
|
367
|
+
const params: BuildTransactionParams = {
|
|
368
|
+
feePayer: {
|
|
369
|
+
publicKey,
|
|
370
|
+
privateKey,
|
|
371
|
+
},
|
|
372
|
+
program: generateTestPubkey(0x02),
|
|
373
|
+
header: {
|
|
374
|
+
fee: 1n,
|
|
375
|
+
nonce: 2n,
|
|
376
|
+
startSlot: 3n,
|
|
377
|
+
},
|
|
378
|
+
};
|
|
379
|
+
|
|
380
|
+
const result = await builder.buildAndSign(params);
|
|
381
|
+
|
|
382
|
+
// Signature should be in first 64 bytes of raw transaction
|
|
383
|
+
const signatureInRaw = result.rawTransaction.slice(0, 64);
|
|
384
|
+
expect(signatureInRaw).toEqual(result.signature);
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
it("should create valid wire format", async () => {
|
|
388
|
+
const builder = createBuilder();
|
|
389
|
+
const privateKey = createPrivateKey();
|
|
390
|
+
const publicKey = generateTestPubkey(0x01);
|
|
391
|
+
const params: BuildTransactionParams = {
|
|
392
|
+
feePayer: {
|
|
393
|
+
publicKey,
|
|
394
|
+
privateKey,
|
|
395
|
+
},
|
|
396
|
+
program: generateTestPubkey(0x02),
|
|
397
|
+
header: {
|
|
398
|
+
fee: 1n,
|
|
399
|
+
nonce: 2n,
|
|
400
|
+
startSlot: 3n,
|
|
401
|
+
},
|
|
402
|
+
};
|
|
403
|
+
|
|
404
|
+
const result = await builder.buildAndSign(params);
|
|
405
|
+
|
|
406
|
+
// Should have at least header size (176 bytes)
|
|
407
|
+
expect(result.rawTransaction.length).toBeGreaterThanOrEqual(176);
|
|
408
|
+
});
|
|
409
|
+
});
|
|
410
|
+
});
|
|
411
|
+
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import { decodeAddress } from "@thru/helpers";
|
|
2
|
+
import { describe, expect, it } from "vitest";
|
|
3
|
+
import { generateTestAddress, generateTestPubkey } from "../../__tests__/helpers/test-utils";
|
|
4
|
+
import {
|
|
5
|
+
normalizeAccountList,
|
|
6
|
+
parseAccountIdentifier,
|
|
7
|
+
parseInstructionData,
|
|
8
|
+
resolveProgramIdentifier,
|
|
9
|
+
} from "../utils";
|
|
10
|
+
|
|
11
|
+
describe("transaction utils", () => {
|
|
12
|
+
describe("normalizeAccountList", () => {
|
|
13
|
+
it("should return empty array for empty input", () => {
|
|
14
|
+
const result = normalizeAccountList([]);
|
|
15
|
+
expect(result).toEqual([]);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it("should return single account unchanged", () => {
|
|
19
|
+
const account = generateTestPubkey(0x01);
|
|
20
|
+
const result = normalizeAccountList([account]);
|
|
21
|
+
|
|
22
|
+
expect(result).toHaveLength(1);
|
|
23
|
+
expect(result[0]).toEqual(account);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it("should deduplicate accounts", () => {
|
|
27
|
+
const account1 = generateTestPubkey(0x01);
|
|
28
|
+
const account2 = generateTestPubkey(0x02);
|
|
29
|
+
const account1Duplicate = new Uint8Array(account1);
|
|
30
|
+
|
|
31
|
+
const result = normalizeAccountList([account1, account2, account1Duplicate]);
|
|
32
|
+
|
|
33
|
+
expect(result).toHaveLength(2);
|
|
34
|
+
expect(result[0]).toEqual(account1);
|
|
35
|
+
expect(result[1]).toEqual(account2);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it("should sort accounts", () => {
|
|
39
|
+
const account1 = generateTestPubkey(0x02);
|
|
40
|
+
const account2 = generateTestPubkey(0x01);
|
|
41
|
+
const account3 = generateTestPubkey(0x03);
|
|
42
|
+
|
|
43
|
+
const result = normalizeAccountList([account1, account2, account3]);
|
|
44
|
+
|
|
45
|
+
expect(result).toHaveLength(3);
|
|
46
|
+
// Should be sorted
|
|
47
|
+
expect(result[0]).toEqual(account2); // 0x01
|
|
48
|
+
expect(result[1]).toEqual(account1); // 0x02
|
|
49
|
+
expect(result[2]).toEqual(account3); // 0x03
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it("should throw error for too many accounts", () => {
|
|
53
|
+
const accounts = Array.from({ length: 1025 }, () => generateTestPubkey());
|
|
54
|
+
|
|
55
|
+
expect(() => normalizeAccountList(accounts)).toThrow("Too many accounts provided: 1025 (max 1024)");
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("should throw error for invalid account length", () => {
|
|
59
|
+
const invalidAccount = new Uint8Array(31); // Should be 32
|
|
60
|
+
|
|
61
|
+
expect(() => normalizeAccountList([invalidAccount])).toThrow("Account addresses must contain 32 bytes");
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
describe("resolveProgramIdentifier", () => {
|
|
66
|
+
it("should accept Uint8Array with 32 bytes", () => {
|
|
67
|
+
const program = generateTestPubkey(0x01);
|
|
68
|
+
const result = resolveProgramIdentifier(program);
|
|
69
|
+
|
|
70
|
+
expect(result).toEqual(program);
|
|
71
|
+
expect(result).not.toBe(program); // Should be a copy
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it("should accept ta- prefixed address string", () => {
|
|
75
|
+
const address = generateTestAddress(0x01);
|
|
76
|
+
const result = resolveProgramIdentifier(address);
|
|
77
|
+
|
|
78
|
+
expect(result.length).toBe(32);
|
|
79
|
+
const decoded = decodeAddress(address);
|
|
80
|
+
expect(result).toEqual(decoded);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it("should accept hex string", () => {
|
|
84
|
+
const programBytes = generateTestPubkey(0x01);
|
|
85
|
+
const hexString = Array.from(programBytes)
|
|
86
|
+
.map(b => b.toString(16).padStart(2, "0"))
|
|
87
|
+
.join("");
|
|
88
|
+
|
|
89
|
+
const result = resolveProgramIdentifier(hexString);
|
|
90
|
+
|
|
91
|
+
expect(result.length).toBe(32);
|
|
92
|
+
expect(result).toEqual(programBytes);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it("should throw error for Uint8Array with wrong length", () => {
|
|
96
|
+
const invalidProgram = new Uint8Array(31);
|
|
97
|
+
|
|
98
|
+
expect(() => resolveProgramIdentifier(invalidProgram)).toThrow("Program public key must contain 32 bytes");
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it("should throw error for invalid string format", () => {
|
|
102
|
+
expect(() => resolveProgramIdentifier("invalid-format")).toThrow("Unsupported program identifier format");
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it("should throw error for hex string with wrong length", () => {
|
|
106
|
+
const shortHex = "0123"; // Too short
|
|
107
|
+
|
|
108
|
+
expect(() => resolveProgramIdentifier(shortHex)).toThrow("Hex-encoded program key must contain 32 bytes");
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
describe("parseAccountIdentifier", () => {
|
|
113
|
+
it("should accept Uint8Array with 32 bytes", () => {
|
|
114
|
+
const account = generateTestPubkey(0x01);
|
|
115
|
+
const result = parseAccountIdentifier(account, "testField");
|
|
116
|
+
|
|
117
|
+
expect(result).toEqual(account);
|
|
118
|
+
expect(result).not.toBe(account); // Should be a copy
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it("should accept ta- prefixed address string", () => {
|
|
122
|
+
const address = generateTestAddress(0x01);
|
|
123
|
+
const result = parseAccountIdentifier(address, "testField");
|
|
124
|
+
|
|
125
|
+
expect(result.length).toBe(32);
|
|
126
|
+
const decoded = decodeAddress(address);
|
|
127
|
+
expect(result).toEqual(decoded);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it("should accept hex string", () => {
|
|
131
|
+
const accountBytes = generateTestPubkey(0x01);
|
|
132
|
+
const hexString = Array.from(accountBytes)
|
|
133
|
+
.map(b => b.toString(16).padStart(2, "0"))
|
|
134
|
+
.join("");
|
|
135
|
+
|
|
136
|
+
const result = parseAccountIdentifier(hexString, "testField");
|
|
137
|
+
|
|
138
|
+
expect(result.length).toBe(32);
|
|
139
|
+
expect(result).toEqual(accountBytes);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it("should throw error for Uint8Array with wrong length", () => {
|
|
143
|
+
const invalidAccount = new Uint8Array(31);
|
|
144
|
+
|
|
145
|
+
expect(() => parseAccountIdentifier(invalidAccount, "testField")).toThrow("testField must contain 32 bytes");
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it("should throw error for invalid string format", () => {
|
|
149
|
+
expect(() => parseAccountIdentifier("invalid-format", "testField")).toThrow(
|
|
150
|
+
"testField must be a 32-byte value, ta-address, or 64-character hex string"
|
|
151
|
+
);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it("should include field name in error message", () => {
|
|
155
|
+
const invalidAccount = new Uint8Array(31);
|
|
156
|
+
|
|
157
|
+
expect(() => parseAccountIdentifier(invalidAccount, "myCustomField")).toThrow("myCustomField must contain 32 bytes");
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
describe("parseInstructionData", () => {
|
|
162
|
+
it("should return undefined for undefined input", () => {
|
|
163
|
+
const result = parseInstructionData(undefined);
|
|
164
|
+
expect(result).toBeUndefined();
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it("should accept Uint8Array", () => {
|
|
168
|
+
const data = new Uint8Array([0x01, 0x02, 0x03]);
|
|
169
|
+
const result = parseInstructionData(data);
|
|
170
|
+
|
|
171
|
+
expect(result).toEqual(data);
|
|
172
|
+
expect(result).not.toBe(data); // Should be a copy
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
it("should accept empty Uint8Array", () => {
|
|
176
|
+
const data = new Uint8Array(0);
|
|
177
|
+
const result = parseInstructionData(data);
|
|
178
|
+
|
|
179
|
+
expect(result).toEqual(data);
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it("should accept hex string", () => {
|
|
183
|
+
const dataBytes = new Uint8Array([0x01, 0x02, 0x03]);
|
|
184
|
+
const hexString = Array.from(dataBytes)
|
|
185
|
+
.map(b => b.toString(16).padStart(2, "0"))
|
|
186
|
+
.join("");
|
|
187
|
+
|
|
188
|
+
const result = parseInstructionData(hexString);
|
|
189
|
+
|
|
190
|
+
expect(result).toEqual(dataBytes);
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
it("should accept empty hex string", () => {
|
|
194
|
+
const result = parseInstructionData("");
|
|
195
|
+
|
|
196
|
+
expect(result).toBeInstanceOf(Uint8Array);
|
|
197
|
+
expect(result?.length).toBe(0);
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
it("should throw error for invalid string format", () => {
|
|
201
|
+
expect(() => parseInstructionData("not-hex")).toThrow("Instruction data must be provided as hex string or Uint8Array");
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it("should handle large instruction data", () => {
|
|
205
|
+
const largeData = new Uint8Array(1000);
|
|
206
|
+
largeData.fill(0x42);
|
|
207
|
+
const result = parseInstructionData(largeData);
|
|
208
|
+
|
|
209
|
+
expect(result).toEqual(largeData);
|
|
210
|
+
expect(result?.length).toBe(1000);
|
|
211
|
+
});
|
|
212
|
+
});
|
|
213
|
+
});
|
|
214
|
+
|
|
@@ -31,11 +31,20 @@ export interface TransactionAccountsInput {
|
|
|
31
31
|
readOnlyAccounts?: AccountAddress[];
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
34
|
+
/**
|
|
35
|
+
* Builder context provided to instruction data functions.
|
|
36
|
+
* Contains all transaction accounts in their final sorted order and helper functions
|
|
37
|
+
* for looking up account indexes.
|
|
38
|
+
*/
|
|
39
|
+
export interface TransactionBuilderContext {
|
|
40
|
+
/** All accounts in final transaction order: [feePayer, program, ...readWrite, ...readOnly] */
|
|
41
|
+
accounts: AccountAddress[];
|
|
42
|
+
/** Get the index of an account by its public key. Throws if account is not found in transaction. */
|
|
43
|
+
getAccountIndex: (pubkey: AccountAddress) => number;
|
|
37
44
|
}
|
|
38
45
|
|
|
46
|
+
|
|
47
|
+
|
|
39
48
|
export interface FeePayerInput {
|
|
40
49
|
publicKey: AccountAddress;
|
|
41
50
|
privateKey?: Bytes32;
|
|
@@ -46,7 +55,8 @@ export interface BuildTransactionParams {
|
|
|
46
55
|
program: ProgramIdentifier;
|
|
47
56
|
header: TransactionHeaderInput;
|
|
48
57
|
accounts?: TransactionAccountsInput;
|
|
49
|
-
|
|
58
|
+
instructionData?: BytesLike;
|
|
59
|
+
proofs?: OptionalProofs;
|
|
50
60
|
}
|
|
51
61
|
|
|
52
62
|
export interface BuiltTransactionResult {
|
package/vitest.config.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { defineConfig } from "vitest/config";
|
|
2
|
+
|
|
3
|
+
export default defineConfig({
|
|
4
|
+
test: {
|
|
5
|
+
environment: "jsdom",
|
|
6
|
+
globals: true, // Enable describe, it, expect globally
|
|
7
|
+
include: ["thru-ts-client-sdk/**/*.{test,spec}.{ts,tsx}"],
|
|
8
|
+
exclude: [
|
|
9
|
+
"**/node_modules/**",
|
|
10
|
+
"**/dist/**",
|
|
11
|
+
"**/proto/**",
|
|
12
|
+
"**/test-scripts/**",
|
|
13
|
+
],
|
|
14
|
+
// Don't fail when no tests are found (useful during development)
|
|
15
|
+
passWithNoTests: true,
|
|
16
|
+
coverage: {
|
|
17
|
+
provider: "v8",
|
|
18
|
+
reporter: ["text", "json", "html"],
|
|
19
|
+
include: ["thru-ts-client-sdk/**/*.ts"],
|
|
20
|
+
exclude: [
|
|
21
|
+
"**/__tests__/**",
|
|
22
|
+
"**/proto/**",
|
|
23
|
+
"**/dist/**",
|
|
24
|
+
"**/test-scripts/**",
|
|
25
|
+
"**/*.test.ts",
|
|
26
|
+
"**/*.spec.ts",
|
|
27
|
+
],
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
});
|
|
31
|
+
|