@cofhe/sdk 0.0.0-beta-20251027110729
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/CHANGELOG.md +47 -0
- package/adapters/ethers5.test.ts +174 -0
- package/adapters/ethers5.ts +36 -0
- package/adapters/ethers6.test.ts +169 -0
- package/adapters/ethers6.ts +36 -0
- package/adapters/hardhat-node.ts +167 -0
- package/adapters/hardhat.hh2.test.ts +159 -0
- package/adapters/hardhat.ts +37 -0
- package/adapters/index.test.ts +25 -0
- package/adapters/index.ts +5 -0
- package/adapters/smartWallet.ts +91 -0
- package/adapters/test-utils.ts +53 -0
- package/adapters/types.ts +6 -0
- package/adapters/wagmi.test.ts +156 -0
- package/adapters/wagmi.ts +17 -0
- package/chains/chains/arbSepolia.ts +14 -0
- package/chains/chains/baseSepolia.ts +14 -0
- package/chains/chains/hardhat.ts +15 -0
- package/chains/chains/sepolia.ts +14 -0
- package/chains/chains.test.ts +49 -0
- package/chains/defineChain.ts +18 -0
- package/chains/index.ts +33 -0
- package/chains/types.ts +32 -0
- package/core/baseBuilder.ts +138 -0
- package/core/client.test.ts +298 -0
- package/core/client.ts +308 -0
- package/core/config.test.ts +224 -0
- package/core/config.ts +213 -0
- package/core/decrypt/MockQueryDecrypterAbi.ts +129 -0
- package/core/decrypt/cofheMocksSealOutput.ts +57 -0
- package/core/decrypt/decryptHandleBuilder.ts +281 -0
- package/core/decrypt/decryptUtils.ts +28 -0
- package/core/decrypt/tnSealOutput.ts +59 -0
- package/core/encrypt/MockZkVerifierAbi.ts +106 -0
- package/core/encrypt/cofheMocksZkVerifySign.ts +278 -0
- package/core/encrypt/encryptInputsBuilder.test.ts +735 -0
- package/core/encrypt/encryptInputsBuilder.ts +512 -0
- package/core/encrypt/encryptUtils.ts +64 -0
- package/core/encrypt/zkPackProveVerify.ts +273 -0
- package/core/error.ts +170 -0
- package/core/fetchKeys.test.ts +212 -0
- package/core/fetchKeys.ts +170 -0
- package/core/index.ts +77 -0
- package/core/keyStore.test.ts +226 -0
- package/core/keyStore.ts +127 -0
- package/core/permits.test.ts +242 -0
- package/core/permits.ts +136 -0
- package/core/result.test.ts +180 -0
- package/core/result.ts +67 -0
- package/core/test-utils.ts +45 -0
- package/core/types.ts +352 -0
- package/core/utils.ts +88 -0
- package/dist/adapters.cjs +88 -0
- package/dist/adapters.d.cts +14558 -0
- package/dist/adapters.d.ts +14558 -0
- package/dist/adapters.js +83 -0
- package/dist/chains.cjs +101 -0
- package/dist/chains.d.cts +99 -0
- package/dist/chains.d.ts +99 -0
- package/dist/chains.js +1 -0
- package/dist/chunk-GZCQQYVI.js +93 -0
- package/dist/chunk-KFGPTJ6X.js +2295 -0
- package/dist/chunk-LU7BMUUT.js +804 -0
- package/dist/core.cjs +3174 -0
- package/dist/core.d.cts +16 -0
- package/dist/core.d.ts +16 -0
- package/dist/core.js +3 -0
- package/dist/node.cjs +3090 -0
- package/dist/node.d.cts +22 -0
- package/dist/node.d.ts +22 -0
- package/dist/node.js +90 -0
- package/dist/permit-S9CnI6MF.d.cts +333 -0
- package/dist/permit-S9CnI6MF.d.ts +333 -0
- package/dist/permits.cjs +856 -0
- package/dist/permits.d.cts +1056 -0
- package/dist/permits.d.ts +1056 -0
- package/dist/permits.js +1 -0
- package/dist/types-KImPrEIe.d.cts +48 -0
- package/dist/types-KImPrEIe.d.ts +48 -0
- package/dist/types-PhwGgQvs.d.ts +953 -0
- package/dist/types-bB7wLj0q.d.cts +953 -0
- package/dist/web.cjs +3067 -0
- package/dist/web.d.cts +22 -0
- package/dist/web.d.ts +22 -0
- package/dist/web.js +64 -0
- package/node/client.test.ts +152 -0
- package/node/config.test.ts +68 -0
- package/node/encryptInputs.test.ts +175 -0
- package/node/index.ts +96 -0
- package/node/storage.ts +51 -0
- package/package.json +120 -0
- package/permits/index.ts +67 -0
- package/permits/localstorage.test.ts +118 -0
- package/permits/permit.test.ts +474 -0
- package/permits/permit.ts +396 -0
- package/permits/sealing.test.ts +84 -0
- package/permits/sealing.ts +131 -0
- package/permits/signature.ts +79 -0
- package/permits/store.test.ts +128 -0
- package/permits/store.ts +168 -0
- package/permits/test-utils.ts +20 -0
- package/permits/types.ts +174 -0
- package/permits/utils.ts +63 -0
- package/permits/validation.test.ts +288 -0
- package/permits/validation.ts +349 -0
- package/web/client.web.test.ts +152 -0
- package/web/config.web.test.ts +71 -0
- package/web/encryptInputs.web.test.ts +195 -0
- package/web/index.ts +97 -0
- package/web/storage.ts +20 -0
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @vitest-environment happy-dom
|
|
3
|
+
*/
|
|
4
|
+
/* eslint-disable no-unused-vars */
|
|
5
|
+
import { permitStore } from '@/permits';
|
|
6
|
+
|
|
7
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
8
|
+
import { createPublicClient, createWalletClient, http, type PublicClient, type WalletClient } from 'viem';
|
|
9
|
+
import { arbitrumSepolia } from 'viem/chains';
|
|
10
|
+
import { privateKeyToAccount } from 'viem/accounts';
|
|
11
|
+
import { permits } from './permits.js';
|
|
12
|
+
|
|
13
|
+
// Type declarations for happy-dom environment
|
|
14
|
+
declare const localStorage: {
|
|
15
|
+
clear: () => void;
|
|
16
|
+
getItem: (name: string) => string | null;
|
|
17
|
+
setItem: (name: string, value: string) => void;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
// Test private keys (well-known test keys from Anvil/Hardhat)
|
|
21
|
+
const BOB_PRIVATE_KEY = '0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80'; // Bob - always issuer
|
|
22
|
+
const ALICE_PRIVATE_KEY = '0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d'; // Alice - always recipient
|
|
23
|
+
|
|
24
|
+
// Create real viem clients for Arbitrum Sepolia
|
|
25
|
+
const publicClient: PublicClient = createPublicClient({
|
|
26
|
+
chain: arbitrumSepolia,
|
|
27
|
+
transport: http(),
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
const bobWalletClient: WalletClient = createWalletClient({
|
|
31
|
+
chain: arbitrumSepolia,
|
|
32
|
+
transport: http(),
|
|
33
|
+
account: privateKeyToAccount(BOB_PRIVATE_KEY),
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
const aliceWalletClient: WalletClient = createWalletClient({
|
|
37
|
+
chain: arbitrumSepolia,
|
|
38
|
+
transport: http(),
|
|
39
|
+
account: privateKeyToAccount(ALICE_PRIVATE_KEY),
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
// Helper to get the wallet addresses
|
|
43
|
+
const bobAddress = bobWalletClient.account!.address;
|
|
44
|
+
const aliceAddress = aliceWalletClient.account!.address;
|
|
45
|
+
const chainId = 421614; // Arbitrum Sepolia
|
|
46
|
+
|
|
47
|
+
describe('Core Permits Tests', () => {
|
|
48
|
+
beforeEach(() => {
|
|
49
|
+
// Clear localStorage and reset stores
|
|
50
|
+
localStorage.clear();
|
|
51
|
+
permitStore.store.setState({ permits: {}, activePermitHash: {} });
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
afterEach(() => {
|
|
55
|
+
localStorage.clear();
|
|
56
|
+
permitStore.store.setState({ permits: {}, activePermitHash: {} });
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
describe('Permit Creation', () => {
|
|
60
|
+
it('should create and store self permit', async () => {
|
|
61
|
+
const permit = await permits.createSelf(
|
|
62
|
+
{ name: 'Test Self Permit', issuer: bobAddress },
|
|
63
|
+
publicClient,
|
|
64
|
+
bobWalletClient
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
expect(permit).toBeDefined();
|
|
68
|
+
expect(permit.name).toBe('Test Self Permit');
|
|
69
|
+
expect(permit.type).toBe('self');
|
|
70
|
+
expect(permit.issuer).toBe(bobAddress);
|
|
71
|
+
expect(permit.issuerSignature).toBeDefined();
|
|
72
|
+
expect(permit.issuerSignature).not.toBe('0x');
|
|
73
|
+
|
|
74
|
+
// Verify localStorage
|
|
75
|
+
const storedData = localStorage.getItem('cofhesdk-permits');
|
|
76
|
+
expect(storedData).toBeDefined();
|
|
77
|
+
const parsedData = JSON.parse(storedData!);
|
|
78
|
+
expect(parsedData.state.permits[chainId][bobAddress]).toBeDefined();
|
|
79
|
+
expect(parsedData.state.activePermitHash[chainId][bobAddress]).toBeDefined();
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('should create and store sharing permit', async () => {
|
|
83
|
+
const permit = await permits.createSharing(
|
|
84
|
+
{
|
|
85
|
+
name: 'Test Sharing Permit',
|
|
86
|
+
issuer: bobAddress,
|
|
87
|
+
recipient: aliceAddress,
|
|
88
|
+
},
|
|
89
|
+
publicClient,
|
|
90
|
+
bobWalletClient
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
expect(permit.name).toBe('Test Sharing Permit');
|
|
94
|
+
expect(permit.type).toBe('sharing');
|
|
95
|
+
expect(permit.issuer).toBe(bobAddress);
|
|
96
|
+
expect(permit.recipient).toBe(aliceAddress);
|
|
97
|
+
expect(permit.issuerSignature).toBeDefined();
|
|
98
|
+
expect(permit.issuerSignature).not.toBe('0x');
|
|
99
|
+
|
|
100
|
+
// Verify localStorage
|
|
101
|
+
const storedData = localStorage.getItem('cofhesdk-permits');
|
|
102
|
+
expect(storedData).toBeDefined();
|
|
103
|
+
const parsedData = JSON.parse(storedData!);
|
|
104
|
+
expect(parsedData.state.permits[chainId][bobAddress]).toBeDefined();
|
|
105
|
+
expect(parsedData.state.activePermitHash[chainId][bobAddress]).toBeDefined();
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('should import shared permit from JSON string', async () => {
|
|
109
|
+
// First create a sharing permit to import
|
|
110
|
+
const sharingPermit = await permits.createSharing(
|
|
111
|
+
{
|
|
112
|
+
name: 'Original Sharing Permit',
|
|
113
|
+
issuer: bobAddress,
|
|
114
|
+
recipient: aliceAddress,
|
|
115
|
+
},
|
|
116
|
+
publicClient,
|
|
117
|
+
bobWalletClient
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
// Export the permit as JSON string
|
|
121
|
+
const permitJson = JSON.stringify({
|
|
122
|
+
name: sharingPermit.name,
|
|
123
|
+
type: sharingPermit.type,
|
|
124
|
+
issuer: sharingPermit.issuer,
|
|
125
|
+
expiration: sharingPermit.expiration,
|
|
126
|
+
recipient: sharingPermit.recipient,
|
|
127
|
+
validatorId: sharingPermit.validatorId,
|
|
128
|
+
validatorContract: sharingPermit.validatorContract,
|
|
129
|
+
issuerSignature: sharingPermit.issuerSignature,
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
// Import the permit as Alice (recipient)
|
|
133
|
+
const permit = await permits.importShared(permitJson, publicClient, aliceWalletClient);
|
|
134
|
+
|
|
135
|
+
expect(permit.name).toBe('Original Sharing Permit');
|
|
136
|
+
expect(permit.type).toBe('recipient');
|
|
137
|
+
expect(permit.issuer).toBe(bobAddress);
|
|
138
|
+
expect(permit.recipient).toBe(aliceAddress);
|
|
139
|
+
expect(permit.recipientSignature).toBeDefined();
|
|
140
|
+
expect(permit.recipientSignature).not.toBe('0x');
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
describe('Permit Retrieval', () => {
|
|
145
|
+
let createdPermit: any;
|
|
146
|
+
let permitHash: string;
|
|
147
|
+
|
|
148
|
+
beforeEach(async () => {
|
|
149
|
+
// Create a real permit for testing
|
|
150
|
+
createdPermit = await permits.createSelf(
|
|
151
|
+
{ name: 'Test Permit', issuer: bobAddress },
|
|
152
|
+
publicClient,
|
|
153
|
+
bobWalletClient
|
|
154
|
+
);
|
|
155
|
+
permitHash = permits.getHash(createdPermit);
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it('should get permit by hash', async () => {
|
|
159
|
+
const permit = await permits.getPermit(chainId, bobAddress, permitHash);
|
|
160
|
+
expect(permit?.name).toBe('Test Permit');
|
|
161
|
+
expect(permit?.type).toBe('self');
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
it('should get all permits', async () => {
|
|
165
|
+
const allPermits = await permits.getPermits(chainId, bobAddress);
|
|
166
|
+
expect(Object.keys(allPermits).length).toBeGreaterThan(0);
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it('should get active permit', async () => {
|
|
170
|
+
const permit = await permits.getActivePermit(chainId, bobAddress);
|
|
171
|
+
expect(permit?.name).toBe('Test Permit');
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it('should get active permit hash', async () => {
|
|
175
|
+
const hash = await permits.getActivePermitHash(chainId, bobAddress);
|
|
176
|
+
expect(typeof hash).toBe('string');
|
|
177
|
+
});
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
describe('localStorage Integration', () => {
|
|
181
|
+
it('should persist permits to localStorage', async () => {
|
|
182
|
+
const createdPermit = await permits.createSelf(
|
|
183
|
+
{ name: 'Test Permit', issuer: bobAddress },
|
|
184
|
+
publicClient,
|
|
185
|
+
bobWalletClient
|
|
186
|
+
);
|
|
187
|
+
|
|
188
|
+
const storedData = localStorage.getItem('cofhesdk-permits');
|
|
189
|
+
expect(storedData).toBeDefined();
|
|
190
|
+
|
|
191
|
+
const parsedData = JSON.parse(storedData!);
|
|
192
|
+
expect(parsedData.state.permits[chainId][bobAddress]).toBeDefined();
|
|
193
|
+
expect(parsedData.state.activePermitHash[chainId][bobAddress]).toBeDefined();
|
|
194
|
+
|
|
195
|
+
// Verify the permit data structure
|
|
196
|
+
const permitKeys = Object.keys(parsedData.state.permits[chainId][bobAddress]);
|
|
197
|
+
expect(permitKeys.length).toBeGreaterThan(0);
|
|
198
|
+
|
|
199
|
+
const permitHash = permits.getHash(createdPermit);
|
|
200
|
+
const serializedPermit = permits.serialize(createdPermit);
|
|
201
|
+
expect(parsedData.state.permits[chainId][bobAddress][permitHash]).toEqual(serializedPermit);
|
|
202
|
+
});
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
describe('Real Network Integration', () => {
|
|
206
|
+
it('should create permit with real EIP712 domain from Arbitrum Sepolia', async () => {
|
|
207
|
+
const permit = await permits.createSelf(
|
|
208
|
+
{ name: 'Real Network Permit', issuer: bobAddress },
|
|
209
|
+
publicClient,
|
|
210
|
+
bobWalletClient
|
|
211
|
+
);
|
|
212
|
+
|
|
213
|
+
expect(permit._signedDomain).toBeDefined();
|
|
214
|
+
expect(permit._signedDomain?.chainId).toBe(chainId);
|
|
215
|
+
expect(permit._signedDomain?.name).toBeDefined();
|
|
216
|
+
expect(permit._signedDomain?.version).toBeDefined();
|
|
217
|
+
expect(permit._signedDomain?.verifyingContract).toBeDefined();
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
it('should handle multiple permits on real network', async () => {
|
|
221
|
+
// Create multiple permits
|
|
222
|
+
await permits.createSelf({ name: 'Permit 1', issuer: bobAddress }, publicClient, bobWalletClient);
|
|
223
|
+
await permits.createSharing(
|
|
224
|
+
{
|
|
225
|
+
name: 'Permit 2',
|
|
226
|
+
issuer: bobAddress,
|
|
227
|
+
recipient: aliceAddress,
|
|
228
|
+
},
|
|
229
|
+
publicClient,
|
|
230
|
+
bobWalletClient
|
|
231
|
+
);
|
|
232
|
+
|
|
233
|
+
// Verify both permits exist
|
|
234
|
+
const allPermits = await permits.getPermits(chainId, bobAddress);
|
|
235
|
+
expect(Object.keys(allPermits).length).toBeGreaterThanOrEqual(2);
|
|
236
|
+
|
|
237
|
+
// Verify active permit is the last created one
|
|
238
|
+
const activePermit = await permits.getActivePermit(chainId, bobAddress);
|
|
239
|
+
expect(activePermit?.name).toBe('Permit 2');
|
|
240
|
+
});
|
|
241
|
+
});
|
|
242
|
+
});
|
package/core/permits.ts
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/* eslint-disable no-unused-vars */
|
|
2
|
+
import {
|
|
3
|
+
type ImportSharedPermitOptions,
|
|
4
|
+
PermitUtils,
|
|
5
|
+
type CreateSelfPermitOptions,
|
|
6
|
+
type CreateSharingPermitOptions,
|
|
7
|
+
type Permit,
|
|
8
|
+
permitStore,
|
|
9
|
+
type SerializedPermit,
|
|
10
|
+
} from '@/permits';
|
|
11
|
+
|
|
12
|
+
import { type PublicClient, type WalletClient } from 'viem';
|
|
13
|
+
|
|
14
|
+
// HELPERS
|
|
15
|
+
|
|
16
|
+
// Helper function to store permit as active permit
|
|
17
|
+
const storeActivePermit = async (permit: Permit, publicClient: any, walletClient: any) => {
|
|
18
|
+
const chainId = await publicClient.getChainId();
|
|
19
|
+
const account = walletClient.account!.address;
|
|
20
|
+
|
|
21
|
+
permitStore.setPermit(chainId, account, permit);
|
|
22
|
+
permitStore.setActivePermitHash(chainId, account, PermitUtils.getHash(permit));
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
// Generic function to handle permit creation with error handling
|
|
26
|
+
const createPermitWithSign = async <T>(
|
|
27
|
+
options: T,
|
|
28
|
+
publicClient: PublicClient,
|
|
29
|
+
walletClient: WalletClient,
|
|
30
|
+
permitMethod: (options: T, publicClient: PublicClient, walletClient: WalletClient) => Promise<Permit>
|
|
31
|
+
): Promise<Permit> => {
|
|
32
|
+
const permit = await permitMethod(options, publicClient, walletClient);
|
|
33
|
+
await storeActivePermit(permit, publicClient, walletClient);
|
|
34
|
+
return permit;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
// CREATE
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Create a permit usable by the connected user
|
|
41
|
+
* Stores the permit and selects it as the active permit
|
|
42
|
+
* @param options - The options for creating a self permit
|
|
43
|
+
* @returns The created permit or error
|
|
44
|
+
*/
|
|
45
|
+
const createSelf = async (
|
|
46
|
+
options: CreateSelfPermitOptions,
|
|
47
|
+
publicClient: PublicClient,
|
|
48
|
+
walletClient: WalletClient
|
|
49
|
+
): Promise<Permit> => {
|
|
50
|
+
return createPermitWithSign(options, publicClient, walletClient, PermitUtils.createSelfAndSign);
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const createSharing = async (
|
|
54
|
+
options: CreateSharingPermitOptions,
|
|
55
|
+
publicClient: PublicClient,
|
|
56
|
+
walletClient: WalletClient
|
|
57
|
+
): Promise<Permit> => {
|
|
58
|
+
return createPermitWithSign(options, publicClient, walletClient, PermitUtils.createSharingAndSign);
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const importShared = async (
|
|
62
|
+
options: ImportSharedPermitOptions | any | string,
|
|
63
|
+
publicClient: PublicClient,
|
|
64
|
+
walletClient: WalletClient
|
|
65
|
+
): Promise<Permit> => {
|
|
66
|
+
return createPermitWithSign(options, publicClient, walletClient, PermitUtils.importSharedAndSign);
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
// PERMIT UTILS
|
|
70
|
+
|
|
71
|
+
const getHash = (permit: Permit) => {
|
|
72
|
+
return PermitUtils.getHash(permit);
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const serialize = (permit: Permit) => {
|
|
76
|
+
return PermitUtils.serialize(permit);
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const deserialize = (serialized: SerializedPermit) => {
|
|
80
|
+
return PermitUtils.deserialize(serialized);
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
// GET
|
|
84
|
+
|
|
85
|
+
const getPermit = async (chainId: number, account: string, hash: string): Promise<Permit | undefined> => {
|
|
86
|
+
return permitStore.getPermit(chainId, account, hash);
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const getPermits = async (chainId: number, account: string): Promise<Record<string, Permit>> => {
|
|
90
|
+
return permitStore.getPermits(chainId, account);
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
const getActivePermit = async (chainId: number, account: string): Promise<Permit | undefined> => {
|
|
94
|
+
return permitStore.getActivePermit(chainId, account);
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
const getActivePermitHash = async (chainId: number, account: string): Promise<string | undefined> => {
|
|
98
|
+
return permitStore.getActivePermitHash(chainId, account);
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
const selectActivePermit = async (chainId: number, account: string, hash: string): Promise<void> => {
|
|
102
|
+
await permitStore.setActivePermitHash(chainId, account, hash);
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
// REMOVE
|
|
106
|
+
|
|
107
|
+
const removePermit = async (chainId: number, account: string, hash: string): Promise<void> => {
|
|
108
|
+
await permitStore.removePermit(chainId, account, hash);
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
const removeActivePermit = async (chainId: number, account: string): Promise<void> => {
|
|
112
|
+
await permitStore.removeActivePermitHash(chainId, account);
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
// EXPORT
|
|
116
|
+
|
|
117
|
+
export const permits = {
|
|
118
|
+
getSnapshot: permitStore.store.getState,
|
|
119
|
+
subscribe: permitStore.store.subscribe,
|
|
120
|
+
|
|
121
|
+
createSelf,
|
|
122
|
+
createSharing,
|
|
123
|
+
importShared,
|
|
124
|
+
|
|
125
|
+
getHash,
|
|
126
|
+
serialize,
|
|
127
|
+
deserialize,
|
|
128
|
+
|
|
129
|
+
getPermit,
|
|
130
|
+
getPermits,
|
|
131
|
+
getActivePermit,
|
|
132
|
+
getActivePermitHash,
|
|
133
|
+
removePermit,
|
|
134
|
+
selectActivePermit,
|
|
135
|
+
removeActivePermit,
|
|
136
|
+
};
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { ResultOk, ResultErr, ResultErrOrInternal, resultWrapper, resultWrapperSync, type Result } from './result.js';
|
|
3
|
+
import { CofhesdkError, CofhesdkErrorCode } from './error.js';
|
|
4
|
+
|
|
5
|
+
export const expectResultError = <T>(result: Result<T>, errorPartial: string) => {
|
|
6
|
+
expect(result.success).to.eq(false, 'Result should be an error');
|
|
7
|
+
expect(result.error!.message).to.include(errorPartial, `Error should contain error partial: ${errorPartial}`);
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export const expectResultSuccess = <T>(result: Result<T>): T => {
|
|
11
|
+
expect(result.success).to.eq(true, 'Result should be a success');
|
|
12
|
+
return result.data!;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export const expectResultValue = <T>(result: Result<T>, value: T): T => {
|
|
16
|
+
expect(result.success).to.eq(true, 'Result should be a success');
|
|
17
|
+
expect(result.data).to.eq(value, `Result should have the expected value ${value}`);
|
|
18
|
+
return result.data!;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
describe('Result Type', () => {
|
|
22
|
+
describe('ResultOk', () => {
|
|
23
|
+
it('should create a successful result', () => {
|
|
24
|
+
const result = ResultOk('test data');
|
|
25
|
+
expect(result.success).toBe(true);
|
|
26
|
+
expect(result.data).toBe('test data');
|
|
27
|
+
expect(result.error).toBe(null);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('should handle different data types', () => {
|
|
31
|
+
const stringResult = ResultOk('string');
|
|
32
|
+
const numberResult = ResultOk(42);
|
|
33
|
+
const objectResult = ResultOk({ key: 'value' });
|
|
34
|
+
const nullResult = ResultOk(null);
|
|
35
|
+
|
|
36
|
+
expect(stringResult.data).toBe('string');
|
|
37
|
+
expect(numberResult.data).toBe(42);
|
|
38
|
+
expect(objectResult.data).toEqual({ key: 'value' });
|
|
39
|
+
expect(nullResult.data).toBe(null);
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
describe('ResultErr', () => {
|
|
44
|
+
it('should create an error result', () => {
|
|
45
|
+
const error = new CofhesdkError({
|
|
46
|
+
code: CofhesdkErrorCode.InternalError,
|
|
47
|
+
message: 'Test error',
|
|
48
|
+
});
|
|
49
|
+
const result = ResultErr(error);
|
|
50
|
+
|
|
51
|
+
expect(result.success).toBe(false);
|
|
52
|
+
expect(result.data).toBe(null);
|
|
53
|
+
expect(result.error).toBe(error);
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
describe('Error Utilities', () => {
|
|
59
|
+
describe('ResultErrOrInternal', () => {
|
|
60
|
+
it('should wrap unknown errors as internal errors', () => {
|
|
61
|
+
const result = ResultErrOrInternal(new Error('string error'));
|
|
62
|
+
expect(result.success).toBe(false);
|
|
63
|
+
expect(result.error!).toBeInstanceOf(CofhesdkError);
|
|
64
|
+
expect(result.error!.code).toBe(CofhesdkErrorCode.InternalError);
|
|
65
|
+
expect(result.error!.message).toBe('An internal error occurred | Caused by: string error');
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('should preserve CofhesdkError instances', () => {
|
|
69
|
+
const originalError = new CofhesdkError({
|
|
70
|
+
code: CofhesdkErrorCode.MissingPublicClient,
|
|
71
|
+
message: 'Public client missing',
|
|
72
|
+
});
|
|
73
|
+
const result = ResultErrOrInternal(originalError);
|
|
74
|
+
expect(result.success).toBe(false);
|
|
75
|
+
expect(result.error!).toBe(originalError);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('should handle Error instances', () => {
|
|
79
|
+
const error = new Error('Standard error');
|
|
80
|
+
const result = ResultErrOrInternal(error);
|
|
81
|
+
expect(result.success).toBe(false);
|
|
82
|
+
expect(result.error!).toBeInstanceOf(CofhesdkError);
|
|
83
|
+
expect(result.error!.cause).toBe(error);
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
describe('Result Wrappers', () => {
|
|
89
|
+
describe('resultWrapperSync', () => {
|
|
90
|
+
it('should wrap successful sync function', () => {
|
|
91
|
+
const result = resultWrapperSync(() => 'success');
|
|
92
|
+
expect(result.success).toBe(true);
|
|
93
|
+
expect(result.data).toBe('success');
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('should wrap failing sync function', () => {
|
|
97
|
+
const result = resultWrapperSync(() => {
|
|
98
|
+
throw new Error('Sync error');
|
|
99
|
+
});
|
|
100
|
+
expect(result.success).toBe(false);
|
|
101
|
+
expect(result.error).toBeInstanceOf(CofhesdkError);
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
describe('resultWrapper', () => {
|
|
106
|
+
it('should wrap successful async function', async () => {
|
|
107
|
+
const result = await resultWrapper(async () => 'async success');
|
|
108
|
+
expect(result.success).toBe(true);
|
|
109
|
+
expect(result.data).toBe('async success');
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it('should wrap failing async function', async () => {
|
|
113
|
+
const result = await resultWrapper(async () => {
|
|
114
|
+
throw new Error('Async error');
|
|
115
|
+
});
|
|
116
|
+
expect(result.success).toBe(false);
|
|
117
|
+
expect(result.error).toBeInstanceOf(CofhesdkError);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('should handle async operations', async () => {
|
|
121
|
+
const result = await resultWrapper(async () => {
|
|
122
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
123
|
+
return 'delayed result';
|
|
124
|
+
});
|
|
125
|
+
expect(result.success).toBe(true);
|
|
126
|
+
expect(result.data).toBe('delayed result');
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it('should handle Promise rejections', async () => {
|
|
130
|
+
const result = await resultWrapper(async () => {
|
|
131
|
+
return Promise.reject(new Error('Promise rejection'));
|
|
132
|
+
});
|
|
133
|
+
expect(result.success).toBe(false);
|
|
134
|
+
expect(result.error).toBeInstanceOf(CofhesdkError);
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
describe('Result Type Guards', () => {
|
|
140
|
+
it('should correctly identify success results', () => {
|
|
141
|
+
const successResult = ResultOk('data');
|
|
142
|
+
expect(successResult.success).toBe(true);
|
|
143
|
+
expect(successResult.data).toBe('data');
|
|
144
|
+
expect(successResult.error).toBe(null);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it('should correctly identify error results', () => {
|
|
148
|
+
const errorResult = ResultErr(
|
|
149
|
+
new CofhesdkError({
|
|
150
|
+
code: CofhesdkErrorCode.InternalError,
|
|
151
|
+
message: 'Test error',
|
|
152
|
+
})
|
|
153
|
+
);
|
|
154
|
+
expect(errorResult.success).toBe(false);
|
|
155
|
+
expect(errorResult.data).toBe(null);
|
|
156
|
+
expect(errorResult.error).toBeInstanceOf(CofhesdkError);
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
describe('Result Test Utilities', () => {
|
|
161
|
+
it('expectResultSuccess should return data for success', () => {
|
|
162
|
+
const result = ResultOk('data');
|
|
163
|
+
expect(expectResultSuccess(result)).toBe('data');
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it('expectResultValue should validate and return matching data', () => {
|
|
167
|
+
const result = ResultOk('expected');
|
|
168
|
+
expect(expectResultValue(result, 'expected')).toBe('expected');
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it('expectResultError should validate error messages', () => {
|
|
172
|
+
const result = ResultErr(
|
|
173
|
+
new CofhesdkError({
|
|
174
|
+
code: CofhesdkErrorCode.InternalError,
|
|
175
|
+
message: 'Test error message',
|
|
176
|
+
})
|
|
177
|
+
);
|
|
178
|
+
expectResultError(result, 'Test error');
|
|
179
|
+
});
|
|
180
|
+
});
|
package/core/result.ts
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/* eslint-disable no-unused-vars */
|
|
2
|
+
import { CofhesdkError, CofhesdkErrorCode } from './error.js';
|
|
3
|
+
|
|
4
|
+
export type Result<T> = { success: true; data: T; error: null } | { success: false; data: null; error: CofhesdkError };
|
|
5
|
+
|
|
6
|
+
export const ResultErr = <T>(error: CofhesdkError): Result<T> => ({
|
|
7
|
+
success: false,
|
|
8
|
+
data: null,
|
|
9
|
+
error,
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
export const ResultOk = <T>(data: T): Result<T> => ({
|
|
13
|
+
success: true,
|
|
14
|
+
data,
|
|
15
|
+
error: null,
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
export const ResultErrOrInternal = <T>(error: unknown): Result<T> => {
|
|
19
|
+
return ResultErr(CofhesdkError.fromError(error));
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export const ResultHttpError = (error: unknown, url: string, status?: number): CofhesdkError => {
|
|
23
|
+
if (error instanceof CofhesdkError) return error;
|
|
24
|
+
|
|
25
|
+
const message = status ? `HTTP error ${status} from ${url}` : `HTTP request failed for ${url}`;
|
|
26
|
+
|
|
27
|
+
return new CofhesdkError({
|
|
28
|
+
code: CofhesdkErrorCode.InternalError,
|
|
29
|
+
message,
|
|
30
|
+
cause: error instanceof Error ? error : undefined,
|
|
31
|
+
});
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export const ResultValidationError = (message: string): CofhesdkError => {
|
|
35
|
+
return new CofhesdkError({
|
|
36
|
+
code: CofhesdkErrorCode.InvalidPermitData,
|
|
37
|
+
message,
|
|
38
|
+
});
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
// Async resultWrapper
|
|
42
|
+
export const resultWrapper = async <T>(
|
|
43
|
+
tryFn: () => Promise<T>,
|
|
44
|
+
catchFn?: (error: CofhesdkError) => void,
|
|
45
|
+
finallyFn?: () => void
|
|
46
|
+
): Promise<Result<T>> => {
|
|
47
|
+
try {
|
|
48
|
+
const result = await tryFn();
|
|
49
|
+
return ResultOk(result);
|
|
50
|
+
} catch (error) {
|
|
51
|
+
const result = ResultErrOrInternal(error);
|
|
52
|
+
catchFn?.(result.error!);
|
|
53
|
+
return result as Result<T>;
|
|
54
|
+
} finally {
|
|
55
|
+
finallyFn?.();
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
// Sync resultWrapper
|
|
60
|
+
export const resultWrapperSync = <T>(fn: () => T): Result<T> => {
|
|
61
|
+
try {
|
|
62
|
+
const result = fn();
|
|
63
|
+
return ResultOk(result);
|
|
64
|
+
} catch (error) {
|
|
65
|
+
return ResultErrOrInternal(error);
|
|
66
|
+
}
|
|
67
|
+
};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { expect } from 'vitest';
|
|
2
|
+
import { type Result } from './result.js';
|
|
3
|
+
import { CofhesdkError, CofhesdkErrorCode } from './error.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Assert that a Result is successful and return its data
|
|
7
|
+
* Useful for type narrowing in tests
|
|
8
|
+
*/
|
|
9
|
+
export const expectResultSuccess = <T>(result: Result<T>): NonNullable<T> => {
|
|
10
|
+
expect(result.error, `Result error: ${result.error?.toString()}`).toBe(null);
|
|
11
|
+
expect(result.success, `Result: ${bigintSafeJsonStringify(result)}`).toBe(true);
|
|
12
|
+
expect(result.data, `Result: ${bigintSafeJsonStringify(result)}`).not.toBe(null);
|
|
13
|
+
return result.data!;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Assert that a Result is an error and optionally verify the error details
|
|
18
|
+
*/
|
|
19
|
+
export const expectResultError = <T>(
|
|
20
|
+
result: Result<T>,
|
|
21
|
+
errorCode?: CofhesdkErrorCode,
|
|
22
|
+
errorMessage?: string
|
|
23
|
+
): CofhesdkError => {
|
|
24
|
+
expect(result.success, `Result: ${bigintSafeJsonStringify(result)}`).toBe(false);
|
|
25
|
+
expect(result.data, `Result: ${bigintSafeJsonStringify(result)}`).toBe(null);
|
|
26
|
+
expect(result.error, `Result: ${bigintSafeJsonStringify(result)}`).not.toBe(null);
|
|
27
|
+
const error = result.error as CofhesdkError;
|
|
28
|
+
expect(error, `Result error: ${result.error?.toString()}`).toBeInstanceOf(CofhesdkError);
|
|
29
|
+
if (errorCode) {
|
|
30
|
+
expect(error.code, `Result error: ${result.error?.toString()}`).toBe(errorCode);
|
|
31
|
+
}
|
|
32
|
+
if (errorMessage) {
|
|
33
|
+
expect(error.message, `Result error: ${result.error?.toString()}`).toBe(errorMessage);
|
|
34
|
+
}
|
|
35
|
+
return error;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const bigintSafeJsonStringify = (value: unknown): string => {
|
|
39
|
+
return JSON.stringify(value, (key, value) => {
|
|
40
|
+
if (typeof value === 'bigint') {
|
|
41
|
+
return `${value}n`;
|
|
42
|
+
}
|
|
43
|
+
return value;
|
|
44
|
+
});
|
|
45
|
+
};
|