@cofhe/sdk 1.0.0 → 1.1.0-alpha.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/core/acps.ts +64 -23
- package/core/client.ts +32 -1
- package/core/clientTypes.ts +22 -1
- package/core/test/acpsPrepare.test.ts +77 -0
- package/dist/acps.d.cts +4 -358
- package/dist/acps.d.ts +4 -358
- package/dist/{chunk-O2ITEUEU.js → chunk-2EO4JSR7.js} +63 -21
- package/dist/{clientTypes-b1pOxOUE.d.ts → clientTypes-CnslFzxB.d.ts} +38 -2
- package/dist/{clientTypes-IO6b5Um8.d.cts → clientTypes-RXwLVWou.d.cts} +38 -2
- package/dist/core.cjs +62 -20
- package/dist/core.d.cts +3 -3
- package/dist/core.d.ts +3 -3
- package/dist/core.js +1 -1
- package/dist/node.cjs +62 -20
- package/dist/node.d.cts +2 -2
- package/dist/node.d.ts +2 -2
- package/dist/node.js +1 -1
- package/dist/{onchain-utils-CgOslubH.d.ts → onchain-utils-Ns56cBm0.d.cts} +356 -2
- package/dist/{onchain-utils-CgOslubH.d.cts → onchain-utils-Ns56cBm0.d.ts} +356 -2
- package/dist/web.cjs +62 -20
- package/dist/web.d.cts +2 -2
- package/dist/web.d.ts +2 -2
- package/dist/web.js +1 -1
- package/package.json +3 -3
package/core/acps.ts
CHANGED
|
@@ -26,7 +26,7 @@ import {
|
|
|
26
26
|
clearAclServedAddresses,
|
|
27
27
|
} from '@/acps';
|
|
28
28
|
|
|
29
|
-
import { type Hex, type PublicClient, type WalletClient, zeroAddress } from 'viem';
|
|
29
|
+
import { type ContractFunctionParameters, type Hex, type PublicClient, type WalletClient, zeroAddress } from 'viem';
|
|
30
30
|
|
|
31
31
|
// HELPERS
|
|
32
32
|
|
|
@@ -311,23 +311,39 @@ const getBlockRevokerData = async (publicClient: PublicClient): Promise<number>
|
|
|
311
311
|
*
|
|
312
312
|
* @returns the revocation transaction hash
|
|
313
313
|
*/
|
|
314
|
-
|
|
314
|
+
/**
|
|
315
|
+
* A registry or revoker write built but not sent: what a simulation or `writeContract` takes,
|
|
316
|
+
* minus the sender. For a caller that sends through its own path (simulate first, record the
|
|
317
|
+
* transaction) instead of the plain `writeContract` the senders below do.
|
|
318
|
+
*/
|
|
319
|
+
export type ShareRegistryWrite<functionName extends 'share' | 'removeShare'> = ContractFunctionParameters<
|
|
320
|
+
typeof ACP_SHARE_REGISTRY_ABI,
|
|
321
|
+
'nonpayable',
|
|
322
|
+
functionName
|
|
323
|
+
>;
|
|
324
|
+
export type RevokerWrite = ContractFunctionParameters<typeof ACP_REVOKER_ABI, 'nonpayable', 'revokeSingle'>;
|
|
325
|
+
|
|
326
|
+
/** The revoker write that revokes one acp, built but not sent. Throws for an acp without a revoker. */
|
|
327
|
+
const prepareRevokeACP = (acp: ACP): RevokerWrite => {
|
|
315
328
|
if (acp.revokerContract === zeroAddress || acp.revokerData === 0) {
|
|
316
329
|
throw new Error('ACP is not revocable: it has no revoker (revokerContract/revokerData unset)');
|
|
317
330
|
}
|
|
331
|
+
return {
|
|
332
|
+
address: acp.revokerContract,
|
|
333
|
+
abi: ACP_REVOKER_ABI,
|
|
334
|
+
functionName: 'revokeSingle',
|
|
335
|
+
args: [BigInt(acp.revokerData)],
|
|
336
|
+
};
|
|
337
|
+
};
|
|
338
|
+
|
|
339
|
+
const revokeACP = async (acp: ACP, walletClient: WalletClient): Promise<Hex> => {
|
|
340
|
+
const write = prepareRevokeACP(acp);
|
|
318
341
|
if (walletClient.account == null) throw new Error('Missing walletClient account');
|
|
319
342
|
if (walletClient.account.address.toLowerCase() !== acp.issuer.toLowerCase()) {
|
|
320
343
|
throw new Error('Only the acp issuer can revoke it');
|
|
321
344
|
}
|
|
322
345
|
|
|
323
|
-
return walletClient.writeContract({
|
|
324
|
-
address: acp.revokerContract,
|
|
325
|
-
abi: ACP_REVOKER_ABI,
|
|
326
|
-
functionName: 'revokeSingle',
|
|
327
|
-
args: [BigInt(acp.revokerData)],
|
|
328
|
-
account: walletClient.account,
|
|
329
|
-
chain: walletClient.chain,
|
|
330
|
-
});
|
|
346
|
+
return walletClient.writeContract({ ...write, account: walletClient.account, chain: walletClient.chain });
|
|
331
347
|
};
|
|
332
348
|
|
|
333
349
|
/**
|
|
@@ -416,34 +432,51 @@ const metadataOf = (acp: ACP, { labels, metadata, appKey }: ShareOnChainOptions)
|
|
|
416
432
|
* to discover and import — the on-chain alternative to `export()` — optionally
|
|
417
433
|
* with the labels of its handles.
|
|
418
434
|
*/
|
|
419
|
-
|
|
435
|
+
/**
|
|
436
|
+
* The registry `share` write for a signed sharing acp, built but not sent, with the share id it
|
|
437
|
+
* gets. Validates the acp, not the sender: the registry takes the write from the issuer only.
|
|
438
|
+
*/
|
|
439
|
+
const prepareShareOnChain = (
|
|
420
440
|
acp: ACP,
|
|
421
|
-
walletClient: WalletClient,
|
|
422
441
|
registry: Hex,
|
|
423
442
|
options: ShareOnChainOptions = {}
|
|
424
|
-
):
|
|
443
|
+
): { write: ShareRegistryWrite<'share'>; shareId: Hex } => {
|
|
425
444
|
if (acp.type !== 'sharing') {
|
|
426
445
|
throw new Error(`Cannot share a '${acp.type}' ACP on-chain — only 'sharing' ACPs are shareable.`);
|
|
427
446
|
}
|
|
428
447
|
if (acp.issuerSignature === '0x') {
|
|
429
448
|
throw new Error('Cannot share an unsigned sharing ACP — sign it first.');
|
|
430
449
|
}
|
|
450
|
+
return {
|
|
451
|
+
write: {
|
|
452
|
+
address: registry,
|
|
453
|
+
abi: ACP_SHARE_REGISTRY_ABI,
|
|
454
|
+
functionName: 'share',
|
|
455
|
+
args: [toChainShare(acp), metadataOf(acp, options)],
|
|
456
|
+
},
|
|
457
|
+
shareId: computeShareId(acp),
|
|
458
|
+
};
|
|
459
|
+
};
|
|
460
|
+
|
|
461
|
+
const shareOnChain = async (
|
|
462
|
+
acp: ACP,
|
|
463
|
+
walletClient: WalletClient,
|
|
464
|
+
registry: Hex,
|
|
465
|
+
options: ShareOnChainOptions = {}
|
|
466
|
+
): Promise<{ txHash: Hex; shareId: Hex }> => {
|
|
467
|
+
const { write, shareId } = prepareShareOnChain(acp, registry, options);
|
|
431
468
|
if (walletClient.account == null) throw new Error('Missing walletClient account');
|
|
432
469
|
if (walletClient.account.address.toLowerCase() !== acp.issuer.toLowerCase()) {
|
|
433
470
|
throw new Error('Only the ACP issuer can share it on-chain');
|
|
434
471
|
}
|
|
435
|
-
const metadata = metadataOf(acp, options);
|
|
436
472
|
|
|
437
473
|
const txHash = await walletClient.writeContract({
|
|
438
|
-
|
|
439
|
-
abi: ACP_SHARE_REGISTRY_ABI,
|
|
440
|
-
functionName: 'share',
|
|
441
|
-
args: [toChainShare(acp), metadata],
|
|
474
|
+
...write,
|
|
442
475
|
account: walletClient.account,
|
|
443
476
|
chain: walletClient.chain ?? null,
|
|
444
477
|
});
|
|
445
478
|
|
|
446
|
-
return { txHash, shareId
|
|
479
|
+
return { txHash, shareId };
|
|
447
480
|
};
|
|
448
481
|
|
|
449
482
|
const toIncomingShare = ({ shareId, share: s, metadata }: PostedShare): IncomingShare => ({
|
|
@@ -514,14 +547,19 @@ const importFromChain = async (
|
|
|
514
547
|
return importShared({ ...options, type: 'sharing' }, publicClient, walletClient, importOptions);
|
|
515
548
|
};
|
|
516
549
|
|
|
550
|
+
/** The registry `removeShare` write for one share id, built but not sent. */
|
|
551
|
+
const prepareRemoveShare = (shareId: Hex, registry: Hex): ShareRegistryWrite<'removeShare'> => ({
|
|
552
|
+
address: registry,
|
|
553
|
+
abi: ACP_SHARE_REGISTRY_ABI,
|
|
554
|
+
functionName: 'removeShare',
|
|
555
|
+
args: [shareId],
|
|
556
|
+
});
|
|
557
|
+
|
|
517
558
|
/** Remove a share from the registry (issuer retracts / recipient dismisses). */
|
|
518
559
|
const removeShareOnChain = async (shareId: Hex, walletClient: WalletClient, registry: Hex): Promise<Hex> => {
|
|
519
560
|
if (walletClient.account == null) throw new Error('Missing walletClient account');
|
|
520
561
|
return walletClient.writeContract({
|
|
521
|
-
|
|
522
|
-
abi: ACP_SHARE_REGISTRY_ABI,
|
|
523
|
-
functionName: 'removeShare',
|
|
524
|
-
args: [shareId],
|
|
562
|
+
...prepareRemoveShare(shareId, registry),
|
|
525
563
|
account: walletClient.account,
|
|
526
564
|
chain: walletClient.chain ?? null,
|
|
527
565
|
});
|
|
@@ -562,15 +600,18 @@ export const acps = {
|
|
|
562
600
|
removeActiveACP,
|
|
563
601
|
|
|
564
602
|
revokeACP,
|
|
603
|
+
prepareRevokeACP,
|
|
565
604
|
revokeAllACPs,
|
|
566
605
|
isACPRevoked,
|
|
567
606
|
checkAccess,
|
|
568
607
|
|
|
569
608
|
shareOnChain,
|
|
609
|
+
prepareShareOnChain,
|
|
570
610
|
getIncomingShares,
|
|
571
611
|
getShareFromChain,
|
|
572
612
|
importFromChain,
|
|
573
613
|
removeShareOnChain,
|
|
614
|
+
prepareRemoveShare,
|
|
574
615
|
computeShareId,
|
|
575
616
|
|
|
576
617
|
applyACPDefaultsFromChain,
|
package/core/client.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { CreateSelfACPOptions, CreateSharingACPOptions, ImportSharedACPOptions } from '@/acps';
|
|
1
|
+
import type { ACP, CreateSelfACPOptions, CreateSharingACPOptions, ImportSharedACPOptions } from '@/acps';
|
|
2
2
|
import { decodeShareMetadata, shareAppKey, verifyShareLabels } from '@/acps';
|
|
3
3
|
|
|
4
4
|
import { createStore } from 'zustand/vanilla';
|
|
@@ -90,6 +90,14 @@ export function createCofheClientBase<TConfig extends CofheConfig>(
|
|
|
90
90
|
}
|
|
91
91
|
};
|
|
92
92
|
|
|
93
|
+
/** The sharing writes the registry and the revoker take from the issuer only; say so before building one. */
|
|
94
|
+
const _requireIssuer = (acp: ACP, what: string) => {
|
|
95
|
+
const sender = connectStore.getState().walletClient?.account?.address;
|
|
96
|
+
if (sender == null || sender.toLowerCase() !== acp.issuer.toLowerCase()) {
|
|
97
|
+
throw new Error(`Only the acp issuer can ${what}`);
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
|
|
93
101
|
// LIFECYCLE
|
|
94
102
|
|
|
95
103
|
async function connect(publicClient: PublicClient, walletClient: WalletClient) {
|
|
@@ -306,6 +314,12 @@ export function createCofheClientBase<TConfig extends CofheConfig>(
|
|
|
306
314
|
return acps.revokeACP(acp, walletClient!);
|
|
307
315
|
},
|
|
308
316
|
|
|
317
|
+
prepareRevokeACP: (acp) => {
|
|
318
|
+
_requireConnected();
|
|
319
|
+
_requireIssuer(acp, 'revoke it');
|
|
320
|
+
return acps.prepareRevokeACP(acp);
|
|
321
|
+
},
|
|
322
|
+
|
|
309
323
|
revokeAllACPs: async (revokerContract?: `0x${string}`) => {
|
|
310
324
|
_requireConnected();
|
|
311
325
|
const { publicClient, walletClient } = connectStore.getState();
|
|
@@ -333,6 +347,23 @@ export function createCofheClientBase<TConfig extends CofheConfig>(
|
|
|
333
347
|
return acps.shareOnChain(acp, walletClient!, sharingRegistry, { ...withAppKey(), ...options });
|
|
334
348
|
},
|
|
335
349
|
|
|
350
|
+
prepareShareOnChain: async (acp, options) => {
|
|
351
|
+
_requireConnected();
|
|
352
|
+
_requireIssuer(acp, 'share it on-chain');
|
|
353
|
+
const { publicClient } = connectStore.getState();
|
|
354
|
+
const chainId = await publicClient!.getChainId();
|
|
355
|
+
const sharingRegistry = await _resolveSharingRegistry(publicClient!, chainId);
|
|
356
|
+
return acps.prepareShareOnChain(acp, sharingRegistry, { ...withAppKey(), ...options });
|
|
357
|
+
},
|
|
358
|
+
|
|
359
|
+
prepareRemoveShare: async (shareId) => {
|
|
360
|
+
_requireConnected();
|
|
361
|
+
const { publicClient } = connectStore.getState();
|
|
362
|
+
const chainId = await publicClient!.getChainId();
|
|
363
|
+
const sharingRegistry = await _resolveSharingRegistry(publicClient!, chainId);
|
|
364
|
+
return acps.prepareRemoveShare(shareId, sharingRegistry);
|
|
365
|
+
},
|
|
366
|
+
|
|
336
367
|
getIncomingShares: async () => {
|
|
337
368
|
_requireConnected();
|
|
338
369
|
const { publicClient, walletClient } = connectStore.getState();
|
package/core/clientTypes.ts
CHANGED
|
@@ -6,7 +6,13 @@ import { type DecryptForTxBuilderUnset } from './decrypt/decryptForTxBuilder.js'
|
|
|
6
6
|
import { type EncryptInputsBuilderUnset } from './encrypt/encryptInputsBuilder.js';
|
|
7
7
|
import { type ZkBuilderAndCrsGenerator, type ZkProveWorkerFunction } from './encrypt/zkPackProveVerify.js';
|
|
8
8
|
import { type FheKeyDeserializer } from './fetchKeys.js';
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
acps,
|
|
11
|
+
type ImportSharedOptions,
|
|
12
|
+
type RevokerWrite,
|
|
13
|
+
type ShareOnChainOptions,
|
|
14
|
+
type ShareRegistryWrite,
|
|
15
|
+
} from './acps.js';
|
|
10
16
|
import type { EncryptableItem, FheTypes, TfheInitializer } from './types.js';
|
|
11
17
|
import type { ACPUtils } from 'acps/acp.js';
|
|
12
18
|
import type {
|
|
@@ -114,6 +120,11 @@ export type CofheClientACPs = {
|
|
|
114
120
|
|
|
115
121
|
// Revocation (on-chain, require connection)
|
|
116
122
|
revokeACP: (acp: ACP) => Promise<`0x${string}`>;
|
|
123
|
+
/**
|
|
124
|
+
* The revoker write `revokeACP` sends, built but not sent — for an app that sends through its own
|
|
125
|
+
* path (a simulation first, a transaction store). Issuer only; throws for an acp without a revoker.
|
|
126
|
+
*/
|
|
127
|
+
prepareRevokeACP: (acp: ACP) => RevokerWrite;
|
|
117
128
|
revokeAllACPs: (revokerContract?: `0x${string}`) => Promise<`0x${string}`>;
|
|
118
129
|
isACPRevoked: (acp: ACP) => Promise<boolean>;
|
|
119
130
|
/**
|
|
@@ -129,6 +140,16 @@ export type CofheClientACPs = {
|
|
|
129
140
|
* optionally with the labels of its handles (`options.labels`, one per handle) or an encoded blob.
|
|
130
141
|
*/
|
|
131
142
|
shareOnChain: (acp: ACP, options?: ShareOnChainOptions) => Promise<{ txHash: `0x${string}`; shareId: `0x${string}` }>;
|
|
143
|
+
/**
|
|
144
|
+
* The registry write `shareOnChain` sends and the share id it gets, built but not sent — for an
|
|
145
|
+
* app that sends through its own path (a simulation first, a transaction store). Issuer only.
|
|
146
|
+
*/
|
|
147
|
+
prepareShareOnChain: (
|
|
148
|
+
acp: ACP,
|
|
149
|
+
options?: ShareOnChainOptions
|
|
150
|
+
) => Promise<{ write: ShareRegistryWrite<'share'>; shareId: `0x${string}` }>;
|
|
151
|
+
/** The registry write `dismissShare` / `cancelShare` sends, built but not sent. */
|
|
152
|
+
prepareRemoveShare: (shareId: `0x${string}`) => Promise<ShareRegistryWrite<'removeShare'>>;
|
|
132
153
|
/** Importable shares addressed to the connected account (unexpired, not revoked), with their metadata. */
|
|
133
154
|
getIncomingShares: () => Promise<IncomingShare[]>;
|
|
134
155
|
/** One share by id, as posted; null when unknown or removed. */
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import type { Hex } from 'viem';
|
|
3
|
+
import { ACP_REVOKER_ABI, ACP_SHARE_REGISTRY_ABI, computeShareId, encodeShareMetadata, toChainShare } from '@/acps';
|
|
4
|
+
import { UNLABELLED } from '../../acps/share-metadata';
|
|
5
|
+
import { createMockACP } from '../../acps/test-utils';
|
|
6
|
+
import { acps } from '../acps';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* The sharing writes built but not sent: what a caller that sends through its own path
|
|
10
|
+
* (simulation, transaction store) gets, and what the plain senders send. No chain involved.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const REGISTRY = '0x00000000000000000000000000000000000000A1';
|
|
14
|
+
const REVOKER = '0x00000000000000000000000000000000000000B2';
|
|
15
|
+
const ALICE = '0x70997970C51812dc3A010C7d01b50e0d17dc79C8';
|
|
16
|
+
const HANDLE: Hex = `0x${'1'.repeat(64)}`;
|
|
17
|
+
|
|
18
|
+
const signedSharing = (overrides: Parameters<typeof createMockACP>[0] = {}) =>
|
|
19
|
+
createMockACP({ type: 'sharing', recipient: ALICE, issuerSignature: '0x1234', ...overrides });
|
|
20
|
+
|
|
21
|
+
describe('prepareShareOnChain', () => {
|
|
22
|
+
it('a signed sharing acp: the registry share call with the acp and its metadata, and its share id', async () => {
|
|
23
|
+
const acp = await signedSharing();
|
|
24
|
+
const { write, shareId } = acps.prepareShareOnChain(acp, REGISTRY);
|
|
25
|
+
expect(write).toStrictEqual({
|
|
26
|
+
address: REGISTRY,
|
|
27
|
+
abi: ACP_SHARE_REGISTRY_ABI,
|
|
28
|
+
functionName: 'share',
|
|
29
|
+
args: [toChainShare(acp), '0x'],
|
|
30
|
+
});
|
|
31
|
+
expect(shareId).toBe(computeShareId(acp));
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('posts the labels it is given, else the metadata on the acp', async () => {
|
|
35
|
+
const acp = await signedSharing({ handles: [HANDLE] });
|
|
36
|
+
const labelled = acps.prepareShareOnChain(acp, REGISTRY, { labels: [UNLABELLED] });
|
|
37
|
+
expect(labelled.write.args[1]).toBe(encodeShareMetadata([UNLABELLED], { issuer: acp.issuer }));
|
|
38
|
+
|
|
39
|
+
const kept = acps.prepareShareOnChain({ ...acp, metadata: '0xdead' }, REGISTRY);
|
|
40
|
+
expect(kept.write.args[1]).toBe('0xdead');
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('refuses a self acp and an unsigned sharing acp', async () => {
|
|
44
|
+
const self = await createMockACP();
|
|
45
|
+
expect(() => acps.prepareShareOnChain(self, REGISTRY)).toThrow(/only 'sharing' ACPs/);
|
|
46
|
+
const unsigned = await createMockACP({ type: 'sharing', recipient: ALICE });
|
|
47
|
+
expect(() => acps.prepareShareOnChain(unsigned, REGISTRY)).toThrow(/sign it first/);
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
describe('prepareRemoveShare', () => {
|
|
52
|
+
it('the registry removeShare call for the share id', () => {
|
|
53
|
+
expect(acps.prepareRemoveShare(HANDLE, REGISTRY)).toStrictEqual({
|
|
54
|
+
address: REGISTRY,
|
|
55
|
+
abi: ACP_SHARE_REGISTRY_ABI,
|
|
56
|
+
functionName: 'removeShare',
|
|
57
|
+
args: [HANDLE],
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
describe('prepareRevokeACP', () => {
|
|
63
|
+
it('the revoker revokeSingle call for the acp revoker data', async () => {
|
|
64
|
+
const acp = await signedSharing({ revokerContract: REVOKER, revokerData: 42 });
|
|
65
|
+
expect(acps.prepareRevokeACP(acp)).toStrictEqual({
|
|
66
|
+
address: REVOKER,
|
|
67
|
+
abi: ACP_REVOKER_ABI,
|
|
68
|
+
functionName: 'revokeSingle',
|
|
69
|
+
args: [42n],
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('refuses an acp without a revoker', async () => {
|
|
74
|
+
const acp = await signedSharing();
|
|
75
|
+
expect(() => acps.prepareRevokeACP(acp)).toThrow(/not revocable/);
|
|
76
|
+
});
|
|
77
|
+
});
|