@thru/thru-sdk 0.0.4
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 +7 -0
- package/buf.gen.yaml +12 -0
- package/buf.lock +9 -0
- package/buf.yaml +15 -0
- package/dist/sdk.d.ts +1354 -0
- package/dist/sdk.js +1130 -0
- package/dist/sdk.js.map +1 -0
- package/package.json +39 -0
- package/proto/thru/common/v1/consensus.proto +75 -0
- package/proto/thru/common/v1/errors.proto +67 -0
- package/proto/thru/common/v1/filters.proto +50 -0
- package/proto/thru/common/v1/pagination.proto +47 -0
- package/proto/thru/core/v1/account.proto +138 -0
- package/proto/thru/core/v1/block.proto +82 -0
- package/proto/thru/core/v1/state.proto +37 -0
- package/proto/thru/core/v1/transaction.proto +95 -0
- package/proto/thru/core/v1/types.proto +52 -0
- package/proto/thru/services/v1/command_service.proto +45 -0
- package/proto/thru/services/v1/query_service.proto +344 -0
- package/proto/thru/services/v1/streaming_service.proto +128 -0
- package/thru-ts-client-sdk/core/bound-client.ts +129 -0
- package/thru-ts-client-sdk/core/client.ts +38 -0
- package/thru-ts-client-sdk/counter.ts +216 -0
- package/thru-ts-client-sdk/create-account.ts +78 -0
- package/thru-ts-client-sdk/defaults.ts +17 -0
- package/thru-ts-client-sdk/get-height.ts +52 -0
- package/thru-ts-client-sdk/modules/accounts.ts +137 -0
- package/thru-ts-client-sdk/modules/blocks.ts +75 -0
- package/thru-ts-client-sdk/modules/events.ts +20 -0
- package/thru-ts-client-sdk/modules/height.ts +9 -0
- package/thru-ts-client-sdk/modules/helpers.ts +340 -0
- package/thru-ts-client-sdk/modules/proofs.ts +20 -0
- package/thru-ts-client-sdk/modules/streaming.ts +34 -0
- package/thru-ts-client-sdk/modules/transactions.ts +274 -0
- package/thru-ts-client-sdk/proto/buf/validate/validate_pb.ts +4761 -0
- package/thru-ts-client-sdk/proto/google/api/annotations_pb.ts +39 -0
- package/thru-ts-client-sdk/proto/google/api/client_pb.ts +953 -0
- package/thru-ts-client-sdk/proto/google/api/field_behavior_pb.ts +157 -0
- package/thru-ts-client-sdk/proto/google/api/http_pb.ts +474 -0
- package/thru-ts-client-sdk/proto/google/api/launch_stage_pb.ts +118 -0
- package/thru-ts-client-sdk/proto/thru/common/v1/consensus_pb.ts +163 -0
- package/thru-ts-client-sdk/proto/thru/common/v1/errors_pb.ts +130 -0
- package/thru-ts-client-sdk/proto/thru/common/v1/filters_pb.ts +81 -0
- package/thru-ts-client-sdk/proto/thru/common/v1/pagination_pb.ts +80 -0
- package/thru-ts-client-sdk/proto/thru/core/v1/account_pb.ts +358 -0
- package/thru-ts-client-sdk/proto/thru/core/v1/block_pb.ts +260 -0
- package/thru-ts-client-sdk/proto/thru/core/v1/state_pb.ts +104 -0
- package/thru-ts-client-sdk/proto/thru/core/v1/transaction_pb.ts +327 -0
- package/thru-ts-client-sdk/proto/thru/core/v1/types_pb.ts +101 -0
- package/thru-ts-client-sdk/proto/thru/services/v1/command_service_pb.ts +81 -0
- package/thru-ts-client-sdk/proto/thru/services/v1/query_service_pb.ts +813 -0
- package/thru-ts-client-sdk/proto/thru/services/v1/streaming_service_pb.ts +391 -0
- package/thru-ts-client-sdk/sdk.ts +58 -0
- package/thru-ts-client-sdk/transactions/Transaction.ts +240 -0
- package/thru-ts-client-sdk/transactions/TransactionBuilder.ts +48 -0
- package/thru-ts-client-sdk/transactions/__tests__/transaction.test.ts +95 -0
- package/thru-ts-client-sdk/transactions/index.ts +3 -0
- package/thru-ts-client-sdk/transactions/types.ts +64 -0
- package/thru-ts-client-sdk/transactions/utils.ts +134 -0
- package/thru-ts-client-sdk/types/types.ts +8 -0
- package/thru-ts-client-sdk/utils/utils.ts +70 -0
- package/tsconfig.json +9 -0
- package/tsup.config.ts +10 -0
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
import { create } from "@bufbuild/protobuf";
|
|
2
|
+
import { sha256 } from "@noble/hashes/sha256";
|
|
3
|
+
import { BlockHash, BlockHashSchema, Pubkey, PubkeySchema, Signature, SignatureSchema } from "../proto/thru/core/v1/types_pb";
|
|
4
|
+
import { decodeBase64, ensureBytes, hexToBytes, isHexString, maskForBits } from "../utils/utils";
|
|
5
|
+
|
|
6
|
+
const BASE64_URL_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
|
|
7
|
+
const BASE64_URL_MAP = new Int16Array(256).fill(-1);
|
|
8
|
+
for (let i = 0; i < BASE64_URL_ALPHABET.length; i++) {
|
|
9
|
+
BASE64_URL_MAP[BASE64_URL_ALPHABET.charCodeAt(i)] = i;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export type BytesLike = string | Uint8Array;
|
|
13
|
+
export type BlockSelector = { slot: number | bigint } | { blockHash: BytesLike };
|
|
14
|
+
|
|
15
|
+
export function toSignature(value: BytesLike): Signature {
|
|
16
|
+
let bytes: Uint8Array;
|
|
17
|
+
if (value instanceof Uint8Array) {
|
|
18
|
+
if (value.length !== 64) {
|
|
19
|
+
throw new Error("signature must contain 64 bytes");
|
|
20
|
+
}
|
|
21
|
+
bytes = value;
|
|
22
|
+
} else if (typeof value === "string") {
|
|
23
|
+
bytes = value.startsWith("ts") ? decodeSignature(value) : decodeBase64(value);
|
|
24
|
+
} else {
|
|
25
|
+
throw new Error("signature is required");
|
|
26
|
+
}
|
|
27
|
+
if (bytes.length !== 64) {
|
|
28
|
+
throw new Error("signature must contain 64 bytes");
|
|
29
|
+
}
|
|
30
|
+
return create(SignatureSchema, { value: bytes });
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function toPubkey(value: BytesLike, field: string): Pubkey {
|
|
34
|
+
let bytes: Uint8Array;
|
|
35
|
+
if (value instanceof Uint8Array) {
|
|
36
|
+
bytes = value;
|
|
37
|
+
} else if (typeof value === "string") {
|
|
38
|
+
bytes = value.startsWith("ta") ? decodeAddress(value) : decodeBase64(value);
|
|
39
|
+
} else {
|
|
40
|
+
throw new Error(`${field} is required`);
|
|
41
|
+
}
|
|
42
|
+
if (bytes.length !== 32) {
|
|
43
|
+
throw new Error(`${field} must contain 32 bytes`);
|
|
44
|
+
}
|
|
45
|
+
return create(PubkeySchema, { value: bytes });
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function toBlockHash(value: BytesLike): BlockHash {
|
|
49
|
+
return create(BlockHashSchema, { value: ensureBytes(value, "blockHash") });
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function encodeSignature(bytes: Uint8Array): string {
|
|
53
|
+
if (bytes.length !== 64) {
|
|
54
|
+
throw new Error("Expected 64-byte signature");
|
|
55
|
+
}
|
|
56
|
+
let checksum = 0;
|
|
57
|
+
let accumulator = 0;
|
|
58
|
+
let bitsCollected = 0;
|
|
59
|
+
const output: string[] = ["t", "s"];
|
|
60
|
+
|
|
61
|
+
for (let i = 0; i < 63; i++) {
|
|
62
|
+
const byte = bytes[i];
|
|
63
|
+
checksum += byte;
|
|
64
|
+
accumulator = ((accumulator << 8) | byte) >>> 0;
|
|
65
|
+
bitsCollected += 8;
|
|
66
|
+
while (bitsCollected >= 6) {
|
|
67
|
+
const index = (accumulator >> (bitsCollected - 6)) & 0x3f;
|
|
68
|
+
output.push(BASE64_URL_ALPHABET[index]);
|
|
69
|
+
bitsCollected -= 6;
|
|
70
|
+
accumulator &= maskForBits(bitsCollected);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const lastByte = bytes[63];
|
|
75
|
+
checksum += lastByte;
|
|
76
|
+
accumulator = ((accumulator << 8) | lastByte) >>> 0;
|
|
77
|
+
bitsCollected += 8;
|
|
78
|
+
accumulator = ((accumulator << 16) | (checksum & 0xffff)) >>> 0;
|
|
79
|
+
bitsCollected += 16;
|
|
80
|
+
|
|
81
|
+
while (bitsCollected >= 6) {
|
|
82
|
+
const index = (accumulator >> (bitsCollected - 6)) & 0x3f;
|
|
83
|
+
output.push(BASE64_URL_ALPHABET[index]);
|
|
84
|
+
bitsCollected -= 6;
|
|
85
|
+
accumulator &= maskForBits(bitsCollected);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return output.join("");
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function decodeSignature(value: string): Uint8Array {
|
|
92
|
+
if (value.length !== 90) {
|
|
93
|
+
throw new Error("Invalid signature length");
|
|
94
|
+
}
|
|
95
|
+
if (!value.startsWith("ts")) {
|
|
96
|
+
throw new Error('Signature must start with "ts"');
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const output = new Uint8Array(64);
|
|
100
|
+
let checksum = 0;
|
|
101
|
+
let inIdx = 2;
|
|
102
|
+
let remaining = 84;
|
|
103
|
+
let outIdx = 0;
|
|
104
|
+
|
|
105
|
+
while (remaining > 0) {
|
|
106
|
+
const a = BASE64_URL_MAP[value.charCodeAt(inIdx)];
|
|
107
|
+
const b = BASE64_URL_MAP[value.charCodeAt(inIdx + 1)];
|
|
108
|
+
const c = BASE64_URL_MAP[value.charCodeAt(inIdx + 2)];
|
|
109
|
+
const d = BASE64_URL_MAP[value.charCodeAt(inIdx + 3)];
|
|
110
|
+
if (a < 0 || b < 0 || c < 0 || d < 0) {
|
|
111
|
+
throw new Error("Invalid signature encoding");
|
|
112
|
+
}
|
|
113
|
+
const triple = (a << 18) | (b << 12) | (c << 6) | d;
|
|
114
|
+
const byte1 = (triple >> 16) & 0xff;
|
|
115
|
+
const byte2 = (triple >> 8) & 0xff;
|
|
116
|
+
const byte3 = triple & 0xff;
|
|
117
|
+
checksum += byte1;
|
|
118
|
+
checksum += byte2;
|
|
119
|
+
checksum += byte3;
|
|
120
|
+
output[outIdx++] = byte1;
|
|
121
|
+
output[outIdx++] = byte2;
|
|
122
|
+
output[outIdx++] = byte3;
|
|
123
|
+
inIdx += 4;
|
|
124
|
+
remaining -= 4;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const a = BASE64_URL_MAP[value.charCodeAt(inIdx)];
|
|
128
|
+
const b = BASE64_URL_MAP[value.charCodeAt(inIdx + 1)];
|
|
129
|
+
const c = BASE64_URL_MAP[value.charCodeAt(inIdx + 2)];
|
|
130
|
+
const d = BASE64_URL_MAP[value.charCodeAt(inIdx + 3)];
|
|
131
|
+
if (a < 0 || b < 0 || c < 0 || d < 0) {
|
|
132
|
+
throw new Error("Invalid signature encoding");
|
|
133
|
+
}
|
|
134
|
+
const triple = (a << 18) | (b << 12) | (c << 6) | d;
|
|
135
|
+
const finalByte = (triple >> 16) & 0xff;
|
|
136
|
+
checksum += finalByte;
|
|
137
|
+
output[outIdx] = finalByte;
|
|
138
|
+
|
|
139
|
+
const incomingChecksum = triple & 0xffff;
|
|
140
|
+
checksum &= 0xffff;
|
|
141
|
+
if (checksum !== incomingChecksum) {
|
|
142
|
+
throw new Error("Signature checksum mismatch");
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return output;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export function encodeAddress(bytes: Uint8Array): string {
|
|
149
|
+
if (bytes.length !== 32) {
|
|
150
|
+
throw new Error("Expected 32-byte address");
|
|
151
|
+
}
|
|
152
|
+
let checksum = 0;
|
|
153
|
+
let accumulator = 0;
|
|
154
|
+
let bitsCollected = 0;
|
|
155
|
+
const output: string[] = ["t", "a"];
|
|
156
|
+
|
|
157
|
+
for (let i = 0; i < 30; i++) {
|
|
158
|
+
const byte = bytes[i];
|
|
159
|
+
checksum += byte;
|
|
160
|
+
accumulator = ((accumulator << 8) | byte) >>> 0;
|
|
161
|
+
bitsCollected += 8;
|
|
162
|
+
while (bitsCollected >= 6) {
|
|
163
|
+
const index = (accumulator >> (bitsCollected - 6)) & 0x3f;
|
|
164
|
+
output.push(BASE64_URL_ALPHABET[index]);
|
|
165
|
+
bitsCollected -= 6;
|
|
166
|
+
accumulator &= maskForBits(bitsCollected);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const secondLast = bytes[30];
|
|
171
|
+
checksum += secondLast;
|
|
172
|
+
accumulator = ((accumulator << 8) | secondLast) >>> 0;
|
|
173
|
+
bitsCollected += 8;
|
|
174
|
+
|
|
175
|
+
const last = bytes[31];
|
|
176
|
+
checksum += last;
|
|
177
|
+
accumulator = ((accumulator << 8) | last) >>> 0;
|
|
178
|
+
bitsCollected += 8;
|
|
179
|
+
|
|
180
|
+
accumulator = ((accumulator << 8) | (checksum & 0xff)) >>> 0;
|
|
181
|
+
bitsCollected += 8;
|
|
182
|
+
|
|
183
|
+
while (bitsCollected >= 6) {
|
|
184
|
+
const index = (accumulator >> (bitsCollected - 6)) & 0x3f;
|
|
185
|
+
output.push(BASE64_URL_ALPHABET[index]);
|
|
186
|
+
bitsCollected -= 6;
|
|
187
|
+
accumulator &= maskForBits(bitsCollected);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return output.join("");
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
export function decodeAddress(value: string): Uint8Array {
|
|
194
|
+
if (value.length !== 46) {
|
|
195
|
+
throw new Error("Invalid address length");
|
|
196
|
+
}
|
|
197
|
+
if (!value.startsWith("ta")) {
|
|
198
|
+
throw new Error('Address must start with "ta"');
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const output = new Uint8Array(32);
|
|
202
|
+
let checksum = 0;
|
|
203
|
+
let inIdx = 2;
|
|
204
|
+
let remaining = 40;
|
|
205
|
+
let outIdx = 0;
|
|
206
|
+
|
|
207
|
+
while (remaining >= 4) {
|
|
208
|
+
const a = BASE64_URL_MAP[value.charCodeAt(inIdx)];
|
|
209
|
+
const b = BASE64_URL_MAP[value.charCodeAt(inIdx + 1)];
|
|
210
|
+
const c = BASE64_URL_MAP[value.charCodeAt(inIdx + 2)];
|
|
211
|
+
const d = BASE64_URL_MAP[value.charCodeAt(inIdx + 3)];
|
|
212
|
+
if (a < 0 || b < 0 || c < 0 || d < 0) {
|
|
213
|
+
throw new Error("Invalid address encoding");
|
|
214
|
+
}
|
|
215
|
+
const triple = (a << 18) | (b << 12) | (c << 6) | d;
|
|
216
|
+
const byte1 = (triple >> 16) & 0xff;
|
|
217
|
+
const byte2 = (triple >> 8) & 0xff;
|
|
218
|
+
const byte3 = triple & 0xff;
|
|
219
|
+
checksum += byte1;
|
|
220
|
+
checksum += byte2;
|
|
221
|
+
checksum += byte3;
|
|
222
|
+
output[outIdx++] = byte1;
|
|
223
|
+
output[outIdx++] = byte2;
|
|
224
|
+
output[outIdx++] = byte3;
|
|
225
|
+
inIdx += 4;
|
|
226
|
+
remaining -= 4;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const a = BASE64_URL_MAP[value.charCodeAt(inIdx)];
|
|
230
|
+
const b = BASE64_URL_MAP[value.charCodeAt(inIdx + 1)];
|
|
231
|
+
const c = BASE64_URL_MAP[value.charCodeAt(inIdx + 2)];
|
|
232
|
+
const d = BASE64_URL_MAP[value.charCodeAt(inIdx + 3)];
|
|
233
|
+
if (a < 0 || b < 0 || c < 0 || d < 0) {
|
|
234
|
+
throw new Error("Invalid address encoding");
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const triple = (a << 18) | (b << 12) | (c << 6) | d;
|
|
238
|
+
const byte1 = (triple >> 16) & 0xff;
|
|
239
|
+
const byte2 = (triple >> 8) & 0xff;
|
|
240
|
+
const incomingChecksum = triple & 0xff;
|
|
241
|
+
|
|
242
|
+
checksum += byte1;
|
|
243
|
+
checksum += byte2;
|
|
244
|
+
output[outIdx++] = byte1;
|
|
245
|
+
output[outIdx++] = byte2;
|
|
246
|
+
|
|
247
|
+
checksum &= 0xff;
|
|
248
|
+
if (checksum !== incomingChecksum) {
|
|
249
|
+
throw new Error("Address checksum mismatch");
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
return output;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
export interface DeriveProgramAddressOptions {
|
|
256
|
+
programAddress: BytesLike;
|
|
257
|
+
seed: BytesLike;
|
|
258
|
+
ephemeral?: boolean;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
export interface DeriveProgramAddressResult {
|
|
262
|
+
bytes: Uint8Array;
|
|
263
|
+
address: string;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
export function deriveProgramAddress(options: DeriveProgramAddressOptions): DeriveProgramAddressResult {
|
|
267
|
+
const programAddress = normalizeProgramAddress(options.programAddress);
|
|
268
|
+
const seed = normalizeSeed(options.seed);
|
|
269
|
+
const ephemeral = options.ephemeral === true;
|
|
270
|
+
|
|
271
|
+
const derivationInput = new Uint8Array(programAddress.length + 1 + seed.length);
|
|
272
|
+
derivationInput.set(programAddress, 0);
|
|
273
|
+
derivationInput[programAddress.length] = ephemeral ? 1 : 0;
|
|
274
|
+
derivationInput.set(seed, programAddress.length + 1);
|
|
275
|
+
|
|
276
|
+
const hash = sha256(derivationInput);
|
|
277
|
+
const derivedBytes = new Uint8Array(hash.slice(0, 32));
|
|
278
|
+
|
|
279
|
+
return {
|
|
280
|
+
bytes: derivedBytes,
|
|
281
|
+
address: encodeAddress(derivedBytes),
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function normalizeProgramAddress(value: BytesLike): Uint8Array {
|
|
286
|
+
if (value instanceof Uint8Array) {
|
|
287
|
+
if (value.length !== 32) {
|
|
288
|
+
throw new Error("Program address must contain 32 bytes");
|
|
289
|
+
}
|
|
290
|
+
return new Uint8Array(value);
|
|
291
|
+
}
|
|
292
|
+
if (typeof value === "string") {
|
|
293
|
+
if (value.startsWith("ta") && value.length === 46) {
|
|
294
|
+
return decodeAddress(value);
|
|
295
|
+
}
|
|
296
|
+
if (isHexString(value)) {
|
|
297
|
+
const bytes = hexToBytes(value);
|
|
298
|
+
if (bytes.length !== 32) {
|
|
299
|
+
throw new Error("Program address hex string must decode to 32 bytes");
|
|
300
|
+
}
|
|
301
|
+
return bytes;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
throw new Error("Program address must be a 32-byte value, ta-address, or 64-character hex string");
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
function normalizeSeed(value: BytesLike | string): Uint8Array {
|
|
308
|
+
if (value instanceof Uint8Array) {
|
|
309
|
+
if (value.length === 0) {
|
|
310
|
+
throw new Error("Seed cannot be empty");
|
|
311
|
+
}
|
|
312
|
+
if (value.length > 32) {
|
|
313
|
+
throw new Error("Seed cannot exceed 32 bytes");
|
|
314
|
+
}
|
|
315
|
+
const seed = new Uint8Array(32);
|
|
316
|
+
seed.set(value);
|
|
317
|
+
return seed;
|
|
318
|
+
}
|
|
319
|
+
if (typeof value === "string") {
|
|
320
|
+
if (value.length === 0) {
|
|
321
|
+
throw new Error("Seed cannot be empty");
|
|
322
|
+
}
|
|
323
|
+
if (isHexString(value)) {
|
|
324
|
+
const bytes = hexToBytes(value);
|
|
325
|
+
if (bytes.length !== 32) {
|
|
326
|
+
throw new Error(`Hex seed must decode to 32 bytes, got ${bytes.length}`);
|
|
327
|
+
}
|
|
328
|
+
return bytes;
|
|
329
|
+
}
|
|
330
|
+
const encoder = new TextEncoder();
|
|
331
|
+
const utf8 = encoder.encode(value);
|
|
332
|
+
if (utf8.length > 32) {
|
|
333
|
+
throw new Error(`UTF-8 seed too long: ${utf8.length} bytes (max 32)`);
|
|
334
|
+
}
|
|
335
|
+
const seed = new Uint8Array(32);
|
|
336
|
+
seed.set(utf8);
|
|
337
|
+
return seed;
|
|
338
|
+
}
|
|
339
|
+
throw new Error("Seed must be provided as Uint8Array or string");
|
|
340
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { create } from "@bufbuild/protobuf";
|
|
2
|
+
|
|
3
|
+
import type { ThruClientContext } from "../core/client";
|
|
4
|
+
import { StateProofRequestSchema } from "../proto/thru/core/v1/state_pb";
|
|
5
|
+
import { GenerateStateProofRequestSchema, GenerateStateProofResponse } from "../proto/thru/services/v1/query_service_pb";
|
|
6
|
+
import { GenerateStateProofOptions } from "../types/types";
|
|
7
|
+
import { toPubkey } from "./helpers";
|
|
8
|
+
|
|
9
|
+
export function generateStateProof(
|
|
10
|
+
ctx: ThruClientContext,
|
|
11
|
+
options: GenerateStateProofOptions,
|
|
12
|
+
): Promise<GenerateStateProofResponse> {
|
|
13
|
+
const request = create(StateProofRequestSchema, {
|
|
14
|
+
address: options.address ? toPubkey(options.address, "address") : undefined,
|
|
15
|
+
proofType: options.proofType,
|
|
16
|
+
targetSlot: options.targetSlot,
|
|
17
|
+
});
|
|
18
|
+
const schemaRequest = create(GenerateStateProofRequestSchema, { request });
|
|
19
|
+
return ctx.query.generateStateProof(schemaRequest);
|
|
20
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { create } from "@bufbuild/protobuf";
|
|
2
|
+
|
|
3
|
+
import type { ThruClientContext } from "../core/client";
|
|
4
|
+
import type { TrackTransactionResponse } from "../proto/thru/services/v1/streaming_service_pb";
|
|
5
|
+
import { TrackTransactionRequestSchema } from "../proto/thru/services/v1/streaming_service_pb";
|
|
6
|
+
import type { BytesLike } from "./helpers";
|
|
7
|
+
import { toSignature as toSignatureMessage } from "./helpers";
|
|
8
|
+
|
|
9
|
+
export interface TrackTransactionOptions {
|
|
10
|
+
timeoutMs?: number;
|
|
11
|
+
signal?: AbortSignal;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function trackTransaction(
|
|
15
|
+
ctx: ThruClientContext,
|
|
16
|
+
signature: BytesLike,
|
|
17
|
+
options: TrackTransactionOptions = {},
|
|
18
|
+
): AsyncIterable<TrackTransactionResponse> {
|
|
19
|
+
const timeoutMs = options.timeoutMs;
|
|
20
|
+
const request = create(TrackTransactionRequestSchema, {
|
|
21
|
+
signature: toSignatureMessage(signature),
|
|
22
|
+
timeout:
|
|
23
|
+
timeoutMs != null
|
|
24
|
+
? {
|
|
25
|
+
seconds: BigInt(Math.floor(timeoutMs / 1000)),
|
|
26
|
+
nanos: (timeoutMs % 1000) * 1_000_000,
|
|
27
|
+
}
|
|
28
|
+
: undefined,
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
return ctx.streaming.trackTransaction(request, {
|
|
32
|
+
signal: options.signal,
|
|
33
|
+
});
|
|
34
|
+
}
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
import { create } from "@bufbuild/protobuf";
|
|
2
|
+
|
|
3
|
+
import type { ThruClientContext } from "../core/client";
|
|
4
|
+
import {
|
|
5
|
+
DEFAULT_COMPUTE_UNITS,
|
|
6
|
+
DEFAULT_EXPIRY_AFTER,
|
|
7
|
+
DEFAULT_FEE,
|
|
8
|
+
DEFAULT_MEMORY_UNITS,
|
|
9
|
+
DEFAULT_MIN_CONSENSUS,
|
|
10
|
+
DEFAULT_STATE_UNITS,
|
|
11
|
+
DEFAULT_TRANSACTION_VIEW,
|
|
12
|
+
} from "../defaults";
|
|
13
|
+
import type { ConsensusStatus, VersionContext } from "../proto/thru/common/v1/consensus_pb";
|
|
14
|
+
import { AccountView } from "../proto/thru/core/v1/account_pb";
|
|
15
|
+
import { Transaction as CoreTransaction, RawTransaction, TransactionView } from "../proto/thru/core/v1/transaction_pb";
|
|
16
|
+
import { SendTransactionRequestSchema } from "../proto/thru/services/v1/command_service_pb";
|
|
17
|
+
import {
|
|
18
|
+
GetRawTransactionRequestSchema,
|
|
19
|
+
GetTransactionRequestSchema,
|
|
20
|
+
GetTransactionStatusRequestSchema,
|
|
21
|
+
TransactionStatus,
|
|
22
|
+
} from "../proto/thru/services/v1/query_service_pb";
|
|
23
|
+
import {
|
|
24
|
+
Transaction as LocalTransaction,
|
|
25
|
+
TransactionBuilder,
|
|
26
|
+
type BuildTransactionParams,
|
|
27
|
+
type OptionalProofs,
|
|
28
|
+
type ProgramIdentifier,
|
|
29
|
+
type SignedTransactionResult,
|
|
30
|
+
type TransactionAccountsInput,
|
|
31
|
+
type TransactionContentInput,
|
|
32
|
+
type TransactionHeaderInput,
|
|
33
|
+
} from "../transactions";
|
|
34
|
+
import { parseAccountIdentifier, parseInstructionData } from "../transactions/utils";
|
|
35
|
+
import type { BytesLike } from "./helpers";
|
|
36
|
+
import { encodeSignature, toSignature as toSignatureMessage } from "./helpers";
|
|
37
|
+
|
|
38
|
+
import { getAccount } from "./accounts";
|
|
39
|
+
import { getBlockHeight } from "./height";
|
|
40
|
+
|
|
41
|
+
export interface TransactionFeePayerConfig {
|
|
42
|
+
publicKey: BytesLike;
|
|
43
|
+
privateKey?: Uint8Array;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface TransactionAccountsConfig {
|
|
47
|
+
readWrite?: BytesLike[];
|
|
48
|
+
readOnly?: BytesLike[];
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface TransactionContentConfig {
|
|
52
|
+
instructions?: BytesLike;
|
|
53
|
+
feePayerStateProof?: Uint8Array;
|
|
54
|
+
feePayerAccountMetaRaw?: Uint8Array;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export interface TransactionHeaderConfig {
|
|
58
|
+
fee?: bigint;
|
|
59
|
+
nonce?: bigint;
|
|
60
|
+
startSlot?: bigint;
|
|
61
|
+
expiryAfter?: number;
|
|
62
|
+
computeUnits?: number;
|
|
63
|
+
stateUnits?: number;
|
|
64
|
+
memoryUnits?: number;
|
|
65
|
+
flags?: number;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export interface BuildTransactionOptions {
|
|
69
|
+
feePayer: TransactionFeePayerConfig;
|
|
70
|
+
program: ProgramIdentifier;
|
|
71
|
+
header?: TransactionHeaderConfig;
|
|
72
|
+
accounts?: TransactionAccountsConfig;
|
|
73
|
+
content?: TransactionContentConfig;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export interface BuildAndSignTransactionOptions extends BuildTransactionOptions {
|
|
77
|
+
feePayer: TransactionFeePayerConfig & { privateKey: Uint8Array };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export interface TransactionQueryOptions {
|
|
81
|
+
view?: TransactionView;
|
|
82
|
+
versionContext?: VersionContext;
|
|
83
|
+
minConsensus?: ConsensusStatus;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export interface RawTransactionQueryOptions {
|
|
87
|
+
versionContext?: VersionContext;
|
|
88
|
+
minConsensus?: ConsensusStatus;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export async function getTransaction(
|
|
92
|
+
ctx: ThruClientContext,
|
|
93
|
+
signature: BytesLike,
|
|
94
|
+
options: TransactionQueryOptions = {},
|
|
95
|
+
): Promise<CoreTransaction> {
|
|
96
|
+
const request = create(GetTransactionRequestSchema, {
|
|
97
|
+
signature: toSignatureMessage(signature),
|
|
98
|
+
view: options.view ?? DEFAULT_TRANSACTION_VIEW,
|
|
99
|
+
versionContext: options.versionContext,
|
|
100
|
+
minConsensus: options.minConsensus ?? DEFAULT_MIN_CONSENSUS,
|
|
101
|
+
});
|
|
102
|
+
return ctx.query.getTransaction(request);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export async function getRawTransaction(
|
|
106
|
+
ctx: ThruClientContext,
|
|
107
|
+
signature: BytesLike,
|
|
108
|
+
options: RawTransactionQueryOptions = {},
|
|
109
|
+
): Promise<RawTransaction> {
|
|
110
|
+
const request = create(GetRawTransactionRequestSchema, {
|
|
111
|
+
signature: toSignatureMessage(signature),
|
|
112
|
+
versionContext: options.versionContext,
|
|
113
|
+
minConsensus: options.minConsensus ?? DEFAULT_MIN_CONSENSUS,
|
|
114
|
+
});
|
|
115
|
+
return ctx.query.getRawTransaction(request);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export async function getTransactionStatus(ctx: ThruClientContext, signature: BytesLike): Promise<TransactionStatus> {
|
|
119
|
+
const request = create(GetTransactionStatusRequestSchema, {
|
|
120
|
+
signature: toSignatureMessage(signature),
|
|
121
|
+
});
|
|
122
|
+
return ctx.query.getTransactionStatus(request);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export async function buildTransaction(
|
|
126
|
+
ctx: ThruClientContext,
|
|
127
|
+
options: BuildTransactionOptions,
|
|
128
|
+
): Promise<LocalTransaction> {
|
|
129
|
+
const builder = createTransactionBuilder();
|
|
130
|
+
const params = await createBuildParams(ctx, options);
|
|
131
|
+
return builder.build(params);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export async function buildAndSignTransaction(
|
|
135
|
+
ctx: ThruClientContext,
|
|
136
|
+
options: BuildAndSignTransactionOptions,
|
|
137
|
+
): Promise<SignedTransactionResult> {
|
|
138
|
+
const builder = createTransactionBuilder();
|
|
139
|
+
const params = await createBuildParams(ctx, options);
|
|
140
|
+
if (!params.feePayer.privateKey) {
|
|
141
|
+
throw new Error("Fee payer private key is required to sign the transaction");
|
|
142
|
+
}
|
|
143
|
+
return builder.buildAndSign(params);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export async function sendTransaction(
|
|
147
|
+
ctx: ThruClientContext,
|
|
148
|
+
transaction: LocalTransaction | Uint8Array,
|
|
149
|
+
): Promise<string> {
|
|
150
|
+
const raw = transaction instanceof Uint8Array ? transaction : transaction.toWire();
|
|
151
|
+
return sendRawTransaction(ctx, raw);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
async function sendRawTransaction(ctx: ThruClientContext, rawTransaction: Uint8Array): Promise<string> {
|
|
155
|
+
const request = create(SendTransactionRequestSchema, { rawTransaction });
|
|
156
|
+
const response = await ctx.command.sendTransaction(request);
|
|
157
|
+
if (!response.signature?.value) {
|
|
158
|
+
throw new Error("No signature returned from sendTransaction");
|
|
159
|
+
}
|
|
160
|
+
return encodeSignature(response.signature.value);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function createTransactionBuilder(): TransactionBuilder {
|
|
164
|
+
return new TransactionBuilder();
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
async function createBuildParams(
|
|
168
|
+
ctx: ThruClientContext,
|
|
169
|
+
options: BuildTransactionOptions,
|
|
170
|
+
): Promise<BuildTransactionParams> {
|
|
171
|
+
const feePayerPublicKey = parseAccountIdentifier(options.feePayer.publicKey, "feePayer.publicKey");
|
|
172
|
+
const header = await createTransactionHeader(ctx, options.header ?? {}, feePayerPublicKey);
|
|
173
|
+
const accounts = parseAccounts(options.accounts);
|
|
174
|
+
const content = createContent(options.content);
|
|
175
|
+
|
|
176
|
+
return {
|
|
177
|
+
feePayer: {
|
|
178
|
+
publicKey: feePayerPublicKey,
|
|
179
|
+
privateKey: options.feePayer.privateKey,
|
|
180
|
+
},
|
|
181
|
+
program: options.program,
|
|
182
|
+
header,
|
|
183
|
+
accounts,
|
|
184
|
+
content,
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
async function createTransactionHeader(
|
|
189
|
+
ctx: ThruClientContext,
|
|
190
|
+
header: TransactionHeaderConfig,
|
|
191
|
+
feePayerPublicKey: Uint8Array,
|
|
192
|
+
): Promise<TransactionHeaderInput> {
|
|
193
|
+
const nonce = header.nonce ?? (await fetchFeePayerNonce(ctx, feePayerPublicKey));
|
|
194
|
+
const startSlot = header.startSlot ?? (await fetchFinalizedSlot(ctx));
|
|
195
|
+
return {
|
|
196
|
+
fee: header.fee ?? DEFAULT_FEE,
|
|
197
|
+
nonce,
|
|
198
|
+
startSlot,
|
|
199
|
+
expiryAfter: header.expiryAfter ?? DEFAULT_EXPIRY_AFTER,
|
|
200
|
+
computeUnits: header.computeUnits ?? DEFAULT_COMPUTE_UNITS,
|
|
201
|
+
stateUnits: header.stateUnits ?? DEFAULT_STATE_UNITS,
|
|
202
|
+
memoryUnits: header.memoryUnits ?? DEFAULT_MEMORY_UNITS,
|
|
203
|
+
flags: header.flags,
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function parseAccounts(accounts?: TransactionAccountsConfig): TransactionAccountsInput | undefined {
|
|
208
|
+
if (!accounts) {
|
|
209
|
+
return undefined;
|
|
210
|
+
}
|
|
211
|
+
const readWrite = accounts.readWrite?.map((value, index) =>
|
|
212
|
+
parseAccountIdentifier(value, `accounts.readWrite[${index}]`),
|
|
213
|
+
);
|
|
214
|
+
const readOnly = accounts.readOnly?.map((value, index) =>
|
|
215
|
+
parseAccountIdentifier(value, `accounts.readOnly[${index}]`),
|
|
216
|
+
);
|
|
217
|
+
|
|
218
|
+
const result: TransactionAccountsInput = {};
|
|
219
|
+
if (readWrite && readWrite.length > 0) {
|
|
220
|
+
result.readWriteAccounts = readWrite;
|
|
221
|
+
}
|
|
222
|
+
if (readOnly && readOnly.length > 0) {
|
|
223
|
+
result.readOnlyAccounts = readOnly;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
if (!result.readWriteAccounts && !result.readOnlyAccounts) {
|
|
227
|
+
return undefined;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
return result;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function createContent(content?: TransactionContentConfig): TransactionContentInput | undefined {
|
|
234
|
+
if (!content) {
|
|
235
|
+
return undefined;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const instructions = parseInstructionData(content.instructions);
|
|
239
|
+
const proofs: OptionalProofs = {};
|
|
240
|
+
if (content.feePayerStateProof) {
|
|
241
|
+
proofs.feePayerStateProof = new Uint8Array(content.feePayerStateProof);
|
|
242
|
+
}
|
|
243
|
+
if (content.feePayerAccountMetaRaw) {
|
|
244
|
+
proofs.feePayerAccountMetaRaw = new Uint8Array(content.feePayerAccountMetaRaw);
|
|
245
|
+
}
|
|
246
|
+
const hasProofs = Boolean(proofs.feePayerStateProof || proofs.feePayerAccountMetaRaw);
|
|
247
|
+
|
|
248
|
+
if (!instructions && !hasProofs) {
|
|
249
|
+
return undefined;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
const result: TransactionContentInput = {};
|
|
253
|
+
if (instructions) {
|
|
254
|
+
result.instructions = instructions;
|
|
255
|
+
}
|
|
256
|
+
if (hasProofs) {
|
|
257
|
+
result.proofs = proofs;
|
|
258
|
+
}
|
|
259
|
+
return result;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
async function fetchFeePayerNonce(ctx: ThruClientContext, feePayer: Uint8Array): Promise<bigint> {
|
|
263
|
+
const account = await getAccount(ctx, feePayer, { view: AccountView.FULL });
|
|
264
|
+
const nonce = account.meta?.nonce;
|
|
265
|
+
if (nonce === undefined) {
|
|
266
|
+
throw new Error("Fee payer account nonce is unavailable");
|
|
267
|
+
}
|
|
268
|
+
return nonce;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
async function fetchFinalizedSlot(ctx: ThruClientContext): Promise<bigint> {
|
|
272
|
+
const height = await getBlockHeight(ctx);
|
|
273
|
+
return height.finalized;
|
|
274
|
+
}
|