@getcofund/protocol 0.0.0 → 0.1.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/LICENSE +21 -0
- package/README.md +413 -1
- package/dist/btc/index.d.ts +12 -0
- package/dist/btc/index.js +13 -0
- package/dist/btc/psbt.d.ts +121 -0
- package/dist/btc/psbt.js +377 -0
- package/dist/btc/reader.d.ts +42 -0
- package/dist/btc/reader.js +85 -0
- package/dist/btc/select.d.ts +48 -0
- package/dist/btc/select.js +59 -0
- package/dist/btc/spend-rules.d.ts +24 -0
- package/dist/btc/spend-rules.js +43 -0
- package/dist/btc/spend.d.ts +136 -0
- package/dist/btc/spend.js +180 -0
- package/dist/btc/types.d.ts +65 -0
- package/dist/btc/types.js +62 -0
- package/dist/btc/vault/bip341-nums.d.ts +35 -0
- package/dist/btc/vault/bip341-nums.js +121 -0
- package/dist/btc/vault/build.d.ts +5 -0
- package/dist/btc/vault/build.js +136 -0
- package/dist/btc/vault/canonical-order.d.ts +10 -0
- package/dist/btc/vault/canonical-order.js +18 -0
- package/dist/btc/vault/from-contract-id.d.ts +9 -0
- package/dist/btc/vault/from-contract-id.js +74 -0
- package/dist/btc/vault/helpers.d.ts +73 -0
- package/dist/btc/vault/helpers.js +129 -0
- package/dist/btc/vault/index.d.ts +21 -0
- package/dist/btc/vault/index.js +21 -0
- package/dist/btc/vault/limits.d.ts +9 -0
- package/dist/btc/vault/limits.js +9 -0
- package/dist/btc/vault/participant-order.d.ts +69 -0
- package/dist/btc/vault/participant-order.js +135 -0
- package/dist/btc/vault/script.d.ts +13 -0
- package/dist/btc/vault/script.js +39 -0
- package/dist/btc/vault/spend.d.ts +63 -0
- package/dist/btc/vault/spend.js +93 -0
- package/dist/btc/vault/tx-size.d.ts +66 -0
- package/dist/btc/vault/tx-size.js +71 -0
- package/dist/btc/vault/types.d.ts +49 -0
- package/dist/btc/vault/types.js +1 -0
- package/dist/btc/vault/vault-policy.d.ts +109 -0
- package/dist/btc/vault/vault-policy.js +213 -0
- package/dist/btc/vault/vaultContractUtil.d.ts +80 -0
- package/dist/btc/vault/vaultContractUtil.js +87 -0
- package/dist/btc/vault/xpub.d.ts +28 -0
- package/dist/btc/vault/xpub.js +41 -0
- package/dist/btc/witness.d.ts +101 -0
- package/dist/btc/witness.js +439 -0
- package/dist/btc-policies/activation.d.ts +76 -0
- package/dist/btc-policies/activation.js +153 -0
- package/dist/btc-policies/index.d.ts +7 -0
- package/dist/btc-policies/index.js +8 -0
- package/dist/btc-policies/policy.d.ts +55 -0
- package/dist/btc-policies/policy.js +210 -0
- package/dist/evm/abis/index.d.ts +10 -0
- package/dist/evm/abis/index.js +11 -0
- package/dist/evm/abis/policies.d.ts +279 -0
- package/dist/evm/abis/policies.js +363 -0
- package/dist/evm/abis/sponsor-guard.d.ts +201 -0
- package/dist/evm/abis/sponsor-guard.js +263 -0
- package/dist/evm/abis/state.d.ts +989 -0
- package/dist/evm/abis/state.js +1290 -0
- package/dist/evm/abis/users.d.ts +408 -0
- package/dist/evm/abis/users.js +532 -0
- package/dist/evm/abis/vault-factory.d.ts +92 -0
- package/dist/evm/abis/vault-factory.js +126 -0
- package/dist/evm/abis/vault.d.ts +343 -0
- package/dist/evm/abis/vault.js +444 -0
- package/dist/evm/address.d.ts +2 -0
- package/dist/evm/address.js +4 -0
- package/dist/evm/calls/common.d.ts +30 -0
- package/dist/evm/calls/common.js +51 -0
- package/dist/evm/calls/index.d.ts +11 -0
- package/dist/evm/calls/index.js +4 -0
- package/dist/evm/calls/policies.d.ts +25 -0
- package/dist/evm/calls/policies.js +173 -0
- package/dist/evm/calls/users.d.ts +61 -0
- package/dist/evm/calls/users.js +353 -0
- package/dist/evm/calls/vault.d.ts +25 -0
- package/dist/evm/calls/vault.js +184 -0
- package/dist/evm/chain.d.ts +74 -0
- package/dist/evm/chain.js +48 -0
- package/dist/evm/company-name.d.ts +46 -0
- package/dist/evm/company-name.js +64 -0
- package/dist/evm/deployment.d.ts +47 -0
- package/dist/evm/deployment.js +110 -0
- package/dist/evm/eip712.d.ts +271 -0
- package/dist/evm/eip712.js +224 -0
- package/dist/evm/index.d.ts +20 -0
- package/dist/evm/index.js +21 -0
- package/dist/evm/reads.d.ts +64 -0
- package/dist/evm/reads.js +131 -0
- package/dist/evm/signatures.d.ts +57 -0
- package/dist/evm/signatures.js +105 -0
- package/dist/evm/simulate/balances.d.ts +76 -0
- package/dist/evm/simulate/balances.js +201 -0
- package/dist/evm/simulate/describe.d.ts +79 -0
- package/dist/evm/simulate/describe.js +188 -0
- package/dist/evm/simulate/index.d.ts +12 -0
- package/dist/evm/simulate/index.js +13 -0
- package/dist/evm/simulate/own-node.d.ts +62 -0
- package/dist/evm/simulate/own-node.js +122 -0
- package/dist/evm/simulate/request.d.ts +86 -0
- package/dist/evm/simulate/request.js +235 -0
- package/dist/evm/simulate/revert.d.ts +29 -0
- package/dist/evm/simulate/revert.js +192 -0
- package/dist/evm/simulate/run.d.ts +51 -0
- package/dist/evm/simulate/run.js +214 -0
- package/dist/evm/stocks/decode-stock-swap.d.ts +32 -0
- package/dist/evm/stocks/decode-stock-swap.js +79 -0
- package/dist/evm/stocks/encode-swap.d.ts +45 -0
- package/dist/evm/stocks/encode-swap.js +45 -0
- package/dist/evm/stocks/index.d.ts +9 -0
- package/dist/evm/stocks/index.js +10 -0
- package/dist/evm/stocks/quote.d.ts +19 -0
- package/dist/evm/stocks/quote.js +46 -0
- package/dist/evm/stocks/stock-pools.d.ts +61 -0
- package/dist/evm/stocks/stock-pools.js +179 -0
- package/dist/evm/submitters.d.ts +32 -0
- package/dist/evm/submitters.js +105 -0
- package/dist/evm/types.d.ts +146 -0
- package/dist/evm/types.js +128 -0
- package/dist/keys/index.d.ts +6 -0
- package/dist/keys/index.js +7 -0
- package/dist/keys/keys.d.ts +63 -0
- package/dist/keys/keys.js +80 -0
- package/dist/keys/xpub-ownership.d.ts +10 -0
- package/dist/keys/xpub-ownership.js +13 -0
- package/package.json +61 -3
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
import { type Address, type Hex } from "viem";
|
|
2
|
+
/**
|
|
3
|
+
* Every EIP-712 payload the v1 contract stack verifies, in one file, shared by
|
|
4
|
+
* the webapp (`apps/cofund/src/lib/evm/eip712.ts` re-exports it) and the SDK.
|
|
5
|
+
* A client that signs locally rebuilds the digest from these definitions and
|
|
6
|
+
* never from types or a domain a server handed it.
|
|
7
|
+
*
|
|
8
|
+
* `docs/v1-relaunch-design.md` §4.2 freezes nine struct types across three
|
|
9
|
+
* domains, and the field ORDER is part of the contract twice over: it is what
|
|
10
|
+
* `keccak256(typeString)` hashes, and it is the order a Ledger renders on
|
|
11
|
+
* screen (§8.1 — `company` first so an admin at two orgs can tell which one
|
|
12
|
+
* they are approving for; `authId` last so a descriptor can hide it).
|
|
13
|
+
*
|
|
14
|
+
* These objects are the single source of truth. They used to be scattered over
|
|
15
|
+
* `gen-eth-transfer-sig.ts`, `api/documents/util/eth-helpers.ts`,
|
|
16
|
+
* `gen-eth-policy-creation-sig.ts`, `invite-authorization-signature.ts` and
|
|
17
|
+
* three Ledger builders, each with its own copy — and the copies drifted (a
|
|
18
|
+
* device signed a `ForwardRequest` while the server recovered an `InviteUser`,
|
|
19
|
+
* rejecting every Ledger admin's EVM invite). One definition, imported.
|
|
20
|
+
*
|
|
21
|
+
* NEVER hand one of these objects straight to the Ledger Ethereum SDK: it adds
|
|
22
|
+
* an `EIP712Domain` key to the `types` object it is given, IN PLACE. Poisoning
|
|
23
|
+
* a module-level constant breaks every later signer or hasher that uses it
|
|
24
|
+
* (an ethers or viem call: "ambiguous primary types or unused types") for the
|
|
25
|
+
* lifetime of the tab.
|
|
26
|
+
* `Object.freeze` makes that a loud failure instead of a silent one; call sites
|
|
27
|
+
* that must hand over a mutable object use the `…Types()` copy functions.
|
|
28
|
+
*/
|
|
29
|
+
type Field = {
|
|
30
|
+
name: string;
|
|
31
|
+
type: string;
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* Mutable in the TYPE so a signer whose field lists are mutable (ethers'
|
|
35
|
+
* `TypedDataField[]`) accepts these directly, frozen at RUNTIME so a mutating
|
|
36
|
+
* consumer fails loudly rather than poisoning the module for the tab.
|
|
37
|
+
* Structural typing cannot express both, and the runtime guarantee is the one
|
|
38
|
+
* that has actually bitten.
|
|
39
|
+
*/
|
|
40
|
+
type TypeSet = Record<string, Field[]>;
|
|
41
|
+
/**
|
|
42
|
+
* ONE domain for every payload in the stack:
|
|
43
|
+
* `{ name: "Cofund", version: "1", chainId, verifyingContract: State }`.
|
|
44
|
+
*
|
|
45
|
+
* v0 had three names and three verifying contracts — `cofund-signer/1.0.0` on
|
|
46
|
+
* the vault clone, `CFHelpersPolicies/v0` on the policies helper, and
|
|
47
|
+
* `Cofund Forwarder/1` on the forwarder. The forwarder is gone (§1) and the
|
|
48
|
+
* other two collapsed, because neither of the things a per-contract domain was
|
|
49
|
+
* doing survives scrutiny:
|
|
50
|
+
*
|
|
51
|
+
* - **It did not scope a signature to an org.** Every v1 struct carries
|
|
52
|
+
* `string company`, and every entrypoint checks
|
|
53
|
+
* `sha256(bytes(company)) == clientId` before it looks at a signature.
|
|
54
|
+
* That is what stops one client's approval moving another's money; the
|
|
55
|
+
* vault clone's address never was.
|
|
56
|
+
* - **It did scope a signature to a HELPER, which was a liability.** An
|
|
57
|
+
* admin's frozen invite authorization was bound to the users helper's
|
|
58
|
+
* address, so `gov.setHelperContract("users", …)` silently invalidated
|
|
59
|
+
* every unspent invite — a state one production migration had to repair by
|
|
60
|
+
* hand. Binding to State instead makes a helper rotation invisible to
|
|
61
|
+
* anything already signed.
|
|
62
|
+
*
|
|
63
|
+
* What is left is a deployment identifier, which is exactly what a domain is
|
|
64
|
+
* for: a digest made for this chain's State cannot be replayed against another
|
|
65
|
+
* chain's, and nothing has to resolve a contract address before it can build
|
|
66
|
+
* one. The browser and the server therefore cannot disagree about a domain.
|
|
67
|
+
*/
|
|
68
|
+
export type Eip712Domain = {
|
|
69
|
+
name: string;
|
|
70
|
+
version: string;
|
|
71
|
+
chainId: number;
|
|
72
|
+
verifyingContract: string;
|
|
73
|
+
};
|
|
74
|
+
/** The domain for one deployment: the chain id and that chain's State. */
|
|
75
|
+
export declare function buildCofundDomain({ chainId, state, }: {
|
|
76
|
+
chainId: number;
|
|
77
|
+
/** `CFHelpersStatev1` on that chain. */
|
|
78
|
+
state: string;
|
|
79
|
+
}): Eip712Domain & {
|
|
80
|
+
name: "Cofund";
|
|
81
|
+
version: "1";
|
|
82
|
+
};
|
|
83
|
+
/**
|
|
84
|
+
* The whole of an invite (§4.4). No `inviteHash` and no invite code: v1 has no
|
|
85
|
+
* pending invite on chain, so what the admin signs IS the seat — identity,
|
|
86
|
+
* role, position — bounded by `expiresAt` and replay-protected by `authId`.
|
|
87
|
+
* The member later signs this SAME digest with their own key, and the platform
|
|
88
|
+
* relays both in one `users.addUser`.
|
|
89
|
+
*/
|
|
90
|
+
export declare const INVITE_USER_TYPES: TypeSet;
|
|
91
|
+
/** `CFHelpersUsersv1.MAX_INVITE_LIFETIME`: the furthest out an `InviteUser`'s `expiresAt` may be. */
|
|
92
|
+
export declare const MAX_INVITE_LIFETIME_SECONDS: number;
|
|
93
|
+
/**
|
|
94
|
+
* Headroom under the contract's ceiling. `_requireWindow` rejects
|
|
95
|
+
* `expiresAt > block.timestamp + MAX_INVITE_LIFETIME`, and `block.timestamp`
|
|
96
|
+
* at inclusion is the sequencer's clock, not the signer's: signing exactly
|
|
97
|
+
* `now + 30 days` reverts with `InvalidParams` whenever the chain is even one
|
|
98
|
+
* second behind (clock skew, a lagging L2 sequencer). Anvil stamps blocks from
|
|
99
|
+
* the same wall clock, so no local stack ever shows it. A full day of headroom
|
|
100
|
+
* (so 29 days signed) is far beyond any skew.
|
|
101
|
+
*/
|
|
102
|
+
export declare const CHAIN_CLOCK_SKEW_MARGIN_SECONDS: number;
|
|
103
|
+
/** What an inviting admin signs: the ceiling minus the skew margin. */
|
|
104
|
+
export declare const INVITE_LIFETIME_SECONDS: number;
|
|
105
|
+
/** An invite's `expiresAt`: unix SECONDS, as `uint256 expiresAt` wants them. */
|
|
106
|
+
export declare function inviteExpiresAt(now?: Date, lifetimeSeconds?: number): string;
|
|
107
|
+
export declare const REMOVE_USER_TYPES: TypeSet;
|
|
108
|
+
/**
|
|
109
|
+
* Closing a client's onboarding phase (§4.3, round 4).
|
|
110
|
+
*
|
|
111
|
+
* While `state.onboardingComplete(clientId)` is false the founder is allowed
|
|
112
|
+
* to act alone: one admin signature seats a co-admin, and one activates a
|
|
113
|
+
* policy. This is what ends that — signed by ANY active admin of the client,
|
|
114
|
+
* relayed like everything else, and irreversible (`OnboardingAlreadyComplete`
|
|
115
|
+
* on a second attempt).
|
|
116
|
+
*/
|
|
117
|
+
export declare const COMPLETE_ONBOARDING_TYPES: TypeSet;
|
|
118
|
+
export declare const ROTATE_USER_TYPES: TypeSet;
|
|
119
|
+
export type InviteUserMessage = {
|
|
120
|
+
company: string;
|
|
121
|
+
callerId: string;
|
|
122
|
+
userId: string;
|
|
123
|
+
userIsAdmin: boolean;
|
|
124
|
+
userPosition: string;
|
|
125
|
+
/** Unix SECONDS. `users.addUser` caps it at `now + 30 days`. */
|
|
126
|
+
expiresAt: string;
|
|
127
|
+
authId: string;
|
|
128
|
+
};
|
|
129
|
+
export type CompleteOnboardingMessage = {
|
|
130
|
+
company: string;
|
|
131
|
+
authId: string;
|
|
132
|
+
};
|
|
133
|
+
export type RemoveUserMessage = {
|
|
134
|
+
company: string;
|
|
135
|
+
removedUserId: string;
|
|
136
|
+
authId: string;
|
|
137
|
+
};
|
|
138
|
+
export type RotateUserMessage = {
|
|
139
|
+
company: string;
|
|
140
|
+
userId: string;
|
|
141
|
+
newAddress: string;
|
|
142
|
+
authId: string;
|
|
143
|
+
};
|
|
144
|
+
/**
|
|
145
|
+
* v0's single `PolicyActivation` carried both option groups with the unused
|
|
146
|
+
* half zeroed, plus `signersHash` and `trRecipientsHash`. v1 splits it in two
|
|
147
|
+
* so the zeroed half never reaches a device, and unhashes both arrays (§8.1
|
|
148
|
+
* tier A): the signer set is `string[] signerIds` — the members' on-chain user
|
|
149
|
+
* ids, NOT their addresses — and the allowlist is `address[] recipients`.
|
|
150
|
+
*/
|
|
151
|
+
export declare const TRANSFER_POLICY_ACTIVATION_TYPES: TypeSet;
|
|
152
|
+
export declare const TRANSACTION_POLICY_ACTIVATION_TYPES: TypeSet;
|
|
153
|
+
export declare const POLICY_DEACTIVATION_TYPES: TypeSet;
|
|
154
|
+
export type TransferPolicyActivationMessage = {
|
|
155
|
+
company: string;
|
|
156
|
+
policyId: string;
|
|
157
|
+
title: string;
|
|
158
|
+
policyType: string;
|
|
159
|
+
signerIds: string[];
|
|
160
|
+
threshold: string;
|
|
161
|
+
token: string;
|
|
162
|
+
maxAmount: string;
|
|
163
|
+
recipients: string[];
|
|
164
|
+
authId: string;
|
|
165
|
+
};
|
|
166
|
+
export type TransactionPolicyActivationMessage = {
|
|
167
|
+
company: string;
|
|
168
|
+
policyId: string;
|
|
169
|
+
title: string;
|
|
170
|
+
policyType: string;
|
|
171
|
+
signerIds: string[];
|
|
172
|
+
threshold: string;
|
|
173
|
+
wrapper: string;
|
|
174
|
+
func: string;
|
|
175
|
+
authId: string;
|
|
176
|
+
};
|
|
177
|
+
export type PolicyDeactivationMessage = {
|
|
178
|
+
company: string;
|
|
179
|
+
policyId: string;
|
|
180
|
+
authId: string;
|
|
181
|
+
};
|
|
182
|
+
export declare const TRANSFER_TYPES: TypeSet;
|
|
183
|
+
/**
|
|
184
|
+
* `allowancesHash` is gone: the allowances a signer grants the wrapper are the
|
|
185
|
+
* risk of the action, so they travel as a nested struct array and render on
|
|
186
|
+
* device. Note the rename AND the field swap — v0 hashed
|
|
187
|
+
* `ERC20Allowance(uint256 amount,address asset)`, v1 signs
|
|
188
|
+
* `Allowance(address asset,uint256 amount)`.
|
|
189
|
+
*
|
|
190
|
+
* `instructionsHash` stays a hash: the wrapper's calldata is arbitrary bytes
|
|
191
|
+
* and per-wrapper decoding is its own project (§4.2).
|
|
192
|
+
*/
|
|
193
|
+
export declare const TRANSACTION_TYPES: TypeSet;
|
|
194
|
+
export type AllowanceMessage = {
|
|
195
|
+
asset: string;
|
|
196
|
+
amount: string;
|
|
197
|
+
};
|
|
198
|
+
/**
|
|
199
|
+
* The one function name a Cofund wrapper transaction ever carries.
|
|
200
|
+
*
|
|
201
|
+
* Every wrapper exposes a single entrypoint, `routerWrapper(func, instructions)`,
|
|
202
|
+
* and routes on `instructions`; `func` is a label the policy commits to at
|
|
203
|
+
* activation and the signers approve at execution. It is a constant, never a
|
|
204
|
+
* value read off the protocol record (`supportedActions` is display copy), so
|
|
205
|
+
* activation, request creation, co-signing and the relay all name the same
|
|
206
|
+
* thing — the app enforces the equality the live v1 vault does not.
|
|
207
|
+
*/
|
|
208
|
+
export declare const WRAPPER_ENTRYPOINT = "entry";
|
|
209
|
+
export type TransferMessage = {
|
|
210
|
+
company: string;
|
|
211
|
+
policyId: string;
|
|
212
|
+
messageType: string;
|
|
213
|
+
amount: string;
|
|
214
|
+
token: string;
|
|
215
|
+
recipient: string;
|
|
216
|
+
authId: string;
|
|
217
|
+
};
|
|
218
|
+
export type TransactionMessage = {
|
|
219
|
+
company: string;
|
|
220
|
+
policyId: string;
|
|
221
|
+
messageType: string;
|
|
222
|
+
wrapper: string;
|
|
223
|
+
func: string;
|
|
224
|
+
allowances: AllowanceMessage[];
|
|
225
|
+
instructionsHash: string;
|
|
226
|
+
authId: string;
|
|
227
|
+
};
|
|
228
|
+
export declare const inviteUserTypes: () => Record<string, Field[]>;
|
|
229
|
+
export declare const removeUserTypes: () => Record<string, Field[]>;
|
|
230
|
+
export declare const completeOnboardingTypes: () => Record<string, Field[]>;
|
|
231
|
+
export declare const rotateUserTypes: () => Record<string, Field[]>;
|
|
232
|
+
export declare const transferPolicyActivationTypes: () => Record<string, Field[]>;
|
|
233
|
+
export declare const transactionPolicyActivationTypes: () => Record<string, Field[]>;
|
|
234
|
+
export declare const policyDeactivationTypes: () => Record<string, Field[]>;
|
|
235
|
+
export declare const transferTypes: () => Record<string, Field[]>;
|
|
236
|
+
export declare const transactionTypes: () => Record<string, Field[]>;
|
|
237
|
+
/**
|
|
238
|
+
* `sha256(bytes(company))`, 0x-prefixed: the id State keys a cofund by, which
|
|
239
|
+
* every entrypoint checks against the signed `company`.
|
|
240
|
+
*/
|
|
241
|
+
export declare function cofundClientId(company: string): Hex;
|
|
242
|
+
/**
|
|
243
|
+
* One message as EIP-712 typed data: what a viem account's or wallet
|
|
244
|
+
* client's `signTypedData` takes (and `eth_signTypedData_v4`), and what
|
|
245
|
+
* `typedDataDigest` hashes. uint256 fields may be decimal strings, as the
|
|
246
|
+
* message types above carry them.
|
|
247
|
+
*/
|
|
248
|
+
export type TypedData = {
|
|
249
|
+
domain: Omit<Eip712Domain, "verifyingContract"> & {
|
|
250
|
+
verifyingContract: Address;
|
|
251
|
+
};
|
|
252
|
+
types: Record<string, readonly {
|
|
253
|
+
name: string;
|
|
254
|
+
type: string;
|
|
255
|
+
}[]>;
|
|
256
|
+
primaryType: string;
|
|
257
|
+
message: Record<string, unknown>;
|
|
258
|
+
};
|
|
259
|
+
/** The one type in `types` no other type references: EIP-712's primary type. */
|
|
260
|
+
export declare function primaryTypeOf(types: Record<string, readonly {
|
|
261
|
+
name: string;
|
|
262
|
+
type: string;
|
|
263
|
+
}[]>): string;
|
|
264
|
+
/** `message` under `domain` as typed data, its primary type read from `types`. */
|
|
265
|
+
export declare function typedData(domain: Eip712Domain, types: Record<string, readonly {
|
|
266
|
+
name: string;
|
|
267
|
+
type: string;
|
|
268
|
+
}[]>, message: object): TypedData;
|
|
269
|
+
/** The `\x19\x01`-prefixed digest a key signs and the contracts recover from. */
|
|
270
|
+
export declare function typedDataDigest(data: TypedData): Hex;
|
|
271
|
+
export {};
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import { hashTypedData, sha256, stringToBytes, } from "viem";
|
|
2
|
+
/** The domain for one deployment: the chain id and that chain's State. */
|
|
3
|
+
export function buildCofundDomain({ chainId, state, }) {
|
|
4
|
+
return { name: "Cofund", version: "1", chainId, verifyingContract: state };
|
|
5
|
+
}
|
|
6
|
+
/* ══════════════════════════════════════════════════════════════════════════
|
|
7
|
+
* Users types — signed under the one `("Cofund", "1")` domain
|
|
8
|
+
* ═════════════════════════════════════════════════════════════════════════ */
|
|
9
|
+
/**
|
|
10
|
+
* The whole of an invite (§4.4). No `inviteHash` and no invite code: v1 has no
|
|
11
|
+
* pending invite on chain, so what the admin signs IS the seat — identity,
|
|
12
|
+
* role, position — bounded by `expiresAt` and replay-protected by `authId`.
|
|
13
|
+
* The member later signs this SAME digest with their own key, and the platform
|
|
14
|
+
* relays both in one `users.addUser`.
|
|
15
|
+
*/
|
|
16
|
+
export const INVITE_USER_TYPES = Object.freeze({
|
|
17
|
+
InviteUser: [
|
|
18
|
+
{ name: "company", type: "string" },
|
|
19
|
+
{ name: "callerId", type: "string" },
|
|
20
|
+
{ name: "userId", type: "string" },
|
|
21
|
+
{ name: "userIsAdmin", type: "bool" },
|
|
22
|
+
{ name: "userPosition", type: "string" },
|
|
23
|
+
{ name: "expiresAt", type: "uint256" },
|
|
24
|
+
{ name: "authId", type: "string" },
|
|
25
|
+
],
|
|
26
|
+
});
|
|
27
|
+
/** `CFHelpersUsersv1.MAX_INVITE_LIFETIME`: the furthest out an `InviteUser`'s `expiresAt` may be. */
|
|
28
|
+
export const MAX_INVITE_LIFETIME_SECONDS = 30 * 24 * 60 * 60;
|
|
29
|
+
/**
|
|
30
|
+
* Headroom under the contract's ceiling. `_requireWindow` rejects
|
|
31
|
+
* `expiresAt > block.timestamp + MAX_INVITE_LIFETIME`, and `block.timestamp`
|
|
32
|
+
* at inclusion is the sequencer's clock, not the signer's: signing exactly
|
|
33
|
+
* `now + 30 days` reverts with `InvalidParams` whenever the chain is even one
|
|
34
|
+
* second behind (clock skew, a lagging L2 sequencer). Anvil stamps blocks from
|
|
35
|
+
* the same wall clock, so no local stack ever shows it. A full day of headroom
|
|
36
|
+
* (so 29 days signed) is far beyond any skew.
|
|
37
|
+
*/
|
|
38
|
+
export const CHAIN_CLOCK_SKEW_MARGIN_SECONDS = 24 * 60 * 60;
|
|
39
|
+
/** What an inviting admin signs: the ceiling minus the skew margin. */
|
|
40
|
+
export const INVITE_LIFETIME_SECONDS = MAX_INVITE_LIFETIME_SECONDS - CHAIN_CLOCK_SKEW_MARGIN_SECONDS;
|
|
41
|
+
/** An invite's `expiresAt`: unix SECONDS, as `uint256 expiresAt` wants them. */
|
|
42
|
+
export function inviteExpiresAt(now = new Date(), lifetimeSeconds = INVITE_LIFETIME_SECONDS) {
|
|
43
|
+
return String(Math.floor(now.getTime() / 1000) + lifetimeSeconds);
|
|
44
|
+
}
|
|
45
|
+
export const REMOVE_USER_TYPES = Object.freeze({
|
|
46
|
+
RemoveUser: [
|
|
47
|
+
{ name: "company", type: "string" },
|
|
48
|
+
{ name: "removedUserId", type: "string" },
|
|
49
|
+
{ name: "authId", type: "string" },
|
|
50
|
+
],
|
|
51
|
+
});
|
|
52
|
+
/**
|
|
53
|
+
* Closing a client's onboarding phase (§4.3, round 4).
|
|
54
|
+
*
|
|
55
|
+
* While `state.onboardingComplete(clientId)` is false the founder is allowed
|
|
56
|
+
* to act alone: one admin signature seats a co-admin, and one activates a
|
|
57
|
+
* policy. This is what ends that — signed by ANY active admin of the client,
|
|
58
|
+
* relayed like everything else, and irreversible (`OnboardingAlreadyComplete`
|
|
59
|
+
* on a second attempt).
|
|
60
|
+
*/
|
|
61
|
+
export const COMPLETE_ONBOARDING_TYPES = Object.freeze({
|
|
62
|
+
CompleteOnboarding: [
|
|
63
|
+
{ name: "company", type: "string" },
|
|
64
|
+
{ name: "authId", type: "string" },
|
|
65
|
+
],
|
|
66
|
+
});
|
|
67
|
+
export const ROTATE_USER_TYPES = Object.freeze({
|
|
68
|
+
RotateUser: [
|
|
69
|
+
{ name: "company", type: "string" },
|
|
70
|
+
{ name: "userId", type: "string" },
|
|
71
|
+
{ name: "newAddress", type: "address" },
|
|
72
|
+
{ name: "authId", type: "string" },
|
|
73
|
+
],
|
|
74
|
+
});
|
|
75
|
+
/* ══════════════════════════════════════════════════════════════════════════
|
|
76
|
+
* Policies types — signed under the one `("Cofund", "1")` domain
|
|
77
|
+
* ═════════════════════════════════════════════════════════════════════════ */
|
|
78
|
+
/**
|
|
79
|
+
* v0's single `PolicyActivation` carried both option groups with the unused
|
|
80
|
+
* half zeroed, plus `signersHash` and `trRecipientsHash`. v1 splits it in two
|
|
81
|
+
* so the zeroed half never reaches a device, and unhashes both arrays (§8.1
|
|
82
|
+
* tier A): the signer set is `string[] signerIds` — the members' on-chain user
|
|
83
|
+
* ids, NOT their addresses — and the allowlist is `address[] recipients`.
|
|
84
|
+
*/
|
|
85
|
+
export const TRANSFER_POLICY_ACTIVATION_TYPES = Object.freeze({
|
|
86
|
+
TransferPolicyActivation: [
|
|
87
|
+
{ name: "company", type: "string" },
|
|
88
|
+
{ name: "policyId", type: "string" },
|
|
89
|
+
{ name: "title", type: "string" },
|
|
90
|
+
{ name: "policyType", type: "string" },
|
|
91
|
+
{ name: "signerIds", type: "string[]" },
|
|
92
|
+
{ name: "threshold", type: "uint256" },
|
|
93
|
+
{ name: "token", type: "address" },
|
|
94
|
+
{ name: "maxAmount", type: "uint256" },
|
|
95
|
+
{ name: "recipients", type: "address[]" },
|
|
96
|
+
{ name: "authId", type: "string" },
|
|
97
|
+
],
|
|
98
|
+
});
|
|
99
|
+
export const TRANSACTION_POLICY_ACTIVATION_TYPES = Object.freeze({
|
|
100
|
+
TransactionPolicyActivation: [
|
|
101
|
+
{ name: "company", type: "string" },
|
|
102
|
+
{ name: "policyId", type: "string" },
|
|
103
|
+
{ name: "title", type: "string" },
|
|
104
|
+
{ name: "policyType", type: "string" },
|
|
105
|
+
{ name: "signerIds", type: "string[]" },
|
|
106
|
+
{ name: "threshold", type: "uint256" },
|
|
107
|
+
{ name: "wrapper", type: "address" },
|
|
108
|
+
{ name: "func", type: "string" },
|
|
109
|
+
{ name: "authId", type: "string" },
|
|
110
|
+
],
|
|
111
|
+
});
|
|
112
|
+
export const POLICY_DEACTIVATION_TYPES = Object.freeze({
|
|
113
|
+
PolicyDeactivation: [
|
|
114
|
+
{ name: "company", type: "string" },
|
|
115
|
+
{ name: "policyId", type: "string" },
|
|
116
|
+
{ name: "authId", type: "string" },
|
|
117
|
+
],
|
|
118
|
+
});
|
|
119
|
+
/* ══════════════════════════════════════════════════════════════════════════
|
|
120
|
+
* Vault types — signed under the one `("Cofund", "1")` domain
|
|
121
|
+
* ═════════════════════════════════════════════════════════════════════════ */
|
|
122
|
+
export const TRANSFER_TYPES = Object.freeze({
|
|
123
|
+
Transfer: [
|
|
124
|
+
{ name: "company", type: "string" },
|
|
125
|
+
{ name: "policyId", type: "string" },
|
|
126
|
+
{ name: "messageType", type: "string" },
|
|
127
|
+
{ name: "amount", type: "uint256" },
|
|
128
|
+
{ name: "token", type: "address" },
|
|
129
|
+
{ name: "recipient", type: "address" },
|
|
130
|
+
{ name: "authId", type: "string" },
|
|
131
|
+
],
|
|
132
|
+
});
|
|
133
|
+
/**
|
|
134
|
+
* `allowancesHash` is gone: the allowances a signer grants the wrapper are the
|
|
135
|
+
* risk of the action, so they travel as a nested struct array and render on
|
|
136
|
+
* device. Note the rename AND the field swap — v0 hashed
|
|
137
|
+
* `ERC20Allowance(uint256 amount,address asset)`, v1 signs
|
|
138
|
+
* `Allowance(address asset,uint256 amount)`.
|
|
139
|
+
*
|
|
140
|
+
* `instructionsHash` stays a hash: the wrapper's calldata is arbitrary bytes
|
|
141
|
+
* and per-wrapper decoding is its own project (§4.2).
|
|
142
|
+
*/
|
|
143
|
+
export const TRANSACTION_TYPES = Object.freeze({
|
|
144
|
+
Transaction: [
|
|
145
|
+
{ name: "company", type: "string" },
|
|
146
|
+
{ name: "policyId", type: "string" },
|
|
147
|
+
{ name: "messageType", type: "string" },
|
|
148
|
+
{ name: "wrapper", type: "address" },
|
|
149
|
+
{ name: "func", type: "string" },
|
|
150
|
+
{ name: "allowances", type: "Allowance[]" },
|
|
151
|
+
{ name: "instructionsHash", type: "bytes32" },
|
|
152
|
+
{ name: "authId", type: "string" },
|
|
153
|
+
],
|
|
154
|
+
Allowance: [
|
|
155
|
+
{ name: "asset", type: "address" },
|
|
156
|
+
{ name: "amount", type: "uint256" },
|
|
157
|
+
],
|
|
158
|
+
});
|
|
159
|
+
/**
|
|
160
|
+
* The one function name a Cofund wrapper transaction ever carries.
|
|
161
|
+
*
|
|
162
|
+
* Every wrapper exposes a single entrypoint, `routerWrapper(func, instructions)`,
|
|
163
|
+
* and routes on `instructions`; `func` is a label the policy commits to at
|
|
164
|
+
* activation and the signers approve at execution. It is a constant, never a
|
|
165
|
+
* value read off the protocol record (`supportedActions` is display copy), so
|
|
166
|
+
* activation, request creation, co-signing and the relay all name the same
|
|
167
|
+
* thing — the app enforces the equality the live v1 vault does not.
|
|
168
|
+
*/
|
|
169
|
+
export const WRAPPER_ENTRYPOINT = "entry";
|
|
170
|
+
/* ══════════════════════════════════════════════════════════════════════════
|
|
171
|
+
* Mutable copies for signers that write into `types`
|
|
172
|
+
* ═════════════════════════════════════════════════════════════════════════ */
|
|
173
|
+
/**
|
|
174
|
+
* A disposable copy for the Ledger Ethereum SDK, which inserts `EIP712Domain`
|
|
175
|
+
* into the object it is handed. The field arrays are shared but never written
|
|
176
|
+
* to — only the top-level object gains keys, and that is what has to stay
|
|
177
|
+
* private to the call.
|
|
178
|
+
*/
|
|
179
|
+
function copy(types) {
|
|
180
|
+
return Object.fromEntries(Object.entries(types).map(([name, fields]) => [name, [...fields]]));
|
|
181
|
+
}
|
|
182
|
+
export const inviteUserTypes = () => copy(INVITE_USER_TYPES);
|
|
183
|
+
export const removeUserTypes = () => copy(REMOVE_USER_TYPES);
|
|
184
|
+
export const completeOnboardingTypes = () => copy(COMPLETE_ONBOARDING_TYPES);
|
|
185
|
+
export const rotateUserTypes = () => copy(ROTATE_USER_TYPES);
|
|
186
|
+
export const transferPolicyActivationTypes = () => copy(TRANSFER_POLICY_ACTIVATION_TYPES);
|
|
187
|
+
export const transactionPolicyActivationTypes = () => copy(TRANSACTION_POLICY_ACTIVATION_TYPES);
|
|
188
|
+
export const policyDeactivationTypes = () => copy(POLICY_DEACTIVATION_TYPES);
|
|
189
|
+
export const transferTypes = () => copy(TRANSFER_TYPES);
|
|
190
|
+
export const transactionTypes = () => copy(TRANSACTION_TYPES);
|
|
191
|
+
/**
|
|
192
|
+
* `sha256(bytes(company))`, 0x-prefixed: the id State keys a cofund by, which
|
|
193
|
+
* every entrypoint checks against the signed `company`.
|
|
194
|
+
*/
|
|
195
|
+
export function cofundClientId(company) {
|
|
196
|
+
return sha256(stringToBytes(company));
|
|
197
|
+
}
|
|
198
|
+
/** `[]` or `[n]`, repeated: what makes a type an array of its base type. */
|
|
199
|
+
const ARRAY_SUFFIX = /(\[\d*\])+$/;
|
|
200
|
+
/** The one type in `types` no other type references: EIP-712's primary type. */
|
|
201
|
+
export function primaryTypeOf(types) {
|
|
202
|
+
const referenced = new Set(Object.values(types).flatMap((fields) => fields.map(({ type }) => type.replace(ARRAY_SUFFIX, ""))));
|
|
203
|
+
const primary = Object.keys(types).filter((name) => name !== "EIP712Domain" && !referenced.has(name));
|
|
204
|
+
if (primary.length !== 1) {
|
|
205
|
+
throw new Error(`Typed data needs exactly one primary type, not ${primary.length}`);
|
|
206
|
+
}
|
|
207
|
+
return primary[0];
|
|
208
|
+
}
|
|
209
|
+
/** `message` under `domain` as typed data, its primary type read from `types`. */
|
|
210
|
+
export function typedData(domain, types, message) {
|
|
211
|
+
return {
|
|
212
|
+
domain: {
|
|
213
|
+
...domain,
|
|
214
|
+
verifyingContract: domain.verifyingContract,
|
|
215
|
+
},
|
|
216
|
+
types,
|
|
217
|
+
primaryType: primaryTypeOf(types),
|
|
218
|
+
message: message,
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
/** The `\x19\x01`-prefixed digest a key signs and the contracts recover from. */
|
|
222
|
+
export function typedDataDigest(data) {
|
|
223
|
+
return hashTypedData(data);
|
|
224
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@getcofund/protocol/evm`: the deployment config, the EIP-712 messages and
|
|
3
|
+
* the company name they carry, a call builder per contract entry point with
|
|
4
|
+
* its checks, State's reads, the two submitters, a vault call's simulation
|
|
5
|
+
* and how it is described, the StockSwap verb, the contracts' ABIs, and the
|
|
6
|
+
* values they are made of.
|
|
7
|
+
*/
|
|
8
|
+
export * from "./abis/index.ts";
|
|
9
|
+
export * from "./address.ts";
|
|
10
|
+
export * from "./calls/index.ts";
|
|
11
|
+
export * from "./chain.ts";
|
|
12
|
+
export * from "./company-name.ts";
|
|
13
|
+
export * from "./deployment.ts";
|
|
14
|
+
export * from "./eip712.ts";
|
|
15
|
+
export * from "./reads.ts";
|
|
16
|
+
export { CallRefusedError, type RecoverFailure, tryRecover, } from "./signatures.ts";
|
|
17
|
+
export * from "./simulate/index.ts";
|
|
18
|
+
export * from "./stocks/index.ts";
|
|
19
|
+
export * from "./submitters.ts";
|
|
20
|
+
export * from "./types.ts";
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@getcofund/protocol/evm`: the deployment config, the EIP-712 messages and
|
|
3
|
+
* the company name they carry, a call builder per contract entry point with
|
|
4
|
+
* its checks, State's reads, the two submitters, a vault call's simulation
|
|
5
|
+
* and how it is described, the StockSwap verb, the contracts' ABIs, and the
|
|
6
|
+
* values they are made of.
|
|
7
|
+
*/
|
|
8
|
+
// biome-ignore lint/performance/noBarrelFile: the package's `./evm` entry
|
|
9
|
+
export * from "./abis/index.js";
|
|
10
|
+
export * from "./address.js";
|
|
11
|
+
export * from "./calls/index.js";
|
|
12
|
+
export * from "./chain.js";
|
|
13
|
+
export * from "./company-name.js";
|
|
14
|
+
export * from "./deployment.js";
|
|
15
|
+
export * from "./eip712.js";
|
|
16
|
+
export * from "./reads.js";
|
|
17
|
+
export { CallRefusedError, tryRecover, } from "./signatures.js";
|
|
18
|
+
export * from "./simulate/index.js";
|
|
19
|
+
export * from "./stocks/index.js";
|
|
20
|
+
export * from "./submitters.js";
|
|
21
|
+
export * from "./types.js";
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { type Address, type Hex } from "viem";
|
|
2
|
+
import { type EvmReader } from "./chain.ts";
|
|
3
|
+
import type { EvmDeployment } from "./deployment.ts";
|
|
4
|
+
/**
|
|
5
|
+
* State's getters, typed, through the consumer's reader: the registries and
|
|
6
|
+
* invariants every call builder checks against. There is no index: a
|
|
7
|
+
* consumer that needs history or speed brings its own.
|
|
8
|
+
*/
|
|
9
|
+
type Deployment = Pick<EvmDeployment, "state">;
|
|
10
|
+
/** A user of a cofund as State holds them. */
|
|
11
|
+
export type CofundUser = {
|
|
12
|
+
address: Address;
|
|
13
|
+
position: string;
|
|
14
|
+
isAdmin: boolean;
|
|
15
|
+
active: boolean;
|
|
16
|
+
};
|
|
17
|
+
/** `State.getUser`: null when `userId` was never seated in the cofund. */
|
|
18
|
+
export declare function readUser(reader: EvmReader, deployment: Deployment, clientId: Hex, userId: string): Promise<CofundUser | null>;
|
|
19
|
+
/** `State.getUserIdByAddress`: the user id `address` holds in the cofund, active or not; null when none. */
|
|
20
|
+
export declare function readUserIdByAddress(reader: EvmReader, deployment: Deployment, clientId: Hex, address: Address): Promise<string | null>;
|
|
21
|
+
/** A policy as State holds it. `signerIds` are the hashes State keeps: `keccak256(bytes(userId))`. */
|
|
22
|
+
export type CofundPolicy = {
|
|
23
|
+
active: boolean;
|
|
24
|
+
title: string;
|
|
25
|
+
policyType: string;
|
|
26
|
+
signerIds: Hex[];
|
|
27
|
+
threshold: number;
|
|
28
|
+
/** A transaction policy's wrapper and function; a transfer policy's are zero. */
|
|
29
|
+
transaction: {
|
|
30
|
+
wrapper: Address;
|
|
31
|
+
func: string;
|
|
32
|
+
};
|
|
33
|
+
/** A transfer policy's cap, token and allowlist (empty: any recipient). */
|
|
34
|
+
transfer: {
|
|
35
|
+
maxAmount: bigint;
|
|
36
|
+
token: Address;
|
|
37
|
+
recipients: Address[];
|
|
38
|
+
};
|
|
39
|
+
isTransaction: boolean;
|
|
40
|
+
};
|
|
41
|
+
/** `State.getPolicy`: null when no policy was ever activated under `policyId`. */
|
|
42
|
+
export declare function readPolicy(reader: EvmReader, deployment: Deployment, clientId: Hex, policyId: string): Promise<CofundPolicy | null>;
|
|
43
|
+
/** `State.clientVaults`: the cofund's vault and its subscription tier; null before it is created. */
|
|
44
|
+
export declare function readClientVault(reader: EvmReader, deployment: Deployment, clientId: Hex): Promise<{
|
|
45
|
+
vault: Address;
|
|
46
|
+
tier: string;
|
|
47
|
+
} | null>;
|
|
48
|
+
/** `State.onboardingComplete`: whether the cofund's onboarding phase is closed. */
|
|
49
|
+
export declare function readOnboardingComplete(reader: EvmReader, deployment: Deployment, clientId: Hex): Promise<boolean>;
|
|
50
|
+
/** `State.getActiveAdmins`: the cofund's active admins. */
|
|
51
|
+
export declare function readActiveAdmins(reader: EvmReader, deployment: Deployment, clientId: Hex): Promise<number>;
|
|
52
|
+
/** A helper State registers: the users and policies helpers, the vault factory, the guard, gov. */
|
|
53
|
+
export type HelperKind = "users" | "policies" | "vaults" | "sponsorGuard" | "gov";
|
|
54
|
+
/** `State.helperContracts(kind)`: zero when none is registered. */
|
|
55
|
+
export declare function readHelperContract(reader: EvmReader, deployment: Deployment, kind: HelperKind): Promise<Address>;
|
|
56
|
+
/** `State.getClientContractAuthID`: whether `authId` is spent for the cofund (one use, across every helper and the vault). */
|
|
57
|
+
export declare function readAuthIdSpent(reader: EvmReader, deployment: Deployment, clientId: Hex, authId: string): Promise<boolean>;
|
|
58
|
+
/** `Users.requiredAdminInviteSignatures`: admin signatures an admin seat needs, the inviter's included. */
|
|
59
|
+
export declare function readRequiredAdminInviteSignatures(reader: EvmReader, deployment: Pick<EvmDeployment, "users">, clientId: Hex): Promise<number>;
|
|
60
|
+
/** `Policies.requiredPolicyActivationSignatures`: admin signatures activating a policy of `threshold` needs. */
|
|
61
|
+
export declare function readRequiredPolicyActivationSignatures(reader: EvmReader, deployment: Pick<EvmDeployment, "policies">, clientId: Hex, threshold: number | bigint): Promise<number>;
|
|
62
|
+
/** `VaultFactory.predictVault`: the address a cofund's vault has, or will have once it is created. */
|
|
63
|
+
export declare function readPredictedVault(reader: EvmReader, deployment: Pick<EvmDeployment, "vaults">, clientId: Hex): Promise<Address>;
|
|
64
|
+
export {};
|