@cofhe/sdk 0.6.0 → 0.7.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.
- package/CHANGELOG.md +49 -0
- package/acps/acp.ts +411 -0
- package/acps/index.ts +70 -0
- package/{permits → acps}/onchain-utils.ts +49 -24
- package/acps/sealing.ts +90 -0
- package/acps/signature.ts +89 -0
- package/acps/store.ts +172 -0
- package/acps/test/acp.test.ts +615 -0
- package/acps/test/localstorage.test.ts +105 -0
- package/acps/test/sealing.test.ts +77 -0
- package/acps/test/store.test.ts +88 -0
- package/acps/test/validation.test.ts +361 -0
- package/acps/test-utils.ts +32 -0
- package/acps/types.ts +252 -0
- package/acps/validation.ts +392 -0
- package/adapters/test/ethers5.test.ts +4 -1
- package/adapters/test/ethers6.test.ts +4 -1
- package/adapters/test/wagmi.test.ts +5 -2
- package/chains/chains/stagingCofhe.ts +21 -0
- package/chains/index.ts +3 -1
- package/chains/test/chains.test.ts +2 -1
- package/core/acps.ts +625 -0
- package/core/client.ts +136 -39
- package/core/clientTypes.ts +52 -41
- package/core/config.ts +66 -5
- package/core/debug.ts +72 -0
- package/core/decrypt/MockThresholdNetworkAbi.ts +20 -11
- package/core/decrypt/apiError.ts +104 -0
- package/core/decrypt/cofheMocksDecryptForTx.ts +11 -11
- package/core/decrypt/cofheMocksDecryptForView.ts +7 -7
- package/core/decrypt/decryptForTxBuilder.ts +102 -102
- package/core/decrypt/decryptForViewBuilder.ts +90 -90
- package/core/decrypt/submitRetry.ts +38 -30
- package/core/decrypt/tnDecryptV1.ts +5 -5
- package/core/decrypt/tnDecryptV2.ts +45 -34
- package/core/decrypt/tnSealOutputV1.ts +4 -3
- package/core/decrypt/tnSealOutputV2.ts +44 -31
- package/core/encrypt/cofheMocksZkVerifySign.ts +59 -74
- package/core/encrypt/encryptInputsBuilder.ts +86 -52
- package/core/encrypt/zkPackProveVerify.ts +25 -18
- package/core/error.ts +34 -6
- package/core/index.ts +14 -14
- package/core/test/acpDefaults.test.ts +52 -0
- package/core/test/acps.test.ts +596 -0
- package/core/test/apiError.test.ts +130 -0
- package/core/test/client.test.ts +22 -19
- package/core/test/config.test.ts +25 -5
- package/core/test/decrypt.test.ts +40 -35
- package/core/test/decryptBuilders.test.ts +68 -68
- package/core/test/decryptErrorCodes.test.ts +217 -0
- package/core/test/encryptInputsBuilder.test.ts +72 -41
- package/core/test/pollCallbacks.test.ts +89 -18
- package/core/test/stagingRedirect.ts +18 -0
- package/core/test/submitRetry.test.ts +182 -0
- package/core/types.ts +9 -69
- package/dist/acp-Wi6isVQI.d.cts +407 -0
- package/dist/acp-Wi6isVQI.d.ts +407 -0
- package/dist/acps.cjs +1081 -0
- package/dist/acps.d.cts +480 -0
- package/dist/acps.d.ts +480 -0
- package/dist/acps.js +2 -0
- package/dist/chains.cjs +14 -1
- package/dist/chains.d.cts +30 -1
- package/dist/chains.d.ts +30 -1
- package/dist/chains.js +1 -1
- package/dist/{chunk-PE5V5CCV.js → chunk-43USWPEH.js} +1000 -604
- package/dist/{chunk-MTRAXQXC.js → chunk-N6IDQRRU.js} +14 -2
- package/dist/chunk-Q7CBWGQX.js +1029 -0
- package/dist/{clientTypes-CyUvRRzA.d.ts → clientTypes-BN3nbzYM.d.ts} +342 -165
- package/dist/{clientTypes-BDy1qIBu.d.cts → clientTypes-CYZjFznO.d.cts} +342 -165
- package/dist/core.cjs +1431 -1026
- package/dist/core.d.cts +55 -10
- package/dist/core.d.ts +55 -10
- package/dist/core.js +3 -3
- package/dist/node.cjs +1356 -976
- package/dist/node.d.cts +2 -2
- package/dist/node.d.ts +2 -2
- package/dist/node.js +3 -3
- package/dist/web.cjs +1356 -976
- package/dist/web.d.cts +2 -2
- package/dist/web.d.ts +2 -2
- package/dist/web.js +3 -3
- package/node/test/inherited.test.ts +75 -65
- package/node/test/tfheinit.test.ts +23 -8
- package/package.json +6 -6
- package/web/test/client.web.test.ts +5 -1
- package/web/test/inherited.web.test.ts +75 -65
- package/web/test/tfheinit.web.test.ts +14 -5
- package/web/test/worker.config.web.test.ts +38 -23
- package/web/test/worker.output.web.test.ts +25 -24
- package/core/encrypt/encryptUtils.ts +0 -67
- package/core/permits.ts +0 -206
- package/core/test/permits.test.ts +0 -534
- package/dist/chunk-VB62WYPL.js +0 -978
- package/dist/permit-DnVMDT5h.d.cts +0 -376
- package/dist/permit-DnVMDT5h.d.ts +0 -376
- package/dist/permits.cjs +0 -1026
- package/dist/permits.d.cts +0 -353
- package/dist/permits.d.ts +0 -353
- package/dist/permits.js +0 -2
- package/permits/index.ts +0 -68
- package/permits/permit.ts +0 -385
- package/permits/sealing.ts +0 -131
- package/permits/signature.ts +0 -79
- package/permits/store.ts +0 -157
- package/permits/test/localstorage.test.ts +0 -113
- package/permits/test/permit.test.ts +0 -557
- package/permits/test/sealing.test.ts +0 -84
- package/permits/test/store.test.ts +0 -88
- package/permits/test/validation.test.ts +0 -361
- package/permits/test-utils.ts +0 -28
- package/permits/types.ts +0 -204
- package/permits/validation.ts +0 -327
- /package/{permits → acps}/utils.ts +0 -0
|
@@ -0,0 +1,596 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @vitest-environment happy-dom
|
|
3
|
+
*/
|
|
4
|
+
import { acpStore } from '@/acps';
|
|
5
|
+
|
|
6
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
7
|
+
|
|
8
|
+
// ACP domain resolution requires an upgraded on-chain ACL (domain ("ACL","2") served by the ACL itself).
|
|
9
|
+
// Public testnets still run the V2 contracts, so the domain fetch is stubbed here —
|
|
10
|
+
// these tests cover the acp orchestration flow, not on-chain domain resolution
|
|
11
|
+
// (exercised e2e against mocks in test/hardhat-plugin-test).
|
|
12
|
+
vi.mock('../../acps/onchain-utils.js', async (importOriginal) => ({
|
|
13
|
+
...(await importOriginal<typeof import('../../acps/onchain-utils.js')>()),
|
|
14
|
+
getAclEIP712Domain: async () => ({
|
|
15
|
+
name: 'ACL',
|
|
16
|
+
version: '2',
|
|
17
|
+
chainId: 421614,
|
|
18
|
+
// arbitrary non-zero address — signatures are never verified on-chain in these tests
|
|
19
|
+
verifyingContract: '0x1111111111111111111111111111111111111111' as `0x${string}`,
|
|
20
|
+
}),
|
|
21
|
+
}));
|
|
22
|
+
import { createPublicClient, createWalletClient, http, type PublicClient, type WalletClient } from 'viem';
|
|
23
|
+
import { arbitrumSepolia } from 'viem/chains';
|
|
24
|
+
import { privateKeyToAccount } from 'viem/accounts';
|
|
25
|
+
import { acps } from '../acps.js';
|
|
26
|
+
|
|
27
|
+
// Type declarations for happy-dom environment
|
|
28
|
+
declare const localStorage: {
|
|
29
|
+
clear: () => void;
|
|
30
|
+
getItem: (name: string) => string | null;
|
|
31
|
+
setItem: (name: string, value: string) => void;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
// Test private keys (well-known test keys from Anvil/Hardhat)
|
|
35
|
+
const BOB_PRIVATE_KEY = '0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80'; // Bob - always issuer
|
|
36
|
+
const ALICE_PRIVATE_KEY = '0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d'; // Alice - always recipient
|
|
37
|
+
|
|
38
|
+
// Create real viem clients for Arbitrum Sepolia
|
|
39
|
+
const publicClient: PublicClient = createPublicClient({
|
|
40
|
+
chain: arbitrumSepolia,
|
|
41
|
+
transport: http(),
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
const bobWalletClient: WalletClient = createWalletClient({
|
|
45
|
+
chain: arbitrumSepolia,
|
|
46
|
+
transport: http(),
|
|
47
|
+
account: privateKeyToAccount(BOB_PRIVATE_KEY),
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
const aliceWalletClient: WalletClient = createWalletClient({
|
|
51
|
+
chain: arbitrumSepolia,
|
|
52
|
+
transport: http(),
|
|
53
|
+
account: privateKeyToAccount(ALICE_PRIVATE_KEY),
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
// Helper to get the wallet addresses
|
|
57
|
+
const bobAddress = bobWalletClient.account!.address;
|
|
58
|
+
const aliceAddress = aliceWalletClient.account!.address;
|
|
59
|
+
const chainId = 421614; // Arbitrum Sepolia
|
|
60
|
+
|
|
61
|
+
describe('Core ACPs Tests', () => {
|
|
62
|
+
beforeEach(() => {
|
|
63
|
+
// Clear localStorage and reset stores
|
|
64
|
+
localStorage.clear();
|
|
65
|
+
acpStore.store.setState({ acps: {}, activeACPHash: {} });
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
afterEach(() => {
|
|
69
|
+
localStorage.clear();
|
|
70
|
+
acpStore.store.setState({ acps: {}, activeACPHash: {} });
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
describe('ACP Creation', () => {
|
|
74
|
+
it('should create and store self acp', async () => {
|
|
75
|
+
const acp = await acps.createSelf({ name: 'Test Self ACP', issuer: bobAddress }, publicClient, bobWalletClient);
|
|
76
|
+
|
|
77
|
+
expect(acp).toBeDefined();
|
|
78
|
+
expect(acp.name).toBe('Test Self ACP');
|
|
79
|
+
expect(acp.type).toBe('self');
|
|
80
|
+
expect(acp.issuer).toBe(bobAddress);
|
|
81
|
+
expect(acp.issuerSignature).toBeDefined();
|
|
82
|
+
expect(acp.issuerSignature).not.toBe('0x');
|
|
83
|
+
|
|
84
|
+
// Verify localStorage
|
|
85
|
+
const storedData = localStorage.getItem('cofhesdk-acps');
|
|
86
|
+
expect(storedData).toBeDefined();
|
|
87
|
+
const parsedData = JSON.parse(storedData!);
|
|
88
|
+
expect(parsedData.state.acps[chainId][bobAddress]).toBeDefined();
|
|
89
|
+
expect(parsedData.state.activeACPHash[chainId][bobAddress]).toBeDefined();
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it('should create and store sharing acp', async () => {
|
|
93
|
+
const acp = await acps.createSharing(
|
|
94
|
+
{
|
|
95
|
+
name: 'Test Sharing ACP',
|
|
96
|
+
issuer: bobAddress,
|
|
97
|
+
recipient: aliceAddress,
|
|
98
|
+
},
|
|
99
|
+
publicClient,
|
|
100
|
+
bobWalletClient
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
expect(acp.name).toBe('Test Sharing ACP');
|
|
104
|
+
expect(acp.type).toBe('sharing');
|
|
105
|
+
expect(acp.issuer).toBe(bobAddress);
|
|
106
|
+
expect(acp.recipient).toBe(aliceAddress);
|
|
107
|
+
expect(acp.issuerSignature).toBeDefined();
|
|
108
|
+
expect(acp.issuerSignature).not.toBe('0x');
|
|
109
|
+
|
|
110
|
+
// Verify localStorage: the sharing acp is stored, but it must NOT become the issuer's
|
|
111
|
+
// active acp (it's delegated to the recipient).
|
|
112
|
+
const storedData = localStorage.getItem('cofhesdk-acps');
|
|
113
|
+
expect(storedData).toBeDefined();
|
|
114
|
+
const parsedData = JSON.parse(storedData!);
|
|
115
|
+
expect(parsedData.state.acps[chainId][bobAddress]).toBeDefined();
|
|
116
|
+
expect(parsedData.state.activeACPHash[chainId]?.[bobAddress]).toBeUndefined();
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it('should import shared acp from JSON string', async () => {
|
|
120
|
+
// First create a sharing acp to import
|
|
121
|
+
const sharingACP = await acps.createSharing(
|
|
122
|
+
{
|
|
123
|
+
name: 'Original Sharing ACP',
|
|
124
|
+
issuer: bobAddress,
|
|
125
|
+
recipient: aliceAddress,
|
|
126
|
+
},
|
|
127
|
+
publicClient,
|
|
128
|
+
bobWalletClient
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
// Export the acp as JSON string
|
|
132
|
+
const acpJson = JSON.stringify({
|
|
133
|
+
name: sharingACP.name,
|
|
134
|
+
type: sharingACP.type,
|
|
135
|
+
issuer: sharingACP.issuer,
|
|
136
|
+
expiration: sharingACP.expiration,
|
|
137
|
+
recipient: sharingACP.recipient,
|
|
138
|
+
revokerData: sharingACP.revokerData,
|
|
139
|
+
revokerContract: sharingACP.revokerContract,
|
|
140
|
+
issuerSignature: sharingACP.issuerSignature,
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
// Import the acp as Alice (recipient)
|
|
144
|
+
const acp = await acps.importShared(acpJson, publicClient, aliceWalletClient);
|
|
145
|
+
|
|
146
|
+
expect(acp.name).toBe('Original Sharing ACP');
|
|
147
|
+
expect(acp.type).toBe('recipient');
|
|
148
|
+
expect(acp.issuer).toBe(bobAddress);
|
|
149
|
+
expect(acp.recipient).toBe(aliceAddress);
|
|
150
|
+
expect(acp.recipientSignature).toBeDefined();
|
|
151
|
+
expect(acp.recipientSignature).not.toBe('0x');
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
describe('ACP Retrieval', () => {
|
|
156
|
+
let createdACP: any;
|
|
157
|
+
let acpHash: string;
|
|
158
|
+
|
|
159
|
+
beforeEach(async () => {
|
|
160
|
+
// Create a real acp for testing
|
|
161
|
+
createdACP = await acps.createSelf({ name: 'Test ACP', issuer: bobAddress }, publicClient, bobWalletClient);
|
|
162
|
+
acpHash = createdACP.hash;
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it('should get acp by hash', async () => {
|
|
166
|
+
const acp = await acps.getACP(chainId, bobAddress, acpHash);
|
|
167
|
+
expect(acp?.name).toBe('Test ACP');
|
|
168
|
+
expect(acp?.type).toBe('self');
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it('should get all acps', async () => {
|
|
172
|
+
const allACPs = await acps.getACPs(chainId, bobAddress);
|
|
173
|
+
expect(Object.keys(allACPs).length).toBeGreaterThan(0);
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
it('should get active acp', async () => {
|
|
177
|
+
const acp = await acps.getActiveACP(chainId, bobAddress);
|
|
178
|
+
expect(acp?.name).toBe('Test ACP');
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
it('should get active acp hash', async () => {
|
|
182
|
+
const hash = await acps.getActiveACPHash(chainId, bobAddress);
|
|
183
|
+
expect(typeof hash).toBe('string');
|
|
184
|
+
});
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
describe('localStorage Integration', () => {
|
|
188
|
+
it('should persist acps to localStorage', async () => {
|
|
189
|
+
const createdACP = await acps.createSelf({ name: 'Test ACP', issuer: bobAddress }, publicClient, bobWalletClient);
|
|
190
|
+
|
|
191
|
+
const storedData = localStorage.getItem('cofhesdk-acps');
|
|
192
|
+
expect(storedData).toBeDefined();
|
|
193
|
+
|
|
194
|
+
const parsedData = JSON.parse(storedData!);
|
|
195
|
+
expect(parsedData.state.acps[chainId][bobAddress]).toBeDefined();
|
|
196
|
+
expect(parsedData.state.activeACPHash[chainId][bobAddress]).toBeDefined();
|
|
197
|
+
|
|
198
|
+
// Verify the acp data structure
|
|
199
|
+
const acpKeys = Object.keys(parsedData.state.acps[chainId][bobAddress]);
|
|
200
|
+
expect(acpKeys.length).toBeGreaterThan(0);
|
|
201
|
+
|
|
202
|
+
const serializedACP = acps.serialize(createdACP);
|
|
203
|
+
expect(parsedData.state.acps[chainId][bobAddress][createdACP.hash]).toEqual(serializedACP);
|
|
204
|
+
});
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
describe('Real Network Integration', () => {
|
|
208
|
+
it('should create acp with real EIP712 domain from Arbitrum Sepolia', async () => {
|
|
209
|
+
const acp = await acps.createSelf(
|
|
210
|
+
{ name: 'Real Network ACP', issuer: bobAddress },
|
|
211
|
+
publicClient,
|
|
212
|
+
bobWalletClient
|
|
213
|
+
);
|
|
214
|
+
|
|
215
|
+
expect(acp._signedDomain).toBeDefined();
|
|
216
|
+
expect(acp._signedDomain?.chainId).toBe(chainId);
|
|
217
|
+
expect(acp._signedDomain?.name).toBeDefined();
|
|
218
|
+
expect(acp._signedDomain?.version).toBeDefined();
|
|
219
|
+
expect(acp._signedDomain?.verifyingContract).toBeDefined();
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
it('should handle multiple acps on real network', async () => {
|
|
223
|
+
// Create multiple acps
|
|
224
|
+
await acps.createSelf({ name: 'ACP 1', issuer: bobAddress }, publicClient, bobWalletClient);
|
|
225
|
+
await acps.createSharing(
|
|
226
|
+
{
|
|
227
|
+
name: 'ACP 2',
|
|
228
|
+
issuer: bobAddress,
|
|
229
|
+
recipient: aliceAddress,
|
|
230
|
+
},
|
|
231
|
+
publicClient,
|
|
232
|
+
bobWalletClient
|
|
233
|
+
);
|
|
234
|
+
|
|
235
|
+
// Verify both acps exist
|
|
236
|
+
const allACPs = await acps.getACPs(chainId, bobAddress);
|
|
237
|
+
expect(Object.keys(allACPs).length).toBeGreaterThanOrEqual(2);
|
|
238
|
+
|
|
239
|
+
// The self acp stays active — creating the sharing (delegated) acp must not change it.
|
|
240
|
+
const activeACP = await acps.getActiveACP(chainId, bobAddress);
|
|
241
|
+
expect(activeACP?.name).toBe('ACP 1');
|
|
242
|
+
});
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
describe('getOrCreateSelfACP', () => {
|
|
246
|
+
it('should create a new self acp when none exists', async () => {
|
|
247
|
+
const acp = await acps.getOrCreateSelfACP(publicClient, bobWalletClient, chainId, bobAddress, {
|
|
248
|
+
issuer: bobAddress,
|
|
249
|
+
name: 'New Self ACP',
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
expect(acp).toBeDefined();
|
|
253
|
+
expect(acp.name).toBe('New Self ACP');
|
|
254
|
+
expect(acp.type).toBe('self');
|
|
255
|
+
expect(acp.issuer).toBe(bobAddress);
|
|
256
|
+
expect(acp.issuerSignature).toBeDefined();
|
|
257
|
+
|
|
258
|
+
// Verify it was stored and set as active
|
|
259
|
+
const activeACP = await acps.getActiveACP(chainId, bobAddress);
|
|
260
|
+
expect(activeACP?.name).toBe('New Self ACP');
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
it('should return existing self acp when one exists', async () => {
|
|
264
|
+
// Create an initial self acp
|
|
265
|
+
const firstACP = await acps.createSelf(
|
|
266
|
+
{ name: 'First Self ACP', issuer: bobAddress },
|
|
267
|
+
publicClient,
|
|
268
|
+
bobWalletClient
|
|
269
|
+
);
|
|
270
|
+
|
|
271
|
+
// Call getOrCreateSelfACP - should return existing
|
|
272
|
+
const acp = await acps.getOrCreateSelfACP(publicClient, bobWalletClient, chainId, bobAddress, {
|
|
273
|
+
issuer: bobAddress,
|
|
274
|
+
name: 'Should Not Create This',
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
expect(acp.name).toBe('First Self ACP');
|
|
278
|
+
expect(acp.hash).toBe(firstACP.hash);
|
|
279
|
+
|
|
280
|
+
// Verify no new acp was created
|
|
281
|
+
const allACPs = await acps.getACPs(chainId, bobAddress);
|
|
282
|
+
expect(Object.keys(allACPs).length).toBe(1);
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
it('should create new self acp when active acp is sharing type', async () => {
|
|
286
|
+
// Create a sharing acp first
|
|
287
|
+
await acps.createSharing(
|
|
288
|
+
{
|
|
289
|
+
name: 'Sharing ACP',
|
|
290
|
+
issuer: bobAddress,
|
|
291
|
+
recipient: aliceAddress,
|
|
292
|
+
},
|
|
293
|
+
publicClient,
|
|
294
|
+
bobWalletClient
|
|
295
|
+
);
|
|
296
|
+
|
|
297
|
+
// Call getOrCreateSelfACP - should create new since active is sharing type
|
|
298
|
+
const acp = await acps.getOrCreateSelfACP(publicClient, bobWalletClient, chainId, bobAddress, {
|
|
299
|
+
issuer: bobAddress,
|
|
300
|
+
name: 'New Self ACP',
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
expect(acp.name).toBe('New Self ACP');
|
|
304
|
+
expect(acp.type).toBe('self');
|
|
305
|
+
|
|
306
|
+
// Verify two acps exist now
|
|
307
|
+
const allACPs = await acps.getACPs(chainId, bobAddress);
|
|
308
|
+
expect(Object.keys(allACPs).length).toBe(2);
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
it('should create a new self acp when active acp is expired', async () => {
|
|
312
|
+
// Create an expired self acp (expiration in the past)
|
|
313
|
+
const expiredACP = await acps.createSelf(
|
|
314
|
+
{ name: 'Expired Self ACP', issuer: bobAddress, expiration: Math.floor(Date.now() / 1000) - 3600 },
|
|
315
|
+
publicClient,
|
|
316
|
+
bobWalletClient
|
|
317
|
+
);
|
|
318
|
+
|
|
319
|
+
// Sanity check - it is the active acp and is expired
|
|
320
|
+
const activeBefore = await acps.getActiveACP(chainId, bobAddress);
|
|
321
|
+
expect(activeBefore?.hash).toBe(expiredACP.hash);
|
|
322
|
+
|
|
323
|
+
// getOrCreateSelfACP should treat the expired acp as missing and create a fresh one
|
|
324
|
+
const acp = await acps.getOrCreateSelfACP(publicClient, bobWalletClient, chainId, bobAddress, {
|
|
325
|
+
issuer: bobAddress,
|
|
326
|
+
name: 'Fresh Self ACP',
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
expect(acp.name).toBe('Fresh Self ACP');
|
|
330
|
+
expect(acp.type).toBe('self');
|
|
331
|
+
expect(acp.hash).not.toBe(expiredACP.hash);
|
|
332
|
+
|
|
333
|
+
// The fresh acp should now be active
|
|
334
|
+
const activeAfter = await acps.getActiveACP(chainId, bobAddress);
|
|
335
|
+
expect(activeAfter?.hash).toBe(acp.hash);
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
it('should use default options when none provided', async () => {
|
|
339
|
+
const acp = await acps.getOrCreateSelfACP(publicClient, bobWalletClient, chainId, bobAddress);
|
|
340
|
+
|
|
341
|
+
expect(acp).toBeDefined();
|
|
342
|
+
expect(acp.type).toBe('self');
|
|
343
|
+
expect(acp.issuer).toBe(bobAddress);
|
|
344
|
+
expect(acp.name).toBe('Autogenerated Self ACP');
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
it('should use default chainId and account when not provided', async () => {
|
|
348
|
+
const acp = await acps.getOrCreateSelfACP(publicClient, bobWalletClient, undefined, undefined, {
|
|
349
|
+
issuer: bobAddress,
|
|
350
|
+
name: 'Test ACP',
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
expect(acp).toBeDefined();
|
|
354
|
+
expect(acp.issuer).toBe(bobAddress);
|
|
355
|
+
|
|
356
|
+
// Verify it was stored with the chain's actual chainId
|
|
357
|
+
const activeACP = await acps.getActiveACP(chainId, bobAddress);
|
|
358
|
+
expect(activeACP?.name).toBe('Test ACP');
|
|
359
|
+
});
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
describe('getOrCreateSharingACP', () => {
|
|
363
|
+
it('should create a new sharing acp when none exists', async () => {
|
|
364
|
+
const acp = await acps.getOrCreateSharingACP(
|
|
365
|
+
publicClient,
|
|
366
|
+
bobWalletClient,
|
|
367
|
+
{
|
|
368
|
+
issuer: bobAddress,
|
|
369
|
+
recipient: aliceAddress,
|
|
370
|
+
name: 'New Sharing ACP',
|
|
371
|
+
},
|
|
372
|
+
chainId,
|
|
373
|
+
bobAddress
|
|
374
|
+
);
|
|
375
|
+
|
|
376
|
+
expect(acp).toBeDefined();
|
|
377
|
+
expect(acp.name).toBe('New Sharing ACP');
|
|
378
|
+
expect(acp.type).toBe('sharing');
|
|
379
|
+
expect(acp.issuer).toBe(bobAddress);
|
|
380
|
+
expect(acp.recipient).toBe(aliceAddress);
|
|
381
|
+
expect(acp.issuerSignature).toBeDefined();
|
|
382
|
+
|
|
383
|
+
// Stored but NOT activated — a sharing acp is delegated, never the issuer's active acp.
|
|
384
|
+
const allACPs = await acps.getACPs(chainId, bobAddress);
|
|
385
|
+
expect(allACPs[acp.hash]?.name).toBe('New Sharing ACP');
|
|
386
|
+
expect(await acps.getActiveACP(chainId, bobAddress)).toBeUndefined();
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
it('should return existing sharing acp when one exists', async () => {
|
|
390
|
+
// Create an initial sharing acp, then make it active by hand (sharing acps are not
|
|
391
|
+
// auto-activated), so getOrCreateSharingACP reuses it instead of creating another.
|
|
392
|
+
const firstACP = await acps.createSharing(
|
|
393
|
+
{
|
|
394
|
+
name: 'First Sharing ACP',
|
|
395
|
+
issuer: bobAddress,
|
|
396
|
+
recipient: aliceAddress,
|
|
397
|
+
},
|
|
398
|
+
publicClient,
|
|
399
|
+
bobWalletClient
|
|
400
|
+
);
|
|
401
|
+
acps.selectActiveACP(chainId, bobAddress, firstACP.hash);
|
|
402
|
+
|
|
403
|
+
// Call getOrCreateSharingACP - should return existing
|
|
404
|
+
const acp = await acps.getOrCreateSharingACP(
|
|
405
|
+
publicClient,
|
|
406
|
+
bobWalletClient,
|
|
407
|
+
{
|
|
408
|
+
issuer: bobAddress,
|
|
409
|
+
recipient: aliceAddress,
|
|
410
|
+
name: 'Should Not Create This',
|
|
411
|
+
},
|
|
412
|
+
chainId,
|
|
413
|
+
bobAddress
|
|
414
|
+
);
|
|
415
|
+
|
|
416
|
+
expect(acp.name).toBe('First Sharing ACP');
|
|
417
|
+
expect(acp.hash).toBe(firstACP.hash);
|
|
418
|
+
|
|
419
|
+
// Verify no new acp was created
|
|
420
|
+
const allACPs = await acps.getACPs(chainId, bobAddress);
|
|
421
|
+
expect(Object.keys(allACPs).length).toBe(1);
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
it('should create new sharing acp when active acp is self type', async () => {
|
|
425
|
+
// Create a self acp first
|
|
426
|
+
await acps.createSelf({ name: 'Self ACP', issuer: bobAddress }, publicClient, bobWalletClient);
|
|
427
|
+
|
|
428
|
+
// Call getOrCreateSharingACP - should create new since active is self type
|
|
429
|
+
const acp = await acps.getOrCreateSharingACP(
|
|
430
|
+
publicClient,
|
|
431
|
+
bobWalletClient,
|
|
432
|
+
{
|
|
433
|
+
issuer: bobAddress,
|
|
434
|
+
recipient: aliceAddress,
|
|
435
|
+
name: 'New Sharing ACP',
|
|
436
|
+
},
|
|
437
|
+
chainId,
|
|
438
|
+
bobAddress
|
|
439
|
+
);
|
|
440
|
+
|
|
441
|
+
expect(acp.name).toBe('New Sharing ACP');
|
|
442
|
+
expect(acp.type).toBe('sharing');
|
|
443
|
+
|
|
444
|
+
// Verify two acps exist now
|
|
445
|
+
const allACPs = await acps.getACPs(chainId, bobAddress);
|
|
446
|
+
expect(Object.keys(allACPs).length).toBe(2);
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
it('should create a new sharing acp when active acp is expired', async () => {
|
|
450
|
+
// Create an expired sharing acp and make it the active acp (by hand — sharing acps
|
|
451
|
+
// aren't auto-activated), so the "active acp is expired" branch is exercised.
|
|
452
|
+
const expiredACP = await acps.createSharing(
|
|
453
|
+
{
|
|
454
|
+
name: 'Expired Sharing ACP',
|
|
455
|
+
issuer: bobAddress,
|
|
456
|
+
recipient: aliceAddress,
|
|
457
|
+
expiration: Math.floor(Date.now() / 1000) - 3600,
|
|
458
|
+
},
|
|
459
|
+
publicClient,
|
|
460
|
+
bobWalletClient
|
|
461
|
+
);
|
|
462
|
+
acps.selectActiveACP(chainId, bobAddress, expiredACP.hash);
|
|
463
|
+
|
|
464
|
+
// getOrCreateSharingACP should treat the expired acp as missing and create a fresh one
|
|
465
|
+
const acp = await acps.getOrCreateSharingACP(
|
|
466
|
+
publicClient,
|
|
467
|
+
bobWalletClient,
|
|
468
|
+
{
|
|
469
|
+
issuer: bobAddress,
|
|
470
|
+
recipient: aliceAddress,
|
|
471
|
+
name: 'Fresh Sharing ACP',
|
|
472
|
+
},
|
|
473
|
+
chainId,
|
|
474
|
+
bobAddress
|
|
475
|
+
);
|
|
476
|
+
|
|
477
|
+
expect(acp.name).toBe('Fresh Sharing ACP');
|
|
478
|
+
expect(acp.type).toBe('sharing');
|
|
479
|
+
expect(acp.hash).not.toBe(expiredACP.hash);
|
|
480
|
+
|
|
481
|
+
// The fresh sharing acp is stored (but not auto-activated).
|
|
482
|
+
const allACPs = await acps.getACPs(chainId, bobAddress);
|
|
483
|
+
expect(allACPs[acp.hash]).toBeDefined();
|
|
484
|
+
});
|
|
485
|
+
|
|
486
|
+
it('should use default chainId and account when not provided', async () => {
|
|
487
|
+
const acp = await acps.getOrCreateSharingACP(
|
|
488
|
+
publicClient,
|
|
489
|
+
bobWalletClient,
|
|
490
|
+
{
|
|
491
|
+
issuer: bobAddress,
|
|
492
|
+
recipient: aliceAddress,
|
|
493
|
+
name: 'Test Sharing ACP',
|
|
494
|
+
},
|
|
495
|
+
undefined,
|
|
496
|
+
undefined
|
|
497
|
+
);
|
|
498
|
+
|
|
499
|
+
expect(acp).toBeDefined();
|
|
500
|
+
expect(acp.issuer).toBe(bobAddress);
|
|
501
|
+
expect(acp.recipient).toBe(aliceAddress);
|
|
502
|
+
|
|
503
|
+
// Stored under the connected wallet's chainId/account (not activated).
|
|
504
|
+
const allACPs = await acps.getACPs(chainId, bobAddress);
|
|
505
|
+
expect(allACPs[acp.hash]?.name).toBe('Test Sharing ACP');
|
|
506
|
+
});
|
|
507
|
+
});
|
|
508
|
+
|
|
509
|
+
describe('Export', () => {
|
|
510
|
+
it('throws when exporting a self acp', async () => {
|
|
511
|
+
const acp = await acps.createSelf({ name: 'Test Self ACP', issuer: bobAddress }, publicClient, bobWalletClient);
|
|
512
|
+
|
|
513
|
+
// export includes the issuer signature — only sharing acps are exportable
|
|
514
|
+
expect(() => acps.export(acp)).toThrow(/only 'sharing' ACPs are exportable/);
|
|
515
|
+
});
|
|
516
|
+
|
|
517
|
+
it('should export sharing acp data with recipient and issuerSignature', async () => {
|
|
518
|
+
const acp = await acps.createSharing(
|
|
519
|
+
{
|
|
520
|
+
name: 'Test Sharing ACP',
|
|
521
|
+
issuer: bobAddress,
|
|
522
|
+
recipient: aliceAddress,
|
|
523
|
+
},
|
|
524
|
+
publicClient,
|
|
525
|
+
bobWalletClient
|
|
526
|
+
);
|
|
527
|
+
|
|
528
|
+
const exported = acps.export(acp);
|
|
529
|
+
const parsed = JSON.parse(exported);
|
|
530
|
+
|
|
531
|
+
expect(parsed.name).toBe('Test Sharing ACP');
|
|
532
|
+
expect(parsed.type).toBe('sharing');
|
|
533
|
+
expect(parsed.issuer).toBe(bobAddress);
|
|
534
|
+
expect(parsed.recipient).toBe(aliceAddress);
|
|
535
|
+
expect(parsed.issuerSignature).toBeDefined();
|
|
536
|
+
expect(parsed.issuerSignature).not.toBe('0x');
|
|
537
|
+
expect(parsed).not.toHaveProperty('sealingPair');
|
|
538
|
+
});
|
|
539
|
+
});
|
|
540
|
+
|
|
541
|
+
describe('getOrCreate - Multiple Types Scenarios', () => {
|
|
542
|
+
it('should keep the self acp active when a sharing acp is also created', async () => {
|
|
543
|
+
// Create self acp (this one activates)
|
|
544
|
+
const selfACP = await acps.getOrCreateSelfACP(publicClient, bobWalletClient, chainId, bobAddress, {
|
|
545
|
+
issuer: bobAddress,
|
|
546
|
+
name: 'Self ACP',
|
|
547
|
+
});
|
|
548
|
+
expect(selfACP.type).toBe('self');
|
|
549
|
+
|
|
550
|
+
// Create sharing acp (creates a new one, but does NOT activate it)
|
|
551
|
+
const sharingACP = await acps.getOrCreateSharingACP(
|
|
552
|
+
publicClient,
|
|
553
|
+
bobWalletClient,
|
|
554
|
+
{
|
|
555
|
+
issuer: bobAddress,
|
|
556
|
+
recipient: aliceAddress,
|
|
557
|
+
name: 'Sharing ACP',
|
|
558
|
+
},
|
|
559
|
+
chainId,
|
|
560
|
+
bobAddress
|
|
561
|
+
);
|
|
562
|
+
expect(sharingACP.type).toBe('sharing');
|
|
563
|
+
|
|
564
|
+
// Both should exist
|
|
565
|
+
const allACPs = await acps.getACPs(chainId, bobAddress);
|
|
566
|
+
expect(Object.keys(allACPs).length).toBe(2);
|
|
567
|
+
|
|
568
|
+
// The active acp stays the self acp — creating the sharing acp doesn't hijack it.
|
|
569
|
+
const activeACP = await acps.getActiveACP(chainId, bobAddress);
|
|
570
|
+
expect(activeACP?.type).toBe('self');
|
|
571
|
+
expect(activeACP?.name).toBe('Self ACP');
|
|
572
|
+
});
|
|
573
|
+
|
|
574
|
+
it('should correctly handle sequential getOrCreate calls', async () => {
|
|
575
|
+
// First call - creates new
|
|
576
|
+
const acp1 = await acps.getOrCreateSelfACP(publicClient, bobWalletClient, chainId, bobAddress, {
|
|
577
|
+
issuer: bobAddress,
|
|
578
|
+
name: 'ACP 1',
|
|
579
|
+
});
|
|
580
|
+
|
|
581
|
+
// Second call - returns existing
|
|
582
|
+
const acp2 = await acps.getOrCreateSelfACP(publicClient, bobWalletClient, chainId, bobAddress, {
|
|
583
|
+
issuer: bobAddress,
|
|
584
|
+
name: 'ACP 2',
|
|
585
|
+
});
|
|
586
|
+
|
|
587
|
+
// Should be the same acp
|
|
588
|
+
expect(acp1.hash).toBe(acp2.hash);
|
|
589
|
+
expect(acp2.name).toBe('ACP 1'); // Original name
|
|
590
|
+
|
|
591
|
+
// Only one acp should exist
|
|
592
|
+
const allACPs = await acps.getACPs(chainId, bobAddress);
|
|
593
|
+
expect(Object.keys(allACPs).length).toBe(1);
|
|
594
|
+
});
|
|
595
|
+
});
|
|
596
|
+
});
|