@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.
Files changed (63) hide show
  1. package/README.md +7 -0
  2. package/buf.gen.yaml +12 -0
  3. package/buf.lock +9 -0
  4. package/buf.yaml +15 -0
  5. package/dist/sdk.d.ts +1354 -0
  6. package/dist/sdk.js +1130 -0
  7. package/dist/sdk.js.map +1 -0
  8. package/package.json +39 -0
  9. package/proto/thru/common/v1/consensus.proto +75 -0
  10. package/proto/thru/common/v1/errors.proto +67 -0
  11. package/proto/thru/common/v1/filters.proto +50 -0
  12. package/proto/thru/common/v1/pagination.proto +47 -0
  13. package/proto/thru/core/v1/account.proto +138 -0
  14. package/proto/thru/core/v1/block.proto +82 -0
  15. package/proto/thru/core/v1/state.proto +37 -0
  16. package/proto/thru/core/v1/transaction.proto +95 -0
  17. package/proto/thru/core/v1/types.proto +52 -0
  18. package/proto/thru/services/v1/command_service.proto +45 -0
  19. package/proto/thru/services/v1/query_service.proto +344 -0
  20. package/proto/thru/services/v1/streaming_service.proto +128 -0
  21. package/thru-ts-client-sdk/core/bound-client.ts +129 -0
  22. package/thru-ts-client-sdk/core/client.ts +38 -0
  23. package/thru-ts-client-sdk/counter.ts +216 -0
  24. package/thru-ts-client-sdk/create-account.ts +78 -0
  25. package/thru-ts-client-sdk/defaults.ts +17 -0
  26. package/thru-ts-client-sdk/get-height.ts +52 -0
  27. package/thru-ts-client-sdk/modules/accounts.ts +137 -0
  28. package/thru-ts-client-sdk/modules/blocks.ts +75 -0
  29. package/thru-ts-client-sdk/modules/events.ts +20 -0
  30. package/thru-ts-client-sdk/modules/height.ts +9 -0
  31. package/thru-ts-client-sdk/modules/helpers.ts +340 -0
  32. package/thru-ts-client-sdk/modules/proofs.ts +20 -0
  33. package/thru-ts-client-sdk/modules/streaming.ts +34 -0
  34. package/thru-ts-client-sdk/modules/transactions.ts +274 -0
  35. package/thru-ts-client-sdk/proto/buf/validate/validate_pb.ts +4761 -0
  36. package/thru-ts-client-sdk/proto/google/api/annotations_pb.ts +39 -0
  37. package/thru-ts-client-sdk/proto/google/api/client_pb.ts +953 -0
  38. package/thru-ts-client-sdk/proto/google/api/field_behavior_pb.ts +157 -0
  39. package/thru-ts-client-sdk/proto/google/api/http_pb.ts +474 -0
  40. package/thru-ts-client-sdk/proto/google/api/launch_stage_pb.ts +118 -0
  41. package/thru-ts-client-sdk/proto/thru/common/v1/consensus_pb.ts +163 -0
  42. package/thru-ts-client-sdk/proto/thru/common/v1/errors_pb.ts +130 -0
  43. package/thru-ts-client-sdk/proto/thru/common/v1/filters_pb.ts +81 -0
  44. package/thru-ts-client-sdk/proto/thru/common/v1/pagination_pb.ts +80 -0
  45. package/thru-ts-client-sdk/proto/thru/core/v1/account_pb.ts +358 -0
  46. package/thru-ts-client-sdk/proto/thru/core/v1/block_pb.ts +260 -0
  47. package/thru-ts-client-sdk/proto/thru/core/v1/state_pb.ts +104 -0
  48. package/thru-ts-client-sdk/proto/thru/core/v1/transaction_pb.ts +327 -0
  49. package/thru-ts-client-sdk/proto/thru/core/v1/types_pb.ts +101 -0
  50. package/thru-ts-client-sdk/proto/thru/services/v1/command_service_pb.ts +81 -0
  51. package/thru-ts-client-sdk/proto/thru/services/v1/query_service_pb.ts +813 -0
  52. package/thru-ts-client-sdk/proto/thru/services/v1/streaming_service_pb.ts +391 -0
  53. package/thru-ts-client-sdk/sdk.ts +58 -0
  54. package/thru-ts-client-sdk/transactions/Transaction.ts +240 -0
  55. package/thru-ts-client-sdk/transactions/TransactionBuilder.ts +48 -0
  56. package/thru-ts-client-sdk/transactions/__tests__/transaction.test.ts +95 -0
  57. package/thru-ts-client-sdk/transactions/index.ts +3 -0
  58. package/thru-ts-client-sdk/transactions/types.ts +64 -0
  59. package/thru-ts-client-sdk/transactions/utils.ts +134 -0
  60. package/thru-ts-client-sdk/types/types.ts +8 -0
  61. package/thru-ts-client-sdk/utils/utils.ts +70 -0
  62. package/tsconfig.json +9 -0
  63. package/tsup.config.ts +10 -0
