@qubic.ts/react 0.1.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.
Files changed (44) hide show
  1. package/fixtures/next-boundary/client-node-leak.tsx +5 -0
  2. package/fixtures/next-boundary/client-ok.tsx +8 -0
  3. package/fixtures/next-boundary/server-ok.ts +5 -0
  4. package/package.json +47 -0
  5. package/scripts/verify-next-boundary.mjs +63 -0
  6. package/src/browser.ts +27 -0
  7. package/src/contract-types.ts +309 -0
  8. package/src/contracts-types.typecheck.ts +85 -0
  9. package/src/hooks/contract-hooks.test.tsx +312 -0
  10. package/src/hooks/read-hooks.test.tsx +339 -0
  11. package/src/hooks/send-hooks.test.tsx +247 -0
  12. package/src/hooks/use-balance.ts +27 -0
  13. package/src/hooks/use-contract-mutation.ts +50 -0
  14. package/src/hooks/use-contract-query.ts +71 -0
  15. package/src/hooks/use-contract.ts +231 -0
  16. package/src/hooks/use-last-processed-tick.ts +21 -0
  17. package/src/hooks/use-send-and-confirm.ts +19 -0
  18. package/src/hooks/use-send.ts +21 -0
  19. package/src/hooks/use-tick-info.ts +16 -0
  20. package/src/hooks/use-transactions.ts +97 -0
  21. package/src/index.ts +67 -0
  22. package/src/node.ts +21 -0
  23. package/src/providers/query-provider.test.tsx +25 -0
  24. package/src/providers/query-provider.tsx +13 -0
  25. package/src/providers/sdk-provider.test.tsx +54 -0
  26. package/src/providers/sdk-provider.tsx +83 -0
  27. package/src/providers/wallet-provider.test.tsx +82 -0
  28. package/src/providers/wallet-provider.tsx +209 -0
  29. package/src/query/keys.ts +9 -0
  30. package/src/typecheck-stubs/contracts.d.ts +77 -0
  31. package/src/typecheck-stubs/sdk.d.ts +254 -0
  32. package/src/vault/browser.ts +52 -0
  33. package/src/vault/node.ts +39 -0
  34. package/src/vault/runtime-boundary.test.ts +22 -0
  35. package/src/wallet/metamask-snap.test.ts +73 -0
  36. package/src/wallet/metamask-snap.ts +121 -0
  37. package/src/wallet/types.ts +55 -0
  38. package/src/wallet/utils.ts +14 -0
  39. package/src/wallet/vault.test.ts +98 -0
  40. package/src/wallet/vault.ts +70 -0
  41. package/src/wallet/walletconnect.test.ts +141 -0
  42. package/src/wallet/walletconnect.ts +218 -0
  43. package/tsconfig.json +14 -0
  44. package/tsconfig.typecheck.json +12 -0
