@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
package/acps/sealing.ts
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import nacl from 'tweetnacl';
|
|
2
|
+
import { type Hex } from 'viem';
|
|
3
|
+
import { fromHexString, toBeArray, toBigInt, toHexString, isBigIntOrNumber, isString } from './utils.js';
|
|
4
|
+
|
|
5
|
+
/** 32-byte X25519 keys as bare hex (no 0x prefix) */
|
|
6
|
+
const KEY_HEX_LENGTH = 64;
|
|
7
|
+
|
|
8
|
+
export type EthEncryptedData = {
|
|
9
|
+
data: Uint8Array;
|
|
10
|
+
public_key: Uint8Array;
|
|
11
|
+
nonce: Uint8Array;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* An X25519 sealing keypair, both halves 0x-prefixed 32-byte hex.
|
|
16
|
+
* The public key travels in the ACP (`sealingKey`); the private key never
|
|
17
|
+
* leaves the client (`sealingPrivateKey`).
|
|
18
|
+
*/
|
|
19
|
+
export type SealingKeyPair = {
|
|
20
|
+
privateKey: Hex;
|
|
21
|
+
publicKey: Hex;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Generates a new sealing keypair. A sealing key is used to encrypt data such
|
|
26
|
+
* that it can only be unsealed (decrypted) by the owner of the corresponding
|
|
27
|
+
* private key.
|
|
28
|
+
*/
|
|
29
|
+
export const GenerateSealingKey = (): SealingKeyPair => {
|
|
30
|
+
const sodiumKeypair = nacl.box.keyPair();
|
|
31
|
+
|
|
32
|
+
return {
|
|
33
|
+
privateKey: `0x${toHexString(sodiumKeypair.secretKey)}`,
|
|
34
|
+
publicKey: `0x${toHexString(sodiumKeypair.publicKey)}`,
|
|
35
|
+
};
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Seals (encrypts) the provided message for a receiver with the specified public key.
|
|
40
|
+
*
|
|
41
|
+
* @param {bigint | number} value - The message to be encrypted.
|
|
42
|
+
* @param {string} publicKey - The public key of the intended recipient (with or without 0x prefix).
|
|
43
|
+
* @returns {EthEncryptedData} - The encrypted message.
|
|
44
|
+
* @throws Will throw if the provided publicKey or value do not meet defined preconditions.
|
|
45
|
+
*/
|
|
46
|
+
export const seal = (value: bigint | number, publicKey: string): EthEncryptedData => {
|
|
47
|
+
isString(publicKey);
|
|
48
|
+
isBigIntOrNumber(value);
|
|
49
|
+
assertKeyLength(publicKey, 'Public');
|
|
50
|
+
|
|
51
|
+
// generate ephemeral keypair
|
|
52
|
+
const ephemeralKeyPair = nacl.box.keyPair();
|
|
53
|
+
|
|
54
|
+
const nonce = nacl.randomBytes(nacl.box.nonceLength);
|
|
55
|
+
|
|
56
|
+
const encryptedMessage = nacl.box(toBeArray(value), nonce, fromHexString(publicKey), ephemeralKeyPair.secretKey);
|
|
57
|
+
|
|
58
|
+
return {
|
|
59
|
+
data: encryptedMessage,
|
|
60
|
+
public_key: ephemeralKeyPair.publicKey,
|
|
61
|
+
nonce: nonce,
|
|
62
|
+
};
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Unseal (decrypt) data with a sealing private key (with or without 0x prefix).
|
|
67
|
+
* The ephemeral public key travels inside the payload, so the private key alone suffices.
|
|
68
|
+
*/
|
|
69
|
+
export const unsealWithPrivateKey = (privateKey: string, parsedData: EthEncryptedData): bigint => {
|
|
70
|
+
assertKeyLength(privateKey, 'Private');
|
|
71
|
+
|
|
72
|
+
const nonce = parsedData.nonce instanceof Uint8Array ? parsedData.nonce : new Uint8Array(parsedData.nonce);
|
|
73
|
+
const ephemPublicKey =
|
|
74
|
+
parsedData.public_key instanceof Uint8Array ? parsedData.public_key : new Uint8Array(parsedData.public_key);
|
|
75
|
+
const dataToDecrypt = parsedData.data instanceof Uint8Array ? parsedData.data : new Uint8Array(parsedData.data);
|
|
76
|
+
|
|
77
|
+
const decryptedMessage = nacl.box.open(dataToDecrypt, nonce, ephemPublicKey, fromHexString(privateKey));
|
|
78
|
+
if (!decryptedMessage) {
|
|
79
|
+
throw new Error('Failed to decrypt message');
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return toBigInt(decryptedMessage);
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
const assertKeyLength = (key: string, kind: 'Private' | 'Public'): void => {
|
|
86
|
+
const bare = key.startsWith('0x') ? key.slice(2) : key;
|
|
87
|
+
if (bare.length !== KEY_HEX_LENGTH) {
|
|
88
|
+
throw new Error(`${kind} key must be of length ${KEY_HEX_LENGTH}`);
|
|
89
|
+
}
|
|
90
|
+
};
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { type EIP712Message, type EIP712Types, type ACPPublic, type ACPSignaturePrimaryType } from './types.js';
|
|
2
|
+
|
|
3
|
+
// Field order must match the on-chain ACP typehash strings exactly (see ACP.sol / ACPUtils)
|
|
4
|
+
const ACPSignatureAllFields = [
|
|
5
|
+
{ name: 'issuer', type: 'address' },
|
|
6
|
+
{ name: 'expiration', type: 'uint64' },
|
|
7
|
+
{ name: 'recipient', type: 'address' },
|
|
8
|
+
{ name: 'revokerData', type: 'uint256' },
|
|
9
|
+
{ name: 'revokerContract', type: 'address' },
|
|
10
|
+
{ name: 'scope', type: 'uint8' },
|
|
11
|
+
{ name: 'contracts', type: 'address[]' },
|
|
12
|
+
{ name: 'handles', type: 'bytes32[]' },
|
|
13
|
+
{ name: 'sealingKey', type: 'bytes32' },
|
|
14
|
+
{ name: 'issuerSignature', type: 'bytes' },
|
|
15
|
+
] as const;
|
|
16
|
+
|
|
17
|
+
type ACPSignatureFieldOption = (typeof ACPSignatureAllFields)[number]['name'];
|
|
18
|
+
|
|
19
|
+
export const SignatureTypes = {
|
|
20
|
+
ACPIssuerSelf: [
|
|
21
|
+
'issuer',
|
|
22
|
+
'expiration',
|
|
23
|
+
'recipient',
|
|
24
|
+
'revokerData',
|
|
25
|
+
'revokerContract',
|
|
26
|
+
'scope',
|
|
27
|
+
'contracts',
|
|
28
|
+
'handles',
|
|
29
|
+
'sealingKey',
|
|
30
|
+
] satisfies ACPSignatureFieldOption[],
|
|
31
|
+
ACPIssuerShared: [
|
|
32
|
+
'issuer',
|
|
33
|
+
'expiration',
|
|
34
|
+
'recipient',
|
|
35
|
+
'revokerData',
|
|
36
|
+
'revokerContract',
|
|
37
|
+
'scope',
|
|
38
|
+
'contracts',
|
|
39
|
+
'handles',
|
|
40
|
+
] satisfies ACPSignatureFieldOption[],
|
|
41
|
+
ACPRecipient: ['sealingKey', 'issuerSignature'] satisfies ACPSignatureFieldOption[],
|
|
42
|
+
} as const;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Get signature types and message for EIP712 signing
|
|
46
|
+
*/
|
|
47
|
+
export const getSignatureTypesAndMessage = <T extends ACPSignatureFieldOption>(
|
|
48
|
+
primaryType: ACPSignaturePrimaryType,
|
|
49
|
+
fields: T[] | readonly T[],
|
|
50
|
+
values: Pick<ACPPublic, T> & Partial<ACPPublic>
|
|
51
|
+
): { types: EIP712Types; primaryType: string; message: EIP712Message } => {
|
|
52
|
+
const types = {
|
|
53
|
+
[primaryType]: ACPSignatureAllFields.filter((fieldType) => fields.includes(fieldType.name as T)),
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const message: Record<T, string | string[] | number | number[] | boolean> = {} as Record<
|
|
57
|
+
T,
|
|
58
|
+
string | string[] | number | number[] | boolean
|
|
59
|
+
>;
|
|
60
|
+
fields.forEach((field) => {
|
|
61
|
+
if (field in values) {
|
|
62
|
+
message[field] = values[field];
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
return { types, primaryType, message: message as EIP712Message };
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Signature utilities for ACP operations
|
|
71
|
+
*/
|
|
72
|
+
export const SignatureUtils = {
|
|
73
|
+
/**
|
|
74
|
+
* Get signature parameters for an ACP
|
|
75
|
+
*/
|
|
76
|
+
getSignatureParams: (acp: ACPPublic, primaryType: ACPSignaturePrimaryType) => {
|
|
77
|
+
return getSignatureTypesAndMessage(primaryType, SignatureTypes[primaryType], acp);
|
|
78
|
+
},
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Determine the required signature type based on acp type
|
|
82
|
+
*/
|
|
83
|
+
getPrimaryType: (acpType: 'self' | 'sharing' | 'recipient'): ACPSignaturePrimaryType => {
|
|
84
|
+
if (acpType === 'self') return 'ACPIssuerSelf';
|
|
85
|
+
if (acpType === 'sharing') return 'ACPIssuerShared';
|
|
86
|
+
if (acpType === 'recipient') return 'ACPRecipient';
|
|
87
|
+
throw new Error(`Unknown acp type: ${acpType}`);
|
|
88
|
+
},
|
|
89
|
+
};
|
package/acps/store.ts
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { createStore } from 'zustand/vanilla';
|
|
2
|
+
import { persist } from 'zustand/middleware';
|
|
3
|
+
import { produce } from 'immer';
|
|
4
|
+
import { type ACP, type SerializedACP } from './types.js';
|
|
5
|
+
import { ACPUtils } from './acp.js';
|
|
6
|
+
|
|
7
|
+
type ChainRecord<T> = Record<number, T>;
|
|
8
|
+
type AccountRecord<T> = Record<string, T>;
|
|
9
|
+
type HashRecord<T> = Record<string, T>;
|
|
10
|
+
|
|
11
|
+
type ACPsStore = {
|
|
12
|
+
acps: ChainRecord<AccountRecord<HashRecord<SerializedACP | undefined>>>;
|
|
13
|
+
activeACPHash: ChainRecord<AccountRecord<string | undefined>>;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
// Stores generated acps for each user, a hash indicating the active acp for each user
|
|
17
|
+
// Can be used to create reactive hooks
|
|
18
|
+
export const ACP_STORE_DEFAULTS: ACPsStore = {
|
|
19
|
+
acps: {},
|
|
20
|
+
activeACPHash: {},
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Store version 3 = ACP with flattened sealing keys (sealingPrivateKey/sealingKey).
|
|
25
|
+
* Store version 2 = ACP (ACP V3). V2 acps are signed with retired EIP-712
|
|
26
|
+
* types and cannot verify on-chain anymore — the migration drops them rather
|
|
27
|
+
* than carrying dead entries (users re-create acps on next use).
|
|
28
|
+
*/
|
|
29
|
+
const ACP_STORE_VERSION = 3;
|
|
30
|
+
|
|
31
|
+
export const _acpStore = createStore<ACPsStore>()(
|
|
32
|
+
persist(() => ACP_STORE_DEFAULTS, {
|
|
33
|
+
name: 'cofhesdk-acps',
|
|
34
|
+
version: ACP_STORE_VERSION,
|
|
35
|
+
migrate: (persistedState, version) => {
|
|
36
|
+
if (version < ACP_STORE_VERSION) return ACP_STORE_DEFAULTS;
|
|
37
|
+
return persistedState as ACPsStore;
|
|
38
|
+
},
|
|
39
|
+
})
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
export const clearStaleStore = () => {
|
|
43
|
+
// Any is used here because we do not have types of the previous store
|
|
44
|
+
const state = _acpStore.getState() as any;
|
|
45
|
+
|
|
46
|
+
// Check if the store has the expected structure
|
|
47
|
+
const hasExpectedStructure =
|
|
48
|
+
state &&
|
|
49
|
+
typeof state === 'object' &&
|
|
50
|
+
'acps' in state &&
|
|
51
|
+
'activeACPHash' in state &&
|
|
52
|
+
typeof state.acps === 'object' &&
|
|
53
|
+
typeof state.activeACPHash === 'object';
|
|
54
|
+
|
|
55
|
+
if (hasExpectedStructure) return;
|
|
56
|
+
// Invalid structure detected - clear the store
|
|
57
|
+
_acpStore.setState({ acps: {}, activeACPHash: {} });
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
export const getACP = (
|
|
61
|
+
chainId: number | undefined,
|
|
62
|
+
account: string | undefined,
|
|
63
|
+
hash: string | undefined
|
|
64
|
+
): ACP | undefined => {
|
|
65
|
+
clearStaleStore();
|
|
66
|
+
if (chainId == null || account == null || hash == null) return;
|
|
67
|
+
|
|
68
|
+
const savedACP = _acpStore.getState().acps[chainId]?.[account]?.[hash];
|
|
69
|
+
if (savedACP == null) return;
|
|
70
|
+
|
|
71
|
+
return ACPUtils.deserialize(savedACP);
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
export const getActiveACP = (chainId: number | undefined, account: string | undefined): ACP | undefined => {
|
|
75
|
+
clearStaleStore();
|
|
76
|
+
if (chainId == null || account == null) return;
|
|
77
|
+
|
|
78
|
+
const activeACPHash = _acpStore.getState().activeACPHash[chainId]?.[account];
|
|
79
|
+
return getACP(chainId, account, activeACPHash);
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
export const getACPs = (chainId: number | undefined, account: string | undefined): Record<string, ACP> => {
|
|
83
|
+
clearStaleStore();
|
|
84
|
+
if (chainId == null || account == null) return {};
|
|
85
|
+
|
|
86
|
+
return Object.entries(_acpStore.getState().acps[chainId]?.[account] ?? {}).reduce(
|
|
87
|
+
(acc, [hash, acp]) => {
|
|
88
|
+
if (acp == undefined) return acc;
|
|
89
|
+
return { ...acc, [hash]: ACPUtils.deserialize(acp) };
|
|
90
|
+
},
|
|
91
|
+
{} as Record<string, ACP>
|
|
92
|
+
);
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
export const setACP = (chainId: number, account: string, acp: ACP) => {
|
|
96
|
+
clearStaleStore();
|
|
97
|
+
_acpStore.setState(
|
|
98
|
+
produce<ACPsStore>((state) => {
|
|
99
|
+
if (state.acps[chainId] == null) state.acps[chainId] = {};
|
|
100
|
+
if (state.acps[chainId][account] == null) state.acps[chainId][account] = {};
|
|
101
|
+
state.acps[chainId][account][acp.hash] = ACPUtils.serialize(acp);
|
|
102
|
+
})
|
|
103
|
+
);
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
export const removeACP = (chainId: number, account: string, hash: string) => {
|
|
107
|
+
clearStaleStore();
|
|
108
|
+
_acpStore.setState(
|
|
109
|
+
produce<ACPsStore>((state) => {
|
|
110
|
+
if (state.acps[chainId] == null) state.acps[chainId] = {};
|
|
111
|
+
if (state.activeACPHash[chainId] == null) state.activeACPHash[chainId] = {};
|
|
112
|
+
|
|
113
|
+
const accountACPs = state.acps[chainId][account];
|
|
114
|
+
if (accountACPs == null) return;
|
|
115
|
+
|
|
116
|
+
if (accountACPs[hash] == null) return;
|
|
117
|
+
|
|
118
|
+
if (state.activeACPHash[chainId][account] === hash) {
|
|
119
|
+
// if the active acp is the one to be removed - unset it
|
|
120
|
+
state.activeACPHash[chainId][account] = undefined;
|
|
121
|
+
}
|
|
122
|
+
// Remove the acp
|
|
123
|
+
accountACPs[hash] = undefined;
|
|
124
|
+
})
|
|
125
|
+
);
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
export const getActiveACPHash = (chainId: number | undefined, account: string | undefined): string | undefined => {
|
|
129
|
+
clearStaleStore();
|
|
130
|
+
if (chainId == null || account == null) return undefined;
|
|
131
|
+
return _acpStore.getState().activeACPHash[chainId]?.[account];
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
export const setActiveACPHash = (chainId: number, account: string, hash: string) => {
|
|
135
|
+
clearStaleStore();
|
|
136
|
+
_acpStore.setState(
|
|
137
|
+
produce<ACPsStore>((state) => {
|
|
138
|
+
if (state.activeACPHash[chainId] == null) state.activeACPHash[chainId] = {};
|
|
139
|
+
state.activeACPHash[chainId][account] = hash;
|
|
140
|
+
})
|
|
141
|
+
);
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
export const removeActiveACPHash = (chainId: number, account: string) => {
|
|
145
|
+
clearStaleStore();
|
|
146
|
+
_acpStore.setState(
|
|
147
|
+
produce<ACPsStore>((state) => {
|
|
148
|
+
if (state.activeACPHash[chainId]) state.activeACPHash[chainId][account] = undefined;
|
|
149
|
+
})
|
|
150
|
+
);
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
export const resetStore = () => {
|
|
154
|
+
clearStaleStore();
|
|
155
|
+
_acpStore.setState({ acps: {}, activeACPHash: {} });
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
export const acpStore = {
|
|
159
|
+
store: _acpStore,
|
|
160
|
+
|
|
161
|
+
getACP,
|
|
162
|
+
getActiveACP,
|
|
163
|
+
getACPs,
|
|
164
|
+
setACP,
|
|
165
|
+
removeACP,
|
|
166
|
+
|
|
167
|
+
getActiveACPHash,
|
|
168
|
+
setActiveACPHash,
|
|
169
|
+
removeActiveACPHash,
|
|
170
|
+
|
|
171
|
+
resetStore,
|
|
172
|
+
};
|