@@ -0,0 +1,48 @@
1
+ import { Transaction } from "./Transaction";
2
+ import type { BuildTransactionParams, SignedTransactionResult, TransactionAccountsInput } from "./types";
3
+ import { normalizeAccountList, resolveProgramIdentifier } from "./utils";
4
+
5
+ const FLAG_HAS_FEE_PAYER_PROOF = 1 << 0;
6
+
7
+ export class TransactionBuilder {
8
+ build(params: BuildTransactionParams): Transaction {
9
+ const program = resolveProgramIdentifier(params.program);
10
+ const accounts = this.normalizeAccounts(params.accounts);
11
+ const instructions = params.content?.instructions;
12
+ const proofs = params.content?.proofs;
13
+ const baseFlags = params.header.flags ?? 0;
14
+ const flags = proofs?.feePayerStateProof ? baseFlags | FLAG_HAS_FEE_PAYER_PROOF : baseFlags;
15
+
16
+ return new Transaction({
17
+ feePayer: params.feePayer.publicKey,
18
+ program,
19
+ header: {
20
+ ...params.header,
21
+ flags,
22
+ },
23
+ accounts,
24
+ instructions,
25
+ proofs,
26
+ });
27
+ }
28
+
29
+ async buildAndSign(params: BuildTransactionParams): Promise<SignedTransactionResult> {
30
+ if (!params.feePayer.privateKey) {
31
+ throw new Error("Fee payer private key is required to sign the transaction");
32
+ }
33
+ const transaction = this.build(params);
34
+ const signature = await transaction.sign(params.feePayer.privateKey);
35
+ const rawTransaction = transaction.toWire();
36
+ return { transaction, signature, rawTransaction };
37
+ }
38
+
39
+ private normalizeAccounts(accounts?: TransactionAccountsInput): TransactionAccountsInput | undefined {
40
+ if (!accounts) {
41
+ return undefined;
42
+ }
43
+ return {
44
+ readWriteAccounts: normalizeAccountList(accounts.readWriteAccounts ?? []),
45
+ readOnlyAccounts: normalizeAccountList(accounts.readOnlyAccounts ?? []),
46
+ };
47
+ }
48
+ }
@@ -0,0 +1,95 @@
1
+ import { strict as assert } from "node:assert";
2
+ import test from "node:test";
3
+
4
+ import { Transaction } from "../Transaction";
5
+
6
+ const HEADER_WITHOUT_SIGNATURE_SIZE = 176 - 64;
7
+ const PUBKEY_LEN = 32;
8
+
9
+ function makeKey(value: number): Uint8Array {
10
+ return new Uint8Array(Array.from({ length: PUBKEY_LEN }, () => value & 0xff));
11
+ }
12
+
13
+ test("Transaction serializes header and payload correctly", () => {
14
+ const feePayer = makeKey(1);
15
+ const program = makeKey(2);
16
+ const rwAccounts = [makeKey(3), makeKey(4)];
17
+ const instructions = new Uint8Array([0xaa, 0xbb, 0xcc]);
18
+
19
+ const tx = new Transaction({
20
+ feePayer,
21
+ program,
22
+ header: {
23
+ fee: 10n,
24
+ nonce: 20n,
25
+ startSlot: 30n,
26
+ expiryAfter: 40,
27
+ computeUnits: 50,
28
+ stateUnits: 60,
29
+ memoryUnits: 70,
30
+ flags: 1,
31
+ },
32
+ accounts: {
33
+ readWriteAccounts: rwAccounts,
34
+ readOnlyAccounts: [],
35
+ },
36
+ instructions,
37
+ });
38
+
39
+ const unsignedWire = tx.toWire();
40
+ const expectedLength = 176 + rwAccounts.length * PUBKEY_LEN + instructions.length;
41
+ assert.equal(unsignedWire.length, expectedLength);
42
+ assert(unsignedWire.subarray(0, 64).every((byte) => byte === 0), "signature prefix should be zero before signing");
43
+
44
+ const forSigning = tx.toWireForSigning();
45
+ const expectedSigningLength = HEADER_WITHOUT_SIGNATURE_SIZE + rwAccounts.length * PUBKEY_LEN + instructions.length;
46
+ assert.equal(forSigning.length, expectedSigningLength);
47
+
48
+ const headerView = new DataView(forSigning.buffer, forSigning.byteOffset, HEADER_WITHOUT_SIGNATURE_SIZE);
49
+ assert.equal(headerView.getUint8(0), 1); // version
50
+ assert.equal(headerView.getUint8(1), 1); // flags
51
+ assert.equal(headerView.getUint16(2, true), rwAccounts.length);
52
+ assert.equal(headerView.getUint16(4, true), 0); // readonly count
53
+ assert.equal(headerView.getUint16(6, true), instructions.length);
54
+ assert.equal(headerView.getUint32(8, true), 50);
55
+ assert.equal(headerView.getUint16(12, true), 60);
56
+ assert.equal(headerView.getUint16(14, true), 70);
57
+ assert.equal(Number(headerView.getBigUint64(16, true)), 10);
58
+ assert.equal(Number(headerView.getBigUint64(24, true)), 20);
59
+ assert.equal(Number(headerView.getBigUint64(32, true)), 30);
60
+ assert.equal(headerView.getUint32(40, true), 40);
61
+
62
+ const rwStart = HEADER_WITHOUT_SIGNATURE_SIZE;
63
+ assert.deepEqual(forSigning.subarray(rwStart, rwStart + PUBKEY_LEN), rwAccounts[0]);
64
+ assert.deepEqual(forSigning.subarray(rwStart + PUBKEY_LEN, rwStart + PUBKEY_LEN * 2), rwAccounts[1]);
65
+
66
+ const instructionStart = rwStart + rwAccounts.length * PUBKEY_LEN;
67
+ assert.deepEqual(forSigning.subarray(instructionStart), instructions);
68
+ });
69
+
70
+ test("Transaction signing populates signature in wire output", async () => {
71
+ const feePayer = makeKey(9);
72
+ const program = makeKey(10);
73
+ const privateKey = new Uint8Array(Array.from({ length: 32 }, (_, idx) => idx + 1));
74
+
75
+ const tx = new Transaction({
76
+ feePayer,
77
+ program,
78
+ header: {
79
+ fee: 1n,
80
+ nonce: 2n,
81
+ startSlot: 3n,
82
+ },
83
+ });
84
+
85
+ const signature = await tx.sign(privateKey);
86
+ assert.equal(signature.length, 64);
87
+
88
+ const storedSignature = tx.getSignature();
89
+ assert(storedSignature);
90
+ assert.deepEqual(signature, storedSignature);
91
+
92
+ const wire = tx.toWire();
93
+ assert.deepEqual(wire.subarray(0, 64), signature);
94
+ assert.deepEqual(wire.subarray(64, 64 + 32), feePayer);
95
+ });
@@ -0,0 +1,3 @@
1
+ export * from "./Transaction";
2
+ export * from "./TransactionBuilder";
3
+ export * from "./types";
@@ -0,0 +1,64 @@
1
+ import type { BytesLike } from "../modules/helpers";
2
+
3
+ export type Bytes32 = Uint8Array;
4
+ export type Bytes64 = Uint8Array;
5
+
6
+ export type AccountAddress = Bytes32;
7
+
8
+ export type ProgramIdentifier = BytesLike;
9
+
10
+ export interface ResourceLimits {
11
+ computeUnits?: number;
12
+ stateUnits?: number;
13
+ memoryUnits?: number;
14
+ }
15
+
16
+ export interface OptionalProofs {
17
+ feePayerStateProof?: Uint8Array;
18
+ feePayerAccountMetaRaw?: Uint8Array;
19
+ }
20
+
21
+ export interface TransactionHeaderInput extends ResourceLimits {
22
+ fee: bigint;
23
+ nonce: bigint;
24
+ startSlot: bigint;
25
+ expiryAfter?: number;
26
+ flags?: number;
27
+ }
28
+
29
+ export interface TransactionAccountsInput {
30
+ readWriteAccounts?: AccountAddress[];
31
+ readOnlyAccounts?: AccountAddress[];
32
+ }
33
+
34
+ export interface TransactionContentInput {
35
+ instructions?: Uint8Array;
36
+ proofs?: OptionalProofs;
37
+ }
38
+
39
+ export interface FeePayerInput {
40
+ publicKey: AccountAddress;
41
+ privateKey?: Bytes32;
42
+ }
43
+
44
+ export interface BuildTransactionParams {
45
+ feePayer: FeePayerInput;
46
+ program: ProgramIdentifier;
47
+ header: TransactionHeaderInput;
48
+ accounts?: TransactionAccountsInput;
49
+ content?: TransactionContentInput;
50
+ }
51
+
52
+ export interface BuiltTransactionResult {
53
+ transaction: TransactionLike;
54
+ }
55
+
56
+ export interface SignedTransactionResult extends BuiltTransactionResult {
57
+ signature: Bytes64;
58
+ rawTransaction: Uint8Array;
59
+ }
60
+
61
+ // Forward declaration to avoid circular imports in type layer
62
+ export interface TransactionLike {
63
+ toWire(): Uint8Array;
64
+ }
@@ -0,0 +1,134 @@
1
+ import type { BytesLike } from "../modules/helpers";
2
+ import { decodeAddress } from "../modules/helpers";
3
+ import { hexToBytes, isHexString } from "../utils/utils";
4
+ import type { AccountAddress, ProgramIdentifier } from "./types";
5
+
6
+ const ACCOUNT_LIMIT = 1024;
7
+
8
+ export function normalizeAccountList(accounts: AccountAddress[]): AccountAddress[] {
9
+ if (accounts.length === 0) {
10
+ return [];
11
+ }
12
+
13
+ if (accounts.length > ACCOUNT_LIMIT) {
14
+ throw new Error(`Too many accounts provided: ${accounts.length} (max ${ACCOUNT_LIMIT})`);
15
+ }
16
+
17
+ const deduped = dedupeAccountList(accounts);
18
+ return deduped;
19
+ }
20
+
21
+ function dedupeAccountList(accounts: AccountAddress[]): AccountAddress[] {
22
+ const seen = new Map<string, AccountAddress>();
23
+ for (const account of accounts) {
24
+ if (account.length !== 32) {
25
+ throw new Error("Account addresses must contain 32 bytes");
26
+ }
27
+
28
+ const key = toHex(account);
29
+ if (!seen.has(key)) {
30
+ seen.set(key, new Uint8Array(account));
31
+ }
32
+ }
33
+
34
+ return Array.from(seen.values()).sort(compareAccounts);
35
+ }
36
+
37
+ function compareAccounts(a: Uint8Array, b: Uint8Array): number {
38
+ for (let i = 0; i < 32; i++) {
39
+ if (a[i] !== b[i]) {
40
+ return a[i] - b[i];
41
+ }
42
+ }
43
+ return 0;
44
+ }
45
+
46
+ function toHex(bytes: Uint8Array): string {
47
+ let result = "";
48
+ for (let i = 0; i < bytes.length; i++) {
49
+ const hex = bytes[i].toString(16).padStart(2, "0");
50
+ result += hex;
51
+ }
52
+ return result;
53
+ }
54
+
55
+ export function resolveProgramIdentifier(identifier: ProgramIdentifier): AccountAddress {
56
+ if (identifier instanceof Uint8Array) {
57
+ if (identifier.length !== 32) {
58
+ throw new Error("Program public key must contain 32 bytes");
59
+ }
60
+ return copyAccount(identifier);
61
+ }
62
+
63
+ if (typeof identifier === "string") {
64
+ const parsed = parseProgramString(identifier);
65
+ if (parsed) {
66
+ return parsed;
67
+ }
68
+ }
69
+
70
+ throw new Error("Unsupported program identifier format");
71
+ }
72
+
73
+ function parseProgramString(value: string): AccountAddress | undefined {
74
+ if (value.startsWith("ta") && value.length === 46) {
75
+ return copyAccount(decodeAddress(value));
76
+ }
77
+ if (isHexString(value)) {
78
+ const bytes = hexToBytes(value);
79
+ if (bytes.length !== 32) {
80
+ throw new Error("Hex-encoded program key must contain 32 bytes");
81
+ }
82
+ return bytes;
83
+ }
84
+ return undefined;
85
+ }
86
+
87
+ function copyAccount(value: AccountAddress): AccountAddress {
88
+ if (value.length !== 32) {
89
+ throw new Error("Program public key must contain 32 bytes");
90
+ }
91
+ return new Uint8Array(value);
92
+ }
93
+
94
+ export function parseAccountIdentifier(value: BytesLike, field: string): AccountAddress {
95
+ if (value instanceof Uint8Array) {
96
+ if (value.length !== 32) {
97
+ throw new Error(`${field} must contain 32 bytes`);
98
+ }
99
+ return new Uint8Array(value);
100
+ }
101
+
102
+ if (typeof value === "string") {
103
+ if (value.startsWith("ta") && value.length === 46) {
104
+ return copyAccount(decodeAddress(value));
105
+ }
106
+ if (isHexString(value)) {
107
+ const bytes = hexToBytes(value);
108
+ if (bytes.length !== 32) {
109
+ throw new Error(`${field} hex string must decode to 32 bytes`);
110
+ }
111
+ return bytes;
112
+ }
113
+ }
114
+
115
+ throw new Error(`${field} must be a 32-byte value, ta-address, or 64-character hex string`);
116
+ }
117
+
118
+ export function parseInstructionData(value?: BytesLike): Uint8Array | undefined {
119
+ if (value === undefined) {
120
+ return undefined;
121
+ }
122
+ if (value instanceof Uint8Array) {
123
+ return new Uint8Array(value);
124
+ }
125
+ if (typeof value === "string") {
126
+ if (value.length === 0) {
127
+ return new Uint8Array();
128
+ }
129
+ if (isHexString(value)) {
130
+ return hexToBytes(value);
131
+ }
132
+ }
133
+ throw new Error("Instruction data must be provided as hex string or Uint8Array");
134
+ }
@@ -0,0 +1,8 @@
1
+ import { BytesLike } from "../modules/helpers";
2
+ import { StateProofType } from "../proto/thru/core/v1/state_pb";
3
+
4
+ export type GenerateStateProofOptions = {
5
+ address?: BytesLike;
6
+ proofType: StateProofType;
7
+ targetSlot: bigint;
8
+ }
@@ -0,0 +1,70 @@
1
+
2
+ const BASE64_URL_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
3
+ const BASE64_URL_MAP = new Int16Array(256).fill(-1);
4
+ for (let i = 0; i < BASE64_URL_ALPHABET.length; i++) {
5
+ BASE64_URL_MAP[BASE64_URL_ALPHABET.charCodeAt(i)] = i;
6
+ }
7
+
8
+ export type BytesLike = string | Uint8Array;
9
+ export type BlockSelector = { slot: number | bigint } | { blockHash: BytesLike };
10
+
11
+ export function decodeBase64(value: string): Uint8Array {
12
+ if (value.length === 0) {
13
+ return new Uint8Array();
14
+ }
15
+ const atobFn = globalThis.atob;
16
+ if (!atobFn) {
17
+ throw new Error("Base64 decoding requires window.atob support");
18
+ }
19
+ const binary = atobFn(value);
20
+ const bytes = new Uint8Array(binary.length);
21
+ for (let i = 0; i < binary.length; i++) {
22
+ bytes[i] = binary.charCodeAt(i);
23
+ }
24
+ return bytes;
25
+ }
26
+
27
+ export function isSlotSelector(selector: BlockSelector): selector is { slot: number | bigint } {
28
+ return "slot" in selector;
29
+ }
30
+
31
+ export function ensureBytes(value: BytesLike | undefined, field: string): Uint8Array {
32
+ if (value instanceof Uint8Array) {
33
+ if (value.length === 0) {
34
+ throw new Error(`${field} cannot be empty`);
35
+ }
36
+ return value;
37
+ }
38
+ if (typeof value === "string") {
39
+ if (value.length === 0) {
40
+ throw new Error(`${field} cannot be empty`);
41
+ }
42
+ return decodeBase64(value);
43
+ }
44
+ throw new Error(`${field} is required`);
45
+ }
46
+
47
+ export function maskForBits(bits: number): number {
48
+ return bits === 0 ? 0 : (1 << bits) - 1;
49
+ }
50
+
51
+ export function isHexString(value: string): boolean {
52
+ const normalized = value.startsWith("0x") ? value.slice(2) : value;
53
+ return normalized.length % 2 === 0 && normalized.length > 0 && /^[0-9a-fA-F]+$/.test(normalized);
54
+ }
55
+
56
+ export function hexToBytes(value: string): Uint8Array {
57
+ const normalized = value.startsWith("0x") ? value.slice(2) : value;
58
+ if (normalized.length % 2 !== 0) {
59
+ throw new Error("Hex string must contain an even number of characters");
60
+ }
61
+ const bytes = new Uint8Array(normalized.length / 2);
62
+ for (let i = 0; i < normalized.length; i += 2) {
63
+ const byte = parseInt(normalized.slice(i, i + 2), 16);
64
+ if (Number.isNaN(byte)) {
65
+ throw new Error("Hex string contains invalid characters");
66
+ }
67
+ bytes[i / 2] = byte;
68
+ }
69
+ return bytes;
70
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,9 @@
1
+ {
2
+ "extends": "../tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "outDir": "./dist",
5
+ "rootDir": "./thru-ts-client-sdk",
6
+ },
7
+ "include": ["thru-ts-client-sdk/**/*"],
8
+ "exclude": ["node_modules", "dist", "thru-ts-client-sdk/**/__tests__/**"]
9
+ }
package/tsup.config.ts ADDED
@@ -0,0 +1,10 @@
1
+ import { defineConfig } from 'tsup';
2
+
3
+ export default defineConfig({
4
+ entry: ['thru-ts-client-sdk/sdk.ts'],
5
+ format: ['esm'],
6
+ dts: true,
7
+ sourcemap: true,
8
+ clean: true,
9
+ treeshake: true,
10
+ });