@@ -0,0 +1,247 @@
1
+ import { describe, expect, it } from "bun:test";
2
+ import type {
3
+ BuildSignedTransferInput,
4
+ SendAndConfirmInput,
5
+ SendTransferResult,
6
+ } from "@qubic.ts/sdk";
7
+ import { createSdk } from "@qubic.ts/sdk";
8
+ import type { UseMutationResult } from "@tanstack/react-query";
9
+ import { QueryClient } from "@tanstack/react-query";
10
+ import React from "react";
11
+ import TestRenderer, { act } from "react-test-renderer";
12
+ import { QubicQueryProvider } from "../providers/query-provider.js";
13
+ import { SdkProvider } from "../providers/sdk-provider.js";
14
+ import { useSend } from "./use-send.js";
15
+ import { useSendAndConfirm } from "./use-send-and-confirm.js";
16
+
17
+ describe("send mutation hooks", () => {
18
+ it("useSend exposes success state for successful transfers", async () => {
19
+ let sendCalls = 0;
20
+ let receivedInput: BuildSignedTransferInput | undefined;
21
+ const sdk = createMockSdk({
22
+ send: async (input) => {
23
+ sendCalls += 1;
24
+ receivedInput = input;
25
+ return createSendResult("send-success");
26
+ },
27
+ });
28
+
29
+ const queryClient = createTestQueryClient();
30
+ let hookResult: UseMutationResult<SendTransferResult, Error, BuildSignedTransferInput> | undefined;
31
+
32
+ function Consumer() {
33
+ hookResult = useSend();
34
+ return null;
35
+ }
36
+
37
+ await act(async () => {
38
+ TestRenderer.create(
39
+ <TestProviders sdk={sdk} queryClient={queryClient}>
40
+ <Consumer />
41
+ </TestProviders>,
42
+ );
43
+ });
44
+
45
+ const input = createSendInput("target-1");
46
+ await act(async () => {
47
+ await hookResult?.mutateAsync(input);
48
+ });
49
+
50
+ await waitFor(() => hookResult?.isSuccess === true);
51
+ expect(sendCalls).toBe(1);
52
+ expect(receivedInput).toEqual(input);
53
+ expect(hookResult?.data?.txId).toBe("tx-send-success");
54
+ });
55
+
56
+ it("useSend exposes error state when transfer send fails", async () => {
57
+ const sdk = createMockSdk({
58
+ send: async () => {
59
+ throw new Error("send failed");
60
+ },
61
+ });
62
+
63
+ const queryClient = createTestQueryClient();
64
+ let hookResult: UseMutationResult<SendTransferResult, Error, BuildSignedTransferInput> | undefined;
65
+
66
+ function Consumer() {
67
+ hookResult = useSend();
68
+ return null;
69
+ }
70
+
71
+ await act(async () => {
72
+ TestRenderer.create(
73
+ <TestProviders sdk={sdk} queryClient={queryClient}>
74
+ <Consumer />
75
+ </TestProviders>,
76
+ );
77
+ });
78
+
79
+ await act(async () => {
80
+ try {
81
+ await hookResult?.mutateAsync(createSendInput("target-2"));
82
+ } catch {
83
+ // Assertion is state-based below.
84
+ }
85
+ });
86
+
87
+ await waitFor(() => hookResult?.isError === true);
88
+ expect(hookResult?.error?.message).toBe("send failed");
89
+ });
90
+
91
+ it("useSendAndConfirm exposes success state for successful send+confirm", async () => {
92
+ let calls = 0;
93
+ let receivedInput: SendAndConfirmInput | undefined;
94
+ const sdk = createMockSdk({
95
+ sendAndConfirm: async (input) => {
96
+ calls += 1;
97
+ receivedInput = input;
98
+ return createSendResult("send-and-confirm-success");
99
+ },
100
+ });
101
+
102
+ const queryClient = createTestQueryClient();
103
+ let hookResult: UseMutationResult<SendTransferResult, Error, SendAndConfirmInput> | undefined;
104
+
105
+ function Consumer() {
106
+ hookResult = useSendAndConfirm();
107
+ return null;
108
+ }
109
+
110
+ await act(async () => {
111
+ TestRenderer.create(
112
+ <TestProviders sdk={sdk} queryClient={queryClient}>
113
+ <Consumer />
114
+ </TestProviders>,
115
+ );
116
+ });
117
+
118
+ const input = createSendAndConfirmInput("target-3");
119
+ await act(async () => {
120
+ await hookResult?.mutateAsync(input);
121
+ });
122
+
123
+ await waitFor(() => hookResult?.isSuccess === true);
124
+ expect(calls).toBe(1);
125
+ expect(receivedInput).toEqual(input);
126
+ expect(hookResult?.data?.txId).toBe("tx-send-and-confirm-success");
127
+ });
128
+
129
+ it("useSendAndConfirm exposes error state on failure", async () => {
130
+ const sdk = createMockSdk({
131
+ sendAndConfirm: async () => {
132
+ throw new Error("sendAndConfirm failed");
133
+ },
134
+ });
135
+
136
+ const queryClient = createTestQueryClient();
137
+ let hookResult: UseMutationResult<SendTransferResult, Error, SendAndConfirmInput> | undefined;
138
+
139
+ function Consumer() {
140
+ hookResult = useSendAndConfirm();
141
+ return null;
142
+ }
143
+
144
+ await act(async () => {
145
+ TestRenderer.create(
146
+ <TestProviders sdk={sdk} queryClient={queryClient}>
147
+ <Consumer />
148
+ </TestProviders>,
149
+ );
150
+ });
151
+
152
+ await act(async () => {
153
+ try {
154
+ await hookResult?.mutateAsync(createSendAndConfirmInput("target-4"));
155
+ } catch {
156
+ // Assertion is state-based below.
157
+ }
158
+ });
159
+
160
+ await waitFor(() => hookResult?.isError === true);
161
+ expect(hookResult?.error?.message).toBe("sendAndConfirm failed");
162
+ });
163
+ });
164
+
165
+ function createTestQueryClient(): QueryClient {
166
+ return new QueryClient({
167
+ defaultOptions: {
168
+ queries: {
169
+ retry: false,
170
+ },
171
+ mutations: {
172
+ retry: false,
173
+ },
174
+ },
175
+ });
176
+ }
177
+
178
+ function TestProviders(props: {
179
+ sdk: ReturnType<typeof createSdk>;
180
+ queryClient: QueryClient;
181
+ children: React.ReactNode;
182
+ }) {
183
+ return (
184
+ <QubicQueryProvider client={props.queryClient}>
185
+ <SdkProvider sdk={props.sdk}>{props.children}</SdkProvider>
186
+ </QubicQueryProvider>
187
+ );
188
+ }
189
+
190
+ function createMockSdk(overrides: Readonly<{
191
+ send?: (input: BuildSignedTransferInput) => Promise<SendTransferResult>;
192
+ sendAndConfirm?: (input: SendAndConfirmInput) => Promise<SendTransferResult>;
193
+ }>): ReturnType<typeof createSdk> {
194
+ return {
195
+ transfers: {
196
+ send: overrides.send ?? (async () => createSendResult("default-send")),
197
+ sendAndConfirm:
198
+ overrides.sendAndConfirm ?? (async () => createSendResult("default-send-and-confirm")),
199
+ },
200
+ } as unknown as ReturnType<typeof createSdk>;
201
+ }
202
+
203
+ function createSendInput(toIdentity: string): BuildSignedTransferInput {
204
+ return {
205
+ fromSeed: "a".repeat(55),
206
+ toIdentity,
207
+ amount: 1n,
208
+ targetTick: 7n,
209
+ };
210
+ }
211
+
212
+ function createSendAndConfirmInput(toIdentity: string): SendAndConfirmInput {
213
+ return {
214
+ ...createSendInput(toIdentity),
215
+ timeoutMs: 2000,
216
+ pollIntervalMs: 100,
217
+ };
218
+ }
219
+
220
+ function createSendResult(tag: string): SendTransferResult {
221
+ return {
222
+ txBytes: new Uint8Array([1, 2, 3]),
223
+ txId: `tx-${tag}`,
224
+ networkTxId: `network-${tag}`,
225
+ targetTick: 8n,
226
+ broadcast: {
227
+ peersBroadcasted: 1,
228
+ encodedTransaction: "AQID",
229
+ transactionId: `network-${tag}`,
230
+ },
231
+ };
232
+ }
233
+
234
+ async function waitFor(predicate: () => boolean, timeoutMs = 3000): Promise<void> {
235
+ const startedAt = Date.now();
236
+ while (Date.now() - startedAt < timeoutMs) {
237
+ if (predicate()) return;
238
+ await act(async () => {
239
+ await sleep(10);
240
+ });
241
+ }
242
+ throw new Error("Timed out waiting for condition");
243
+ }
244
+
245
+ function sleep(ms: number): Promise<void> {
246
+ return new Promise((resolve) => setTimeout(resolve, ms));
247
+ }
@@ -0,0 +1,27 @@
1
+ import type { LiveBalance } from "@qubic.ts/sdk";
2
+ import type { UseQueryOptions, UseQueryResult } from "@tanstack/react-query";
3
+ import { useQuery } from "@tanstack/react-query";
4
+ import { useSdk } from "../providers/sdk-provider.js";
5
+ import { queryKeys } from "../query/keys.js";
6
+
7
+ export type UseBalanceOptions = Omit<UseQueryOptions<LiveBalance, Error>, "queryKey" | "queryFn">;
8
+
9
+ export function useBalance(
10
+ identity: string | undefined,
11
+ options: UseBalanceOptions = {},
12
+ ): UseQueryResult<LiveBalance, Error> {
13
+ const sdk = useSdk();
14
+ const enabled = Boolean(identity) && (options.enabled ?? true);
15
+
16
+ return useQuery({
17
+ ...options,
18
+ queryKey: identity ? queryKeys.balance(identity) : queryKeys.balance(""),
19
+ enabled,
20
+ queryFn: async () => {
21
+ if (!identity) {
22
+ throw new Error("useBalance requires identity");
23
+ }
24
+ return sdk.rpc.live.balance(identity);
25
+ },
26
+ });
27
+ }
@@ -0,0 +1,50 @@
1
+ import type { UseMutationOptions, UseMutationResult } from "@tanstack/react-query";
2
+ import { useMutation } from "@tanstack/react-query";
3
+ import type {
4
+ ContractCodecSchema,
5
+ ContractMutationResult,
6
+ ContractProcedureInput,
7
+ ContractProcedureOutput,
8
+ ContractProcedureTxInput,
9
+ } from "../contract-types.js";
10
+ import { useContract } from "./use-contract.js";
11
+
12
+ export type UseContractMutationOptions<Input = unknown> = Omit<
13
+ UseMutationOptions<ContractMutationResult, Error, ContractProcedureTxInput<Input>>,
14
+ "mutationFn"
15
+ >;
16
+
17
+ type TypedProcedureInput<
18
+ Schema extends ContractCodecSchema,
19
+ Name extends string,
20
+ > = ContractProcedureTxInput<
21
+ ContractProcedureInput<Schema, Name>,
22
+ ContractProcedureOutput<Schema, Name>
23
+ > &
24
+ Readonly<{ name: Name }>;
25
+
26
+ export function useContractMutation<
27
+ Schema extends ContractCodecSchema,
28
+ Name extends keyof NonNullable<Schema["procedures"]> & string,
29
+ >(
30
+ nameOrIndex: string | number,
31
+ options?: UseContractMutationOptions<ContractProcedureInput<Schema, Name>>,
32
+ ): UseMutationResult<
33
+ ContractMutationResult<ContractProcedureOutput<Schema, Name>>,
34
+ Error,
35
+ TypedProcedureInput<Schema, Name>
36
+ >;
37
+ export function useContractMutation<Input = unknown>(
38
+ nameOrIndex: string | number,
39
+ options?: UseContractMutationOptions<Input>,
40
+ ): UseMutationResult<ContractMutationResult, Error, ContractProcedureTxInput<Input>>;
41
+ export function useContractMutation<Input = unknown>(
42
+ nameOrIndex: string | number,
43
+ options: UseContractMutationOptions<Input> = {},
44
+ ): UseMutationResult<ContractMutationResult, Error, ContractProcedureTxInput<Input>> {
45
+ const contract = useContract(nameOrIndex);
46
+ return useMutation({
47
+ ...options,
48
+ mutationFn: (input) => contract.sendProcedure<Input, unknown>(input),
49
+ });
50
+ }
@@ -0,0 +1,71 @@
1
+ import type { UseQueryOptions, UseQueryResult } from "@tanstack/react-query";
2
+ import { useQuery } from "@tanstack/react-query";
3
+ import type {
4
+ ContractCodecSchema,
5
+ ContractFunctionInput,
6
+ ContractFunctionOutput,
7
+ ContractQueryInput,
8
+ ContractQueryResult,
9
+ } from "../contract-types.js";
10
+ import { queryKeys } from "../query/keys.js";
11
+ import { useContract } from "./use-contract.js";
12
+
13
+ export type UseContractQueryOptions<_Output> = Omit<
14
+ UseQueryOptions<ContractQueryResult<_Output>, Error>,
15
+ "queryKey" | "queryFn"
16
+ > &
17
+ Readonly<{
18
+ inputKey?: string;
19
+ }>;
20
+
21
+ export function useContractQuery<
22
+ Schema extends ContractCodecSchema,
23
+ Name extends keyof NonNullable<Schema["functions"]> & string,
24
+ >(
25
+ nameOrIndex: string | number,
26
+ entry: Name,
27
+ input: ContractQueryInput<ContractFunctionInput<Schema, Name>>,
28
+ options?: UseContractQueryOptions<ContractFunctionOutput<Schema, Name>>,
29
+ ): UseQueryResult<ContractQueryResult<ContractFunctionOutput<Schema, Name>>, Error>;
30
+ export function useContractQuery<Input = unknown, Output = unknown>(
31
+ nameOrIndex: string | number,
32
+ entry: string,
33
+ input: ContractQueryInput<Input>,
34
+ options?: UseContractQueryOptions<Output>,
35
+ ): UseQueryResult<ContractQueryResult<Output>, Error>;
36
+ export function useContractQuery<Input = unknown, Output = unknown>(
37
+ nameOrIndex: string | number,
38
+ entry: string,
39
+ input: ContractQueryInput<Input>,
40
+ options: UseContractQueryOptions<Output> = {},
41
+ ): UseQueryResult<ContractQueryResult<Output>, Error> {
42
+ const contract = useContract(nameOrIndex);
43
+ const inputKey = options.inputKey ?? defaultInputKey(input);
44
+
45
+ return useQuery({
46
+ ...options,
47
+ queryKey: queryKeys.contractQuery(String(nameOrIndex), entry, inputKey),
48
+ queryFn: () => contract.query<Input, Output>(entry, input),
49
+ });
50
+ }
51
+
52
+ function defaultInputKey(input: ContractQueryInput): string {
53
+ if (input.inputBytes) return toBase64(input.inputBytes);
54
+ if (input.inputValue !== undefined) {
55
+ try {
56
+ return JSON.stringify(input.inputValue, (_key, value) =>
57
+ typeof value === "bigint" ? value.toString() : value,
58
+ );
59
+ } catch {
60
+ return "value";
61
+ }
62
+ }
63
+ return "empty";
64
+ }
65
+
66
+ function toBase64(bytes: Uint8Array): string {
67
+ if (typeof Buffer !== "undefined") return Buffer.from(bytes).toString("base64");
68
+ let binary = "";
69
+ for (const b of bytes) binary += String.fromCharCode(b);
70
+ return btoa(binary);
71
+ }
@@ -0,0 +1,231 @@
1
+ import type { ContractDefinition, ContractEntryKind } from "@qubic.ts/contracts";
2
+ import {
3
+ bytesToBase64,
4
+ decodeContractEntryInputData,
5
+ encodeContractEntryInputData,
6
+ } from "@qubic.ts/contracts";
7
+ import type { SeedSourceInput } from "@qubic.ts/sdk";
8
+ import type {
9
+ ContractHandleLike,
10
+ ContractMutationResult,
11
+ ContractProcedureTxInput,
12
+ ContractQueryInput,
13
+ ContractQueryResult,
14
+ ContractRegistryInput,
15
+ } from "../contract-types.js";
16
+ import { useContractsRegistry, useSdk } from "../providers/sdk-provider.js";
17
+
18
+ export function useContract(nameOrIndex: string | number): ContractHandleLike {
19
+ const sdk = useSdk();
20
+ const registry = useContractsRegistry();
21
+ const contract = resolveContract(registry, nameOrIndex);
22
+
23
+ return {
24
+ async query<Input = unknown, Output = unknown>(
25
+ name: string,
26
+ input: ContractQueryInput<Input>,
27
+ ): Promise<ContractQueryResult<Output>> {
28
+ const entry = resolveEntry(contract, "function", name);
29
+ const inputBytes = resolveInputBytes({
30
+ registry,
31
+ contract,
32
+ entryName: name,
33
+ kind: "function",
34
+ input,
35
+ });
36
+ const raw = await sdk.contracts.queryRaw({
37
+ contractIndex: contract.contractIndex,
38
+ inputType: entry.inputType,
39
+ inputBytes,
40
+ ...toDefined("expectedOutputSize", input.expectedOutputSize ?? entry.outputSize),
41
+ ...toDefined("retries", input.retries),
42
+ ...toDefined("retryDelayMs", input.retryDelayMs),
43
+ ...toDefined("signal", input.signal),
44
+ });
45
+ const decoded = decodeQueryOutput<Output>({
46
+ registry,
47
+ contract,
48
+ entryName: name,
49
+ bytes: raw.responseBytes,
50
+ ...toDefined("allowSequentialLayout", input.allowSequentialLayout),
51
+ ...toDefined("decodeOutput", input.decodeOutput),
52
+ });
53
+ return decoded === undefined ? raw : { ...raw, decoded };
54
+ },
55
+ async sendProcedure<Input = unknown, Output = unknown>(
56
+ input: ContractProcedureTxInput<Input, Output>,
57
+ ): Promise<ContractMutationResult<Output>> {
58
+ const entry = resolveEntry(contract, "procedure", input.name);
59
+ const inputBytes = resolveInputBytes({
60
+ registry,
61
+ contract,
62
+ entryName: input.name,
63
+ kind: "procedure",
64
+ input,
65
+ });
66
+ const seedSource = resolveSeedSource(input);
67
+ const toIdentity = resolveToIdentity(contract, input.toIdentity);
68
+ const tx = await sdk.transactions.send({
69
+ ...seedSource,
70
+ toIdentity,
71
+ amount: input.amount ?? 0n,
72
+ inputType: entry.inputType,
73
+ inputBytes,
74
+ ...toDefined("targetTick", input.targetTick),
75
+ });
76
+ const baseResult = {
77
+ ...tx,
78
+ responseBytes: tx.txBytes,
79
+ responseBase64: bytesToBase64(tx.txBytes),
80
+ };
81
+ const decoded = input.decodeResponse?.(tx);
82
+ return decoded === undefined ? baseResult : { ...baseResult, decoded };
83
+ },
84
+ };
85
+ }
86
+
87
+ function resolveContract(
88
+ registry: ContractRegistryInput,
89
+ nameOrIndex: string | number,
90
+ ): ContractDefinition {
91
+ const contracts = isContractDefinitionList(registry) ? registry : registry.contracts;
92
+ const contract =
93
+ typeof nameOrIndex === "number"
94
+ ? contracts.find((item) => item.contractIndex === nameOrIndex)
95
+ : contracts.find((item) => item.name === nameOrIndex);
96
+ if (!contract) {
97
+ throw new Error(`Unknown contract: ${String(nameOrIndex)}`);
98
+ }
99
+ return contract;
100
+ }
101
+
102
+ function isContractDefinitionList(
103
+ registry: ContractRegistryInput,
104
+ ): registry is readonly ContractDefinition[] {
105
+ return Array.isArray(registry);
106
+ }
107
+
108
+ function resolveEntry(contract: ContractDefinition, kind: ContractEntryKind, name: string) {
109
+ const entry = contract.entries.find((item) => item.kind === kind && item.name === name);
110
+ if (!entry) {
111
+ throw new Error(`Unknown ${kind}: ${contract.name}.${name}`);
112
+ }
113
+ return entry;
114
+ }
115
+
116
+ function resolveInputBytes(options: {
117
+ registry: ContractRegistryInput;
118
+ contract: ContractDefinition;
119
+ entryName: string;
120
+ kind: ContractEntryKind;
121
+ input: ContractQueryInput | ContractProcedureTxInput<unknown, unknown>;
122
+ }): Uint8Array {
123
+ if (options.input.inputBytes) return options.input.inputBytes;
124
+ if (options.input.inputValue === undefined) {
125
+ throw new Error(
126
+ `inputBytes or inputValue is required for ${options.contract.name}.${options.entryName}`,
127
+ );
128
+ }
129
+ return encodeContractEntryInputData({
130
+ registry: options.registry,
131
+ contractName: options.contract.name,
132
+ entryName: options.entryName,
133
+ kind: options.kind,
134
+ value: options.input.inputValue,
135
+ ...toDefined("allowSequentialLayout", options.input.allowSequentialLayout),
136
+ }).bytes;
137
+ }
138
+
139
+ function resolveSeedSource(input: ContractProcedureTxInput): SeedSourceInput {
140
+ if ("fromSeed" in input && typeof input.fromSeed === "string") {
141
+ return { fromSeed: input.fromSeed };
142
+ }
143
+ if ("fromVault" in input && typeof input.fromVault === "string") {
144
+ return { fromVault: input.fromVault };
145
+ }
146
+ throw new Error("Procedure input requires fromSeed or fromVault");
147
+ }
148
+
149
+ function decodeQueryOutput<Output>(options: {
150
+ registry: ContractRegistryInput;
151
+ contract: ContractDefinition;
152
+ entryName: string;
153
+ bytes: Uint8Array;
154
+ allowSequentialLayout?: boolean | undefined;
155
+ decodeOutput?: boolean | undefined;
156
+ }): Output | undefined {
157
+ if (options.decodeOutput === false) return undefined;
158
+ const entry = resolveEntry(options.contract, "function", options.entryName);
159
+ if (!entry.outputTypeName) return undefined;
160
+ const patchedRegistry = patchRegistryForOutputDecode({
161
+ registry: options.registry,
162
+ contractName: options.contract.name,
163
+ entryName: options.entryName,
164
+ outputTypeName: entry.outputTypeName,
165
+ });
166
+ try {
167
+ return decodeContractEntryInputData<Output>({
168
+ registry: patchedRegistry,
169
+ contractName: options.contract.name,
170
+ entryName: options.entryName,
171
+ kind: "function",
172
+ bytes: options.bytes,
173
+ ...toDefined("allowSequentialLayout", options.allowSequentialLayout),
174
+ }).value;
175
+ } catch {
176
+ return undefined;
177
+ }
178
+ }
179
+
180
+ function patchRegistryForOutputDecode(options: {
181
+ registry: ContractRegistryInput;
182
+ contractName: string;
183
+ entryName: string;
184
+ outputTypeName: string;
185
+ }): ContractRegistryInput {
186
+ if (isContractDefinitionList(options.registry)) {
187
+ return options.registry.map((contract) => patchContractEntry(contract, options));
188
+ }
189
+ return {
190
+ ...options.registry,
191
+ contracts: options.registry.contracts.map((contract) => patchContractEntry(contract, options)),
192
+ };
193
+ }
194
+
195
+ function patchContractEntry(
196
+ contract: ContractDefinition,
197
+ options: { contractName: string; entryName: string; outputTypeName: string },
198
+ ): ContractDefinition {
199
+ if (contract.name !== options.contractName) return contract;
200
+ return {
201
+ ...contract,
202
+ entries: contract.entries.map((entry) =>
203
+ entry.kind === "function" && entry.name === options.entryName
204
+ ? {
205
+ ...entry,
206
+ inputTypeName: options.outputTypeName,
207
+ }
208
+ : entry,
209
+ ),
210
+ };
211
+ }
212
+
213
+ function resolveToIdentity(contract: ContractDefinition, override?: string): string {
214
+ if (typeof override === "string") return override;
215
+ if (isIdentity(contract.address)) return contract.address;
216
+ throw new Error(
217
+ `Missing contract identity for ${contract.name}. Pass input.toIdentity until registry contains identity60 addresses.`,
218
+ );
219
+ }
220
+
221
+ function isIdentity(value: string): boolean {
222
+ return /^[A-Za-z]{60}$/.test(value);
223
+ }
224
+
225
+ function toDefined<Key extends string, Value>(
226
+ key: Key,
227
+ value: Value | undefined,
228
+ ): { [K in Key]?: Value } {
229
+ if (value === undefined) return {};
230
+ return { [key]: value } as { [K in Key]?: Value };
231
+ }
@@ -0,0 +1,21 @@
1
+ import type { LastProcessedTick } from "@qubic.ts/sdk";
2
+ import type { UseQueryOptions, UseQueryResult } from "@tanstack/react-query";
3
+ import { useQuery } from "@tanstack/react-query";
4
+ import { useSdk } from "../providers/sdk-provider.js";
5
+ import { queryKeys } from "../query/keys.js";
6
+
7
+ export type UseLastProcessedTickOptions = Omit<
8
+ UseQueryOptions<LastProcessedTick, Error>,
9
+ "queryKey" | "queryFn"
10
+ >;
11
+
12
+ export function useLastProcessedTick(
13
+ options: UseLastProcessedTickOptions = {},
14
+ ): UseQueryResult<LastProcessedTick, Error> {
15
+ const sdk = useSdk();
16
+ return useQuery({
17
+ ...options,
18
+ queryKey: queryKeys.lastProcessedTick(),
19
+ queryFn: () => sdk.rpc.query.getLastProcessedTick(),
20
+ });
21
+ }
@@ -0,0 +1,19 @@
1
+ import type { SendAndConfirmInput, SendTransferResult } from "@qubic.ts/sdk";
2
+ import type { UseMutationOptions, UseMutationResult } from "@tanstack/react-query";
3
+ import { useMutation } from "@tanstack/react-query";
4
+ import { useSdk } from "../providers/sdk-provider.js";
5
+
6
+ export type UseSendAndConfirmOptions = Omit<
7
+ UseMutationOptions<SendTransferResult, Error, SendAndConfirmInput>,
8
+ "mutationFn"
9
+ >;
10
+
11
+ export function useSendAndConfirm(
12
+ options: UseSendAndConfirmOptions = {},
13
+ ): UseMutationResult<SendTransferResult, Error, SendAndConfirmInput> {
14
+ const sdk = useSdk();
15
+ return useMutation({
16
+ ...options,
17
+ mutationFn: (input) => sdk.transfers.sendAndConfirm(input),
18
+ });
19
+ }
@@ -0,0 +1,21 @@
1
+ import type { BuildSignedTransferInput, SendTransferResult } from "@qubic.ts/sdk";
2
+ import type { UseMutationOptions, UseMutationResult } from "@tanstack/react-query";
3
+ import { useMutation } from "@tanstack/react-query";
4
+ import { useSdk } from "../providers/sdk-provider.js";
5
+
6
+ export type UseSendOptions = Omit<
7
+ UseMutationOptions<SendTransferResult, Error, BuildSignedTransferInput>,
8
+ "mutationFn"
9
+ >;
10
+
11
+ export function useSend(options: UseSendOptions = {}): UseMutationResult<
12
+ SendTransferResult,
13
+ Error,
14
+ BuildSignedTransferInput
15
+ > {
16
+ const sdk = useSdk();
17
+ return useMutation({
18
+ ...options,
19
+ mutationFn: (input) => sdk.transfers.send(input),
20
+ });
21
+ }