@cofhe/sdk 0.2.0 → 0.3.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 +36 -0
- package/chains/defineChain.ts +2 -2
- package/chains/types.ts +3 -3
- package/core/baseBuilder.ts +18 -18
- package/core/client.test.ts +155 -41
- package/core/client.ts +72 -32
- package/core/clientTypes.ts +28 -18
- package/core/config.test.ts +40 -33
- package/core/config.ts +56 -51
- package/core/consts.ts +22 -0
- package/core/decrypt/{MockQueryDecrypterAbi.ts → MockThresholdNetworkAbi.ts} +71 -21
- package/core/decrypt/cofheMocksDecryptForTx.ts +142 -0
- package/core/decrypt/{cofheMocksSealOutput.ts → cofheMocksDecryptForView.ts} +12 -14
- package/core/decrypt/decryptForTxBuilder.ts +340 -0
- package/core/decrypt/{decryptHandleBuilder.ts → decryptForViewBuilder.ts} +75 -42
- package/core/decrypt/tnDecrypt.ts +232 -0
- package/core/decrypt/tnSealOutputV1.ts +5 -5
- package/core/decrypt/tnSealOutputV2.ts +27 -27
- package/core/encrypt/cofheMocksZkVerifySign.ts +19 -26
- package/core/encrypt/encryptInputsBuilder.test.ts +57 -61
- package/core/encrypt/encryptInputsBuilder.ts +65 -42
- package/core/encrypt/zkPackProveVerify.ts +11 -11
- package/core/error.ts +18 -18
- package/core/fetchKeys.test.ts +3 -3
- package/core/fetchKeys.ts +3 -3
- package/core/index.ts +22 -11
- package/core/permits.test.ts +5 -6
- package/core/permits.ts +5 -4
- package/core/utils.ts +10 -10
- package/dist/chains.cjs +4 -7
- package/dist/chains.d.cts +12 -12
- package/dist/chains.d.ts +12 -12
- package/dist/chains.js +1 -1
- package/dist/{chunk-WGCRJCBR.js → chunk-2TPSCOW3.js} +820 -224
- package/dist/{chunk-UGBVZNRT.js → chunk-NWDKXBIP.js} +309 -189
- package/dist/{chunk-WEAZ25JO.js → chunk-TBLR7NNE.js} +4 -7
- package/dist/{clientTypes-5_1nwtUe.d.cts → clientTypes-6aTZPQ_4.d.ts} +233 -173
- package/dist/{clientTypes-Es7fyi65.d.ts → clientTypes-Bhq7pCSA.d.cts} +233 -173
- package/dist/core.cjs +1138 -418
- package/dist/core.d.cts +37 -24
- package/dist/core.d.ts +37 -24
- package/dist/core.js +3 -3
- package/dist/node.cjs +1082 -370
- package/dist/node.d.cts +12 -12
- package/dist/node.d.ts +12 -12
- package/dist/node.js +8 -8
- package/dist/{permit-fUSe6KKq.d.cts → permit-MZ502UBl.d.cts} +30 -33
- package/dist/{permit-fUSe6KKq.d.ts → permit-MZ502UBl.d.ts} +30 -33
- package/dist/permits.cjs +305 -187
- package/dist/permits.d.cts +111 -812
- package/dist/permits.d.ts +111 -812
- package/dist/permits.js +1 -1
- package/dist/types-YiAC4gig.d.cts +33 -0
- package/dist/types-YiAC4gig.d.ts +33 -0
- package/dist/web.cjs +1085 -373
- package/dist/web.d.cts +13 -13
- package/dist/web.d.ts +13 -13
- package/dist/web.js +10 -10
- package/node/client.test.ts +34 -34
- package/node/config.test.ts +11 -11
- package/node/encryptInputs.test.ts +29 -29
- package/node/index.ts +15 -15
- package/package.json +3 -3
- package/permits/localstorage.test.ts +9 -13
- package/permits/onchain-utils.ts +221 -0
- package/permits/permit.test.ts +51 -5
- package/permits/permit.ts +28 -74
- package/permits/store.test.ts +10 -50
- package/permits/store.ts +4 -14
- package/permits/test-utils.ts +10 -2
- package/permits/types.ts +22 -9
- package/permits/utils.ts +0 -4
- package/permits/validation.test.ts +29 -32
- package/permits/validation.ts +112 -194
- package/web/client.web.test.ts +34 -34
- package/web/config.web.test.ts +11 -11
- package/web/encryptInputs.web.test.ts +29 -29
- package/web/index.ts +19 -19
- package/web/worker.builder.web.test.ts +28 -28
- package/web/worker.config.web.test.ts +47 -47
- package/web/worker.output.web.test.ts +10 -10
- package/dist/types-KImPrEIe.d.cts +0 -48
- package/dist/types-KImPrEIe.d.ts +0 -48
|
@@ -39,7 +39,6 @@ describe('Permits localStorage Tests', () => {
|
|
|
39
39
|
|
|
40
40
|
it('should persist permits to localStorage', async () => {
|
|
41
41
|
const permit = await createMockPermit();
|
|
42
|
-
const hash = PermitUtils.getHash(permit);
|
|
43
42
|
|
|
44
43
|
setPermit(chainId, account, permit);
|
|
45
44
|
|
|
@@ -48,40 +47,38 @@ describe('Permits localStorage Tests', () => {
|
|
|
48
47
|
expect(storedData).toBeDefined();
|
|
49
48
|
|
|
50
49
|
const parsedData = JSON.parse(storedData!);
|
|
51
|
-
expect(parsedData.state.permits[chainId][account][hash]).toBeDefined();
|
|
50
|
+
expect(parsedData.state.permits[chainId][account][permit.hash]).toBeDefined();
|
|
52
51
|
});
|
|
53
52
|
|
|
54
53
|
it('should persist active permit hash to localStorage', async () => {
|
|
55
54
|
const permit = await createMockPermit();
|
|
56
|
-
const hash = PermitUtils.getHash(permit);
|
|
57
55
|
|
|
58
56
|
setPermit(chainId, account, permit);
|
|
59
|
-
setActivePermitHash(chainId, account, hash);
|
|
57
|
+
setActivePermitHash(chainId, account, permit.hash);
|
|
60
58
|
|
|
61
59
|
// Verify active permit hash is stored
|
|
62
60
|
const storedData = localStorage.getItem('cofhesdk-permits');
|
|
63
61
|
expect(storedData).toBeDefined();
|
|
64
62
|
|
|
65
63
|
const parsedData = JSON.parse(storedData!);
|
|
66
|
-
expect(parsedData.state.activePermitHash[chainId][account]).toBe(hash);
|
|
64
|
+
expect(parsedData.state.activePermitHash[chainId][account]).toBe(permit.hash);
|
|
67
65
|
});
|
|
68
66
|
|
|
69
67
|
it('should restore permits from localStorage', async () => {
|
|
70
68
|
const permit = await createMockPermit();
|
|
71
|
-
const hash = PermitUtils.getHash(permit);
|
|
72
69
|
|
|
73
70
|
// Add permit to localStorage
|
|
74
71
|
setPermit(chainId, account, permit);
|
|
75
|
-
setActivePermitHash(chainId, account, hash);
|
|
72
|
+
setActivePermitHash(chainId, account, permit.hash);
|
|
76
73
|
const serializedPermit = PermitUtils.serialize(permit);
|
|
77
74
|
|
|
78
75
|
// Verify data is restored
|
|
79
|
-
const retrievedPermit = getPermit(chainId, account, hash);
|
|
76
|
+
const retrievedPermit = getPermit(chainId, account, permit.hash);
|
|
80
77
|
expect(retrievedPermit).toBeDefined();
|
|
81
78
|
expect(PermitUtils.serialize(retrievedPermit!)).toEqual(serializedPermit);
|
|
82
79
|
|
|
83
80
|
const activeHash = getActivePermitHash(chainId, account);
|
|
84
|
-
expect(activeHash).toBe(hash);
|
|
81
|
+
expect(activeHash).toBe(permit.hash);
|
|
85
82
|
});
|
|
86
83
|
|
|
87
84
|
it('should handle corrupted localStorage data gracefully', () => {
|
|
@@ -96,22 +93,21 @@ describe('Permits localStorage Tests', () => {
|
|
|
96
93
|
|
|
97
94
|
it('should clean up localStorage when permits are removed', async () => {
|
|
98
95
|
const permit = await createMockPermit();
|
|
99
|
-
const hash = PermitUtils.getHash(permit);
|
|
100
96
|
|
|
101
97
|
setPermit(chainId, account, permit);
|
|
102
|
-
setActivePermitHash(chainId, account, hash);
|
|
98
|
+
setActivePermitHash(chainId, account, permit.hash);
|
|
103
99
|
|
|
104
100
|
// Verify data exists
|
|
105
101
|
let storedData = localStorage.getItem('cofhesdk-permits');
|
|
106
102
|
expect(storedData).toBeDefined();
|
|
107
103
|
|
|
108
104
|
// Remove permit
|
|
109
|
-
removePermit(chainId, account, hash
|
|
105
|
+
removePermit(chainId, account, permit.hash);
|
|
110
106
|
|
|
111
107
|
// Verify data is cleaned up
|
|
112
108
|
storedData = localStorage.getItem('cofhesdk-permits');
|
|
113
109
|
const parsedData = JSON.parse(storedData!);
|
|
114
|
-
expect(parsedData.state.permits[chainId][account][hash]).toBeUndefined();
|
|
110
|
+
expect(parsedData.state.permits[chainId][account][permit.hash]).toBeUndefined();
|
|
115
111
|
expect(parsedData.state.activePermitHash[chainId][account]).toBeUndefined();
|
|
116
112
|
});
|
|
117
113
|
});
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import {
|
|
2
|
+
BaseError,
|
|
3
|
+
ContractFunctionRevertedError,
|
|
4
|
+
type Hex,
|
|
5
|
+
type PublicClient,
|
|
6
|
+
decodeErrorResult,
|
|
7
|
+
parseAbi,
|
|
8
|
+
} from 'viem';
|
|
9
|
+
import type { EIP712Domain, Permission } from './types';
|
|
10
|
+
import { TASK_MANAGER_ADDRESS } from '../core/consts.js';
|
|
11
|
+
|
|
12
|
+
export const getAclAddress = async (publicClient: PublicClient): Promise<Hex> => {
|
|
13
|
+
const ACL_IFACE = 'function acl() view returns (address)';
|
|
14
|
+
|
|
15
|
+
// Parse the ABI for the ACL function
|
|
16
|
+
const aclAbi = parseAbi([ACL_IFACE]);
|
|
17
|
+
|
|
18
|
+
// Get the ACL address
|
|
19
|
+
return (await publicClient.readContract({
|
|
20
|
+
address: TASK_MANAGER_ADDRESS as `0x${string}`,
|
|
21
|
+
abi: aclAbi,
|
|
22
|
+
functionName: 'acl',
|
|
23
|
+
})) as `0x${string}`;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export const getAclEIP712Domain = async (publicClient: PublicClient): Promise<EIP712Domain> => {
|
|
27
|
+
const aclAddress = await getAclAddress(publicClient);
|
|
28
|
+
const EIP712_DOMAIN_IFACE =
|
|
29
|
+
'function eip712Domain() public view returns (bytes1 fields, string name, string version, uint256 chainId, address verifyingContract, bytes32 salt, uint256[] extensions)';
|
|
30
|
+
|
|
31
|
+
// Parse the ABI for the EIP712 domain function
|
|
32
|
+
const domainAbi = parseAbi([EIP712_DOMAIN_IFACE]);
|
|
33
|
+
|
|
34
|
+
// Get the EIP712 domain
|
|
35
|
+
const domain = await publicClient.readContract({
|
|
36
|
+
address: aclAddress,
|
|
37
|
+
abi: domainAbi,
|
|
38
|
+
functionName: 'eip712Domain',
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
// eslint-disable-next-line no-unused-vars
|
|
42
|
+
const [_fields, name, version, chainId, verifyingContract, _salt, _extensions] = domain;
|
|
43
|
+
|
|
44
|
+
return {
|
|
45
|
+
name,
|
|
46
|
+
version,
|
|
47
|
+
chainId: Number(chainId),
|
|
48
|
+
verifyingContract,
|
|
49
|
+
};
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
export const checkPermitValidityOnChain = async (
|
|
53
|
+
permission: Permission,
|
|
54
|
+
publicClient: PublicClient
|
|
55
|
+
): Promise<boolean> => {
|
|
56
|
+
const aclAddress = await getAclAddress(publicClient);
|
|
57
|
+
|
|
58
|
+
// Check if the permit is valid
|
|
59
|
+
try {
|
|
60
|
+
await publicClient.simulateContract({
|
|
61
|
+
address: aclAddress,
|
|
62
|
+
abi: checkPermitValidityAbi,
|
|
63
|
+
functionName: 'checkPermitValidity',
|
|
64
|
+
args: [
|
|
65
|
+
{
|
|
66
|
+
issuer: permission.issuer,
|
|
67
|
+
expiration: BigInt(permission.expiration),
|
|
68
|
+
recipient: permission.recipient,
|
|
69
|
+
validatorId: BigInt(permission.validatorId),
|
|
70
|
+
validatorContract: permission.validatorContract,
|
|
71
|
+
sealingKey: permission.sealingKey,
|
|
72
|
+
issuerSignature: permission.issuerSignature,
|
|
73
|
+
recipientSignature: permission.recipientSignature,
|
|
74
|
+
},
|
|
75
|
+
],
|
|
76
|
+
});
|
|
77
|
+
return true;
|
|
78
|
+
} catch (err: any) {
|
|
79
|
+
// Viem default handling
|
|
80
|
+
if (err instanceof BaseError) {
|
|
81
|
+
const revertError = err.walk((err: any) => err instanceof ContractFunctionRevertedError);
|
|
82
|
+
if (revertError instanceof ContractFunctionRevertedError) {
|
|
83
|
+
const errorName = revertError.data?.errorName ?? '';
|
|
84
|
+
throw new Error(errorName);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Check details field for custom error names (e.g., from Hardhat test nodes)
|
|
89
|
+
const customErrorName = extractCustomErrorFromDetails(err, checkPermitValidityAbi);
|
|
90
|
+
if (customErrorName) {
|
|
91
|
+
throw new Error(customErrorName);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Hardhat wrapped error will need to be unwrapped to get the return data
|
|
95
|
+
const hhDetailsData = extractReturnData(err);
|
|
96
|
+
if (hhDetailsData != null) {
|
|
97
|
+
const decoded = decodeErrorResult({
|
|
98
|
+
abi: checkPermitValidityAbi,
|
|
99
|
+
data: hhDetailsData,
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
throw new Error(decoded.errorName);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Fallback throw the original error
|
|
106
|
+
throw err;
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
function extractCustomErrorFromDetails(err: unknown, abi: readonly any[]): string | undefined {
|
|
111
|
+
// Check details field for custom error names (e.g., from Hardhat test nodes)
|
|
112
|
+
const anyErr = err as any;
|
|
113
|
+
const details = anyErr?.details ?? anyErr?.cause?.details;
|
|
114
|
+
|
|
115
|
+
if (typeof details === 'string') {
|
|
116
|
+
// Match pattern: "reverted with custom error 'ErrorName()'"
|
|
117
|
+
const customErrorMatch = details.match(/reverted with custom error '(\w+)\(\)'/);
|
|
118
|
+
if (customErrorMatch) {
|
|
119
|
+
const errorName = customErrorMatch[1];
|
|
120
|
+
// Check if this error exists in our ABI
|
|
121
|
+
const errorExists = abi.some((item) => item.type === 'error' && item.name === errorName);
|
|
122
|
+
if (errorExists) {
|
|
123
|
+
return errorName;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return undefined;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function extractReturnData(err: unknown): `0x${string}` | undefined {
|
|
132
|
+
// viem BaseError has `details`, but fall back to any message-like string we can find
|
|
133
|
+
const anyErr = err as any;
|
|
134
|
+
const s = anyErr?.details ?? anyErr?.cause?.details ?? anyErr?.shortMessage ?? anyErr?.message ?? String(err);
|
|
135
|
+
|
|
136
|
+
return s.match(/return data:\s*(0x[a-fA-F0-9]+)/)?.[1] as `0x${string}` | undefined;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const checkPermitValidityAbi = [
|
|
140
|
+
{
|
|
141
|
+
type: 'function',
|
|
142
|
+
name: 'checkPermitValidity',
|
|
143
|
+
inputs: [
|
|
144
|
+
{
|
|
145
|
+
name: 'permission',
|
|
146
|
+
type: 'tuple',
|
|
147
|
+
internalType: 'struct Permission',
|
|
148
|
+
components: [
|
|
149
|
+
{
|
|
150
|
+
name: 'issuer',
|
|
151
|
+
type: 'address',
|
|
152
|
+
internalType: 'address',
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
name: 'expiration',
|
|
156
|
+
type: 'uint64',
|
|
157
|
+
internalType: 'uint64',
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
name: 'recipient',
|
|
161
|
+
type: 'address',
|
|
162
|
+
internalType: 'address',
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
name: 'validatorId',
|
|
166
|
+
type: 'uint256',
|
|
167
|
+
internalType: 'uint256',
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
name: 'validatorContract',
|
|
171
|
+
type: 'address',
|
|
172
|
+
internalType: 'address',
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
name: 'sealingKey',
|
|
176
|
+
type: 'bytes32',
|
|
177
|
+
internalType: 'bytes32',
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
name: 'issuerSignature',
|
|
181
|
+
type: 'bytes',
|
|
182
|
+
internalType: 'bytes',
|
|
183
|
+
},
|
|
184
|
+
{
|
|
185
|
+
name: 'recipientSignature',
|
|
186
|
+
type: 'bytes',
|
|
187
|
+
internalType: 'bytes',
|
|
188
|
+
},
|
|
189
|
+
],
|
|
190
|
+
},
|
|
191
|
+
],
|
|
192
|
+
outputs: [
|
|
193
|
+
{
|
|
194
|
+
name: '',
|
|
195
|
+
type: 'bool',
|
|
196
|
+
internalType: 'bool',
|
|
197
|
+
},
|
|
198
|
+
],
|
|
199
|
+
stateMutability: 'view',
|
|
200
|
+
},
|
|
201
|
+
{
|
|
202
|
+
type: 'error',
|
|
203
|
+
name: 'PermissionInvalid_Disabled',
|
|
204
|
+
inputs: [],
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
type: 'error',
|
|
208
|
+
name: 'PermissionInvalid_Expired',
|
|
209
|
+
inputs: [],
|
|
210
|
+
},
|
|
211
|
+
{
|
|
212
|
+
type: 'error',
|
|
213
|
+
name: 'PermissionInvalid_IssuerSignature',
|
|
214
|
+
inputs: [],
|
|
215
|
+
},
|
|
216
|
+
{
|
|
217
|
+
type: 'error',
|
|
218
|
+
name: 'PermissionInvalid_RecipientSignature',
|
|
219
|
+
inputs: [],
|
|
220
|
+
},
|
|
221
|
+
] as const;
|
package/permits/permit.test.ts
CHANGED
|
@@ -46,6 +46,7 @@ describe('PermitUtils Tests', () => {
|
|
|
46
46
|
|
|
47
47
|
const permit = PermitUtils.createSelf(options);
|
|
48
48
|
|
|
49
|
+
expect(permit.hash).toBe(PermitUtils.getHash(permit));
|
|
49
50
|
expect(permit.type).toBe('self');
|
|
50
51
|
expect(permit.name).toBe('Test Permit');
|
|
51
52
|
expect(permit.type).toBe('self');
|
|
@@ -81,6 +82,7 @@ describe('PermitUtils Tests', () => {
|
|
|
81
82
|
|
|
82
83
|
const permit = PermitUtils.createSharing(options);
|
|
83
84
|
|
|
85
|
+
expect(permit.hash).toBe(PermitUtils.getHash(permit));
|
|
84
86
|
expect(permit.type).toBe('sharing');
|
|
85
87
|
expect(permit.name).toBe('Test Sharing Permit');
|
|
86
88
|
expect(permit.type).toBe('sharing');
|
|
@@ -111,6 +113,7 @@ describe('PermitUtils Tests', () => {
|
|
|
111
113
|
it('should import a shared permit with valid options', async () => {
|
|
112
114
|
const options: ImportSharedPermitOptions = {
|
|
113
115
|
issuer: bobAddress,
|
|
116
|
+
expiration: Math.floor(Date.now() / 1000) + 3600, // 1 hour from now
|
|
114
117
|
recipient: aliceAddress,
|
|
115
118
|
issuerSignature: '0x1234567890abcdef',
|
|
116
119
|
name: 'Test Import Permit',
|
|
@@ -118,6 +121,7 @@ describe('PermitUtils Tests', () => {
|
|
|
118
121
|
|
|
119
122
|
const permit = PermitUtils.importShared(options);
|
|
120
123
|
|
|
124
|
+
expect(permit.hash).toBe(PermitUtils.getHash(permit));
|
|
121
125
|
expect(permit.type).toBe('recipient');
|
|
122
126
|
expect(permit.name).toBe('Test Import Permit');
|
|
123
127
|
expect(permit.issuer).toBe(bobAddress);
|
|
@@ -134,6 +138,7 @@ describe('PermitUtils Tests', () => {
|
|
|
134
138
|
it('should import a shared permit with valid options as string', async () => {
|
|
135
139
|
const options: ImportSharedPermitOptions = {
|
|
136
140
|
issuer: bobAddress,
|
|
141
|
+
expiration: Math.floor(Date.now() / 1000) + 3600, // 1 hour from now
|
|
137
142
|
recipient: aliceAddress,
|
|
138
143
|
issuerSignature: '0x1234567890abcdef',
|
|
139
144
|
};
|
|
@@ -168,6 +173,7 @@ describe('PermitUtils Tests', () => {
|
|
|
168
173
|
it('should throw error for missing issuerSignature', async () => {
|
|
169
174
|
const options: ImportSharedPermitOptions = {
|
|
170
175
|
issuer: bobAddress,
|
|
176
|
+
expiration: Math.floor(Date.now() / 1000) + 3600, // 1 hour from now
|
|
171
177
|
recipient: aliceAddress,
|
|
172
178
|
issuerSignature: '0x', // Invalid empty signature
|
|
173
179
|
name: 'Test Import Permit',
|
|
@@ -175,6 +181,15 @@ describe('PermitUtils Tests', () => {
|
|
|
175
181
|
|
|
176
182
|
expect(() => PermitUtils.importShared(options)).toThrow();
|
|
177
183
|
});
|
|
184
|
+
|
|
185
|
+
it('should throw error for missing expiration', async () => {
|
|
186
|
+
const options = {
|
|
187
|
+
issuer: bobAddress,
|
|
188
|
+
recipient: aliceAddress,
|
|
189
|
+
issuerSignature: '0x1234567890abcdef',
|
|
190
|
+
} as unknown as ImportSharedPermitOptions;
|
|
191
|
+
expect(() => PermitUtils.importShared(options)).toThrow();
|
|
192
|
+
});
|
|
178
193
|
});
|
|
179
194
|
|
|
180
195
|
describe('createSelfAndSign', () => {
|
|
@@ -217,6 +232,7 @@ describe('PermitUtils Tests', () => {
|
|
|
217
232
|
const options: ImportSharedPermitOptions = {
|
|
218
233
|
issuer: bobAddress,
|
|
219
234
|
recipient: aliceAddress,
|
|
235
|
+
expiration: Math.floor(Date.now() / 1000) + 3600, // 1 hour from now
|
|
220
236
|
issuerSignature: '0x1234567890abcdef',
|
|
221
237
|
name: 'Test Import Permit',
|
|
222
238
|
};
|
|
@@ -233,6 +249,7 @@ describe('PermitUtils Tests', () => {
|
|
|
233
249
|
const options: ImportSharedPermitOptions = {
|
|
234
250
|
issuer: bobAddress,
|
|
235
251
|
recipient: aliceAddress,
|
|
252
|
+
expiration: Math.floor(Date.now() / 1000) + 3600, // 1 hour from now
|
|
236
253
|
issuerSignature: '0x1234567890abcdef',
|
|
237
254
|
};
|
|
238
255
|
|
|
@@ -250,6 +267,7 @@ describe('PermitUtils Tests', () => {
|
|
|
250
267
|
const options: ImportSharedPermitOptions = {
|
|
251
268
|
issuer: bobAddress,
|
|
252
269
|
recipient: aliceAddress,
|
|
270
|
+
expiration: Math.floor(Date.now() / 1000) + 3600, // 1 hour from now
|
|
253
271
|
issuerSignature: '0x1234567890abcdef',
|
|
254
272
|
};
|
|
255
273
|
|
|
@@ -283,7 +301,8 @@ describe('PermitUtils Tests', () => {
|
|
|
283
301
|
const permit = PermitUtils.importShared({
|
|
284
302
|
issuer: bobAddress,
|
|
285
303
|
recipient: aliceAddress,
|
|
286
|
-
|
|
304
|
+
expiration: Math.floor(Date.now() / 1000) + 3600, // 1 hour from now
|
|
305
|
+
issuerSignature: '0x1111111111111111111111111111111111111111111111111111111111111111',
|
|
287
306
|
name: 'Test Permit',
|
|
288
307
|
});
|
|
289
308
|
|
|
@@ -361,10 +380,7 @@ describe('PermitUtils Tests', () => {
|
|
|
361
380
|
name: 'Test Permit',
|
|
362
381
|
});
|
|
363
382
|
|
|
364
|
-
|
|
365
|
-
const hash2 = PermitUtils.getHash(permit2);
|
|
366
|
-
|
|
367
|
-
expect(hash1).toBe(hash2);
|
|
383
|
+
expect(permit1.hash).toBe(permit2.hash);
|
|
368
384
|
});
|
|
369
385
|
});
|
|
370
386
|
|
|
@@ -473,5 +489,35 @@ describe('PermitUtils Tests', () => {
|
|
|
473
489
|
|
|
474
490
|
expect(typeof isValid).toBe('boolean');
|
|
475
491
|
}, 10000); // 10 second timeout for network call
|
|
492
|
+
|
|
493
|
+
// TODO: Uncomment when updated ACL with checkPermitValidity function is deployed
|
|
494
|
+
|
|
495
|
+
// it('should check permit validity on chain with real contract data', async () => {
|
|
496
|
+
// const permit = PermitUtils.createSelf({
|
|
497
|
+
// type: 'self',
|
|
498
|
+
// issuer: bobAddress,
|
|
499
|
+
// name: 'Test Permit',
|
|
500
|
+
// });
|
|
501
|
+
|
|
502
|
+
// const signedPermit = await PermitUtils.sign(permit, publicClient, bobWalletClient);
|
|
503
|
+
|
|
504
|
+
// const isValid = await PermitUtils.checkValidityOnChain(signedPermit, publicClient);
|
|
505
|
+
|
|
506
|
+
// expect(typeof isValid).toBe('boolean');
|
|
507
|
+
// expect(isValid).toBe(true);
|
|
508
|
+
|
|
509
|
+
// const permitInvalid = PermitUtils.createSelf({
|
|
510
|
+
// type: 'self',
|
|
511
|
+
// issuer: bobAddress,
|
|
512
|
+
// name: 'Test Permit',
|
|
513
|
+
// expiration: Math.floor(Date.now() / 1000) - 3600, // 1 hour ago
|
|
514
|
+
// });
|
|
515
|
+
|
|
516
|
+
// const signedPermitInvalid = await PermitUtils.sign(permitInvalid, publicClient, bobWalletClient);
|
|
517
|
+
// const isValidInvalid = await PermitUtils.checkValidityOnChain(signedPermitInvalid, publicClient);
|
|
518
|
+
|
|
519
|
+
// expect(typeof isValidInvalid).toBe('boolean');
|
|
520
|
+
// expect(isValidInvalid).toBe(false);
|
|
521
|
+
// });
|
|
476
522
|
});
|
|
477
523
|
});
|
package/permits/permit.ts
CHANGED
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
type EIP712Domain,
|
|
12
12
|
type Permission,
|
|
13
13
|
type EthEncryptedData,
|
|
14
|
+
type PermitHashFields,
|
|
14
15
|
} from './types.js';
|
|
15
16
|
import {
|
|
16
17
|
validateSelfPermitOptions,
|
|
@@ -21,8 +22,10 @@ import {
|
|
|
21
22
|
validateImportPermit,
|
|
22
23
|
ValidationUtils,
|
|
23
24
|
} from './validation.js';
|
|
25
|
+
import * as z from 'zod';
|
|
24
26
|
import { SignatureUtils } from './signature.js';
|
|
25
27
|
import { GenerateSealingKey, SealingKey } from './sealing.js';
|
|
28
|
+
import { checkPermitValidityOnChain, getAclEIP712Domain } from './onchain-utils.js';
|
|
26
29
|
|
|
27
30
|
/**
|
|
28
31
|
* Main Permit utilities - functional approach for React compatibility
|
|
@@ -34,17 +37,12 @@ export const PermitUtils = {
|
|
|
34
37
|
createSelf: (options: CreateSelfPermitOptions): SelfPermit => {
|
|
35
38
|
const validation = validateSelfPermitOptions(options);
|
|
36
39
|
|
|
37
|
-
if (!validation.success) {
|
|
38
|
-
throw new Error(
|
|
39
|
-
'PermitUtils :: createSelf :: Parsing SelfPermitOptions failed ' + JSON.stringify(validation.error, null, 2)
|
|
40
|
-
);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
40
|
// Always generate a new sealing key - users cannot provide their own
|
|
44
41
|
const sealingPair = GenerateSealingKey();
|
|
45
42
|
|
|
46
43
|
const permit = {
|
|
47
|
-
|
|
44
|
+
hash: PermitUtils.getHash(validation),
|
|
45
|
+
...validation,
|
|
48
46
|
sealingPair,
|
|
49
47
|
_signedDomain: undefined,
|
|
50
48
|
} satisfies SelfPermit;
|
|
@@ -58,18 +56,12 @@ export const PermitUtils = {
|
|
|
58
56
|
createSharing: (options: CreateSharingPermitOptions): SharingPermit => {
|
|
59
57
|
const validation = validateSharingPermitOptions(options);
|
|
60
58
|
|
|
61
|
-
if (!validation.success) {
|
|
62
|
-
throw new Error(
|
|
63
|
-
'PermitUtils :: createSharing :: Parsing SharingPermitOptions failed ' +
|
|
64
|
-
JSON.stringify(validation.error, null, 2)
|
|
65
|
-
);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
59
|
// Always generate a new sealing key - users cannot provide their own
|
|
69
60
|
const sealingPair = GenerateSealingKey();
|
|
70
61
|
|
|
71
62
|
const permit = {
|
|
72
|
-
|
|
63
|
+
hash: PermitUtils.getHash(validation),
|
|
64
|
+
...validation,
|
|
73
65
|
sealingPair,
|
|
74
66
|
_signedDomain: undefined,
|
|
75
67
|
} satisfies SharingPermit;
|
|
@@ -89,35 +81,28 @@ export const PermitUtils = {
|
|
|
89
81
|
try {
|
|
90
82
|
parsedOptions = JSON.parse(options);
|
|
91
83
|
} catch (error) {
|
|
92
|
-
throw new Error(`
|
|
84
|
+
throw new Error(`Failed to parse JSON string: ${error}`);
|
|
93
85
|
}
|
|
94
86
|
} else if (typeof options === 'object' && options !== null) {
|
|
95
87
|
// Handle both ImportSharedPermitOptions and any object
|
|
96
88
|
parsedOptions = options;
|
|
97
89
|
} else {
|
|
98
|
-
throw new Error(
|
|
99
|
-
'PermitUtils :: importShared :: Invalid input type, expected ImportSharedPermitOptions, object, or string'
|
|
100
|
-
);
|
|
90
|
+
throw new Error('Invalid input type, expected ImportSharedPermitOptions, object, or string');
|
|
101
91
|
}
|
|
102
92
|
|
|
103
93
|
// Validate type if provided
|
|
104
94
|
if (parsedOptions.type != null && parsedOptions.type !== 'sharing') {
|
|
105
|
-
throw new Error(`
|
|
95
|
+
throw new Error(`Invalid permit type <${parsedOptions.type}>, must be "sharing"`);
|
|
106
96
|
}
|
|
107
97
|
|
|
108
98
|
const validation = validateImportPermitOptions({ ...parsedOptions, type: 'recipient' });
|
|
109
99
|
|
|
110
|
-
if (!validation.success) {
|
|
111
|
-
throw new Error(
|
|
112
|
-
'PermitUtils :: importShared :: Parsing ImportPermitOptions failed ' + JSON.stringify(validation.error, null, 2)
|
|
113
|
-
);
|
|
114
|
-
}
|
|
115
|
-
|
|
116
100
|
// Always generate a new sealing key - users cannot provide their own
|
|
117
101
|
const sealingPair = GenerateSealingKey();
|
|
118
102
|
|
|
119
103
|
const permit = {
|
|
120
|
-
|
|
104
|
+
hash: PermitUtils.getHash(validation),
|
|
105
|
+
...validation,
|
|
121
106
|
sealingPair,
|
|
122
107
|
_signedDomain: undefined,
|
|
123
108
|
} satisfies RecipientPermit;
|
|
@@ -131,12 +116,12 @@ export const PermitUtils = {
|
|
|
131
116
|
sign: async <T extends Permit>(permit: T, publicClient: PublicClient, walletClient: WalletClient): Promise<T> => {
|
|
132
117
|
if (walletClient == null || walletClient.account == null) {
|
|
133
118
|
throw new Error(
|
|
134
|
-
'
|
|
119
|
+
'Missing walletClient, you must pass in a `walletClient` for the connected user to create a permit signature'
|
|
135
120
|
);
|
|
136
121
|
}
|
|
137
122
|
|
|
138
123
|
const primaryType = SignatureUtils.getPrimaryType(permit.type);
|
|
139
|
-
const domain = await
|
|
124
|
+
const domain = await getAclEIP712Domain(publicClient);
|
|
140
125
|
const { types, message } = SignatureUtils.getSignatureParams(PermitUtils.getPermission(permit, true), primaryType);
|
|
141
126
|
|
|
142
127
|
const signature = await walletClient.signTypedData({
|
|
@@ -216,6 +201,7 @@ export const PermitUtils = {
|
|
|
216
201
|
*/
|
|
217
202
|
serialize: (permit: Permit): SerializedPermit => {
|
|
218
203
|
return {
|
|
204
|
+
hash: permit.hash,
|
|
219
205
|
name: permit.name,
|
|
220
206
|
type: permit.type,
|
|
221
207
|
issuer: permit.issuer,
|
|
@@ -241,7 +227,7 @@ export const PermitUtils = {
|
|
|
241
227
|
} else if (permit.type === 'recipient') {
|
|
242
228
|
return validateImportPermit(permit);
|
|
243
229
|
} else {
|
|
244
|
-
throw new Error('
|
|
230
|
+
throw new Error('Invalid permit type');
|
|
245
231
|
}
|
|
246
232
|
},
|
|
247
233
|
|
|
@@ -250,13 +236,7 @@ export const PermitUtils = {
|
|
|
250
236
|
*/
|
|
251
237
|
getPermission: (permit: Permit, skipValidation = false): Permission => {
|
|
252
238
|
if (!skipValidation) {
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
if (!validationResult.success) {
|
|
256
|
-
throw new Error(
|
|
257
|
-
`PermitUtils :: getPermission :: permit validation failed - ${JSON.stringify(validationResult.error, null, 2)} ${JSON.stringify(permit, null, 2)}`
|
|
258
|
-
);
|
|
259
|
-
}
|
|
239
|
+
PermitUtils.validate(permit);
|
|
260
240
|
}
|
|
261
241
|
|
|
262
242
|
return {
|
|
@@ -274,7 +254,7 @@ export const PermitUtils = {
|
|
|
274
254
|
/**
|
|
275
255
|
* Get a stable hash for the permit (used as key in storage)
|
|
276
256
|
*/
|
|
277
|
-
getHash: (permit:
|
|
257
|
+
getHash: (permit: PermitHashFields): string => {
|
|
278
258
|
const data = JSON.stringify({
|
|
279
259
|
type: permit.type,
|
|
280
260
|
issuer: permit.issuer,
|
|
@@ -345,41 +325,7 @@ export const PermitUtils = {
|
|
|
345
325
|
* Fetch EIP712 domain from the blockchain
|
|
346
326
|
*/
|
|
347
327
|
fetchEIP712Domain: async (publicClient: PublicClient): Promise<EIP712Domain> => {
|
|
348
|
-
|
|
349
|
-
const TASK_MANAGER_ADDRESS = '0xeA30c4B8b44078Bbf8a6ef5b9f1eC1626C7848D9';
|
|
350
|
-
const ACL_IFACE = 'function acl() view returns (address)';
|
|
351
|
-
const EIP712_DOMAIN_IFACE =
|
|
352
|
-
'function eip712Domain() public view returns (bytes1 fields, string name, string version, uint256 chainId, address verifyingContract, bytes32 salt, uint256[] extensions)';
|
|
353
|
-
|
|
354
|
-
// Parse the ABI for the ACL function
|
|
355
|
-
const aclAbi = parseAbi([ACL_IFACE]);
|
|
356
|
-
|
|
357
|
-
// Get the ACL address
|
|
358
|
-
const aclAddress = (await publicClient.readContract({
|
|
359
|
-
address: TASK_MANAGER_ADDRESS as `0x${string}`,
|
|
360
|
-
abi: aclAbi,
|
|
361
|
-
functionName: 'acl',
|
|
362
|
-
})) as `0x${string}`;
|
|
363
|
-
|
|
364
|
-
// Parse the ABI for the EIP712 domain function
|
|
365
|
-
const domainAbi = parseAbi([EIP712_DOMAIN_IFACE]);
|
|
366
|
-
|
|
367
|
-
// Get the EIP712 domain
|
|
368
|
-
const domain = await publicClient.readContract({
|
|
369
|
-
address: aclAddress,
|
|
370
|
-
abi: domainAbi,
|
|
371
|
-
functionName: 'eip712Domain',
|
|
372
|
-
});
|
|
373
|
-
|
|
374
|
-
// eslint-disable-next-line no-unused-vars
|
|
375
|
-
const [_fields, name, version, chainId, verifyingContract, _salt, _extensions] = domain;
|
|
376
|
-
|
|
377
|
-
return {
|
|
378
|
-
name,
|
|
379
|
-
version,
|
|
380
|
-
chainId: Number(chainId),
|
|
381
|
-
verifyingContract,
|
|
382
|
-
};
|
|
328
|
+
return getAclEIP712Domain(publicClient);
|
|
383
329
|
},
|
|
384
330
|
|
|
385
331
|
/**
|
|
@@ -399,7 +345,15 @@ export const PermitUtils = {
|
|
|
399
345
|
*/
|
|
400
346
|
checkSignedDomainValid: async (permit: Permit, publicClient: PublicClient): Promise<boolean> => {
|
|
401
347
|
if (permit._signedDomain == null) return false;
|
|
402
|
-
const domain = await
|
|
348
|
+
const domain = await getAclEIP712Domain(publicClient);
|
|
403
349
|
return PermitUtils.matchesDomain(permit, domain);
|
|
404
350
|
},
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* Check if permit passes the on-chain validation
|
|
354
|
+
*/
|
|
355
|
+
checkValidityOnChain: async (permit: Permit, publicClient: PublicClient): Promise<boolean> => {
|
|
356
|
+
const permission = PermitUtils.getPermission(permit);
|
|
357
|
+
return checkPermitValidityOnChain(permission, publicClient);
|
|
358
|
+
},
|
|
405
359
|
};
|