Skip to content
This repository was archived by the owner on Jul 30, 2025. It is now read-only.

Commit f10e2f4

Browse files
authored
Stricter error handling (#250)
1 parent 87aa4be commit f10e2f4

9 files changed

Lines changed: 91 additions & 20 deletions

File tree

ts/shielder-sdk-tests/web/fixtures/callbacks.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,18 @@ export const setupCallbacks = (): CallbacksFixture => {
2222
txHistory.set(token, []);
2323
}
2424
txHistory.get(token)!.push(tx);
25+
},
26+
onAccountNotOnChain: (error, stage, operation) => {
27+
console.error(
28+
`Account not on chain during ${stage} for operation ${operation}:`,
29+
error
30+
);
31+
},
32+
onSdkOutdated: (error, stage, operation) => {
33+
console.warn(
34+
`SDK outdated during ${stage} for operation ${operation}:`,
35+
error
36+
);
2537
}
2638
};
2739

ts/shielder-sdk/__tests/client/actions.test.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@ describe("ShielderActions", () => {
176176

177177
await expect(method()).rejects.toThrow(mockError);
178178

179-
expect(mockCallbacks.onError).toHaveBeenCalledWith(
179+
expect(mockCallbacks.onSdkOutdated).toHaveBeenCalledWith(
180180
mockError,
181181
"generation",
182182
operation
@@ -258,7 +258,9 @@ describe("ShielderActions", () => {
258258
mockCallbacks = {
259259
onCalldataGenerated: vitest.fn(),
260260
onCalldataSent: vitest.fn(),
261-
onError: vitest.fn()
261+
onError: vitest.fn(),
262+
onAccountNotOnChain: vitest.fn(),
263+
onSdkOutdated: vitest.fn()
262264
};
263265

264266
// Setup mock account state

ts/shielder-sdk/__tests/client/client.test.ts

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@ import { MockedCryptoClient } from "../helpers";
55
import { ShielderClient } from "../../src/client/client";
66
import { createShielderClient } from "../../src/client/factories";
77
import { Contract } from "../../src/chain/contract";
8-
import { quotedFeesFromExpectedTokenFee, Relayer } from "../../src/chain/relayer";
8+
import {
9+
quotedFeesFromExpectedTokenFee,
10+
Relayer
11+
} from "../../src/chain/relayer";
912
import { InjectedStorageInterface } from "../../src/storage/storageSchema";
1013
import {
1114
AccountStateMerkleIndexed,
@@ -89,7 +92,9 @@ describe("ShielderClient", () => {
8992
callbacks = {
9093
onCalldataGenerated: vitest.fn(),
9194
onCalldataSent: vitest.fn(),
92-
onError: vitest.fn()
95+
onError: vitest.fn(),
96+
onSdkOutdated: vitest.fn(),
97+
onAccountNotOnChain: vitest.fn()
9398
};
9499

95100
// Create client instance with mocked components
@@ -141,7 +146,11 @@ describe("ShielderClient", () => {
141146
contractAddress: mockContractAddress,
142147
relayerUrl: mockRelayerUrl,
143148
storage: mockStorageInterface,
144-
cryptoClient: mockCryptoClient
149+
cryptoClient: mockCryptoClient,
150+
callbacks: {
151+
onSdkOutdated: vitest.fn(),
152+
onAccountNotOnChain: vitest.fn()
153+
}
145154
});
146155
});
147156

@@ -157,15 +166,20 @@ describe("ShielderClient", () => {
157166
it("should create ShielderClient with default callbacks", () => {
158167
expect(client).toBeInstanceOf(ShielderClient);
159168
// Verify that the client was created with empty callbacks object
160-
expect(client["callbacks"]).toEqual({});
169+
expect(client["callbacks"]).toEqual({
170+
onSdkOutdated: expect.any(Function),
171+
onAccountNotOnChain: expect.any(Function)
172+
});
161173
});
162174

163175
it("should create ShielderClient with provided callbacks", () => {
164176
const mockCallbacks = {
165177
onCalldataGenerated: vitest.fn(),
166178
onCalldataSent: vitest.fn(),
167179
onNewTransaction: vitest.fn(),
168-
onError: vitest.fn()
180+
onError: vitest.fn(),
181+
onAccountNotOnChain: vitest.fn(),
182+
onSdkOutdated: vitest.fn()
169183
};
170184

171185
client = createShielderClient({
@@ -209,7 +223,7 @@ describe("ShielderClient", () => {
209223
mockStateSynchronizer.syncAllAccounts.mockRejectedValue(mockError);
210224

211225
await expect(client.syncShielder()).rejects.toThrow(mockError);
212-
expect(callbacks.onError).toHaveBeenCalledWith(
226+
expect(callbacks.onSdkOutdated).toHaveBeenCalledWith(
213227
mockError,
214228
"syncing",
215229
"sync"

ts/shielder-sdk/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@cardinal-cryptography/shielder-sdk",
3-
"version": "0.2.0-beta.9",
3+
"version": "0.2.0-beta.10",
44
"description": "A web package for interacting with Shielder, a part of zkOS privacy engine.",
55
"license": "Apache-2.0",
66
"keywords": [

ts/shielder-sdk/src/client/actions.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { WithdrawAction } from "@/actions/withdraw";
1818
import { contractVersion } from "@/constants";
1919
import { Calldata } from "@/actions/types";
2020
import { AccountStateMerkleIndexed } from "@/state/types";
21+
import { handleShielderError } from "@/utils/errorHandler";
2122

2223
export class ShielderActions {
2324
constructor(
@@ -224,7 +225,7 @@ export class ShielderActions {
224225
try {
225226
calldata = await generateCalldata();
226227
} catch (error) {
227-
this.callbacks.onError?.(error, "generation", operation);
228+
handleShielderError(error, this.callbacks, "generation", operation);
228229
throw error;
229230
}
230231
this.callbacks.onCalldataGenerated?.(calldata, operation);
@@ -234,7 +235,7 @@ export class ShielderActions {
234235
this.callbacks.onCalldataSent?.(txHash, operation);
235236
return txHash;
236237
} catch (error) {
237-
this.callbacks.onError?.(error, "sending", operation);
238+
handleShielderError(error, this.callbacks, "sending", operation);
238239
throw error;
239240
}
240241
}

ts/shielder-sdk/src/client/client.ts

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { AccountRegistry } from "@/state/accountRegistry";
88
import { ShielderActions } from "./actions";
99
import { ShielderComponents } from "./factories";
1010
import { QuotedFees } from "@/chain/relayer";
11+
import { handleShielderError } from "@/utils/errorHandler";
1112

1213
export class ShielderClient {
1314
private accountRegistry: AccountRegistry;
@@ -22,10 +23,7 @@ export class ShielderClient {
2223
* @param {ShielderComponents} components - components for the shielder client
2324
* @param {ShielderCallbacks} callbacks - callbacks for the shielder actions
2425
*/
25-
constructor(
26-
components: ShielderComponents,
27-
callbacks: ShielderCallbacks = {}
28-
) {
26+
constructor(components: ShielderComponents, callbacks: ShielderCallbacks) {
2927
this.accountRegistry = components.accountRegistry;
3028
this.stateSynchronizer = components.stateSynchronizer;
3129
this.historyFetcher = components.historyFetcher;
@@ -47,7 +45,7 @@ export class ShielderClient {
4745
try {
4846
return await this.stateSynchronizer.syncAllAccounts();
4947
} catch (error) {
50-
this.callbacks.onError?.(error, "syncing", "sync");
48+
handleShielderError(error, this.callbacks, "syncing", "sync");
5149
throw error;
5250
}
5351
}
@@ -87,7 +85,7 @@ export class ShielderClient {
8785
yield transaction;
8886
}
8987
} catch (error) {
90-
this.callbacks.onError?.(error, "syncing", "sync");
88+
handleShielderError(error, this.callbacks, "syncing", "sync");
9189
throw error;
9290
}
9391
}

ts/shielder-sdk/src/client/factories.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ type BaseShielderConfig = {
3232
storage: InjectedStorageInterface;
3333
cryptoClient: CryptoClient;
3434
publicClient: PublicClient;
35-
callbacks?: ShielderCallbacks;
35+
callbacks: ShielderCallbacks;
3636
};
3737

3838
// Public config for creating a client
@@ -64,7 +64,7 @@ export const createShielderClient = (
6464
relayer
6565
});
6666

67-
return new ShielderClient(components, config.callbacks || {});
67+
return new ShielderClient(components, config.callbacks);
6868
};
6969

7070
// Internal config for component creation
@@ -124,7 +124,7 @@ function createShielderComponents(
124124
actionComponents.depositAction,
125125
actionComponents.withdrawAction,
126126
config.publicClient,
127-
config.callbacks || {}
127+
config.callbacks
128128
);
129129

130130
return {

ts/shielder-sdk/src/client/types.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,24 @@ export interface ShielderCallbacks {
2929
* @param tx - new transaction
3030
*/
3131
onNewTransaction?: (tx: ShielderTransaction) => unknown;
32+
/**
33+
* Fired when the current shielder state is broken. Usually this means that the chain
34+
* has been reorganized and the shielder state is no longer valid.
35+
* The client should be re-initialized to recover from this state.
36+
*/
37+
onAccountNotOnChain: (
38+
error: unknown,
39+
stage: "generation" | "sending" | "syncing",
40+
operation: ShielderOperation
41+
) => unknown;
42+
/**
43+
* Fired when the SDK version is outdated and cannot be used for the operation.
44+
*/
45+
onSdkOutdated: (
46+
error: unknown,
47+
stage: "generation" | "sending" | "syncing",
48+
operation: ShielderOperation
49+
) => unknown;
3250
/**
3351
* Fired when an error occurs during the generation or sending of the calldata, or during syncing.
3452
*/
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { ShielderCallbacks, ShielderOperation } from "@/client/types";
2+
import { OutdatedSdkError, AccountNotOnChainError } from "@/errors";
3+
4+
/**
5+
* Handles errors in a consistent way across the Shielder SDK.
6+
* Checks for specific error types and calls appropriate callbacks.
7+
*
8+
* @param error - The error that occurred
9+
* @param callbacks - The callbacks object containing error handlers
10+
* @param stage - The stage where the error occurred
11+
* @param operation - The operation being performed when the error occurred
12+
*/
13+
export function handleShielderError(
14+
error: unknown,
15+
callbacks: ShielderCallbacks,
16+
stage: "generation" | "sending" | "syncing",
17+
operation: ShielderOperation
18+
) {
19+
if (error instanceof OutdatedSdkError) {
20+
callbacks.onSdkOutdated(error, stage, operation);
21+
} else if (error instanceof AccountNotOnChainError) {
22+
callbacks.onAccountNotOnChain(error, stage, operation);
23+
} else {
24+
callbacks.onError?.(error, stage, operation);
25+
}
26+
}

0 commit comments

Comments
 (0)