@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.
Files changed (129) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +413 -1
  3. package/dist/btc/index.d.ts +12 -0
  4. package/dist/btc/index.js +13 -0
  5. package/dist/btc/psbt.d.ts +121 -0
  6. package/dist/btc/psbt.js +377 -0
  7. package/dist/btc/reader.d.ts +42 -0
  8. package/dist/btc/reader.js +85 -0
  9. package/dist/btc/select.d.ts +48 -0
  10. package/dist/btc/select.js +59 -0
  11. package/dist/btc/spend-rules.d.ts +24 -0
  12. package/dist/btc/spend-rules.js +43 -0
  13. package/dist/btc/spend.d.ts +136 -0
  14. package/dist/btc/spend.js +180 -0
  15. package/dist/btc/types.d.ts +65 -0
  16. package/dist/btc/types.js +62 -0
  17. package/dist/btc/vault/bip341-nums.d.ts +35 -0
  18. package/dist/btc/vault/bip341-nums.js +121 -0
  19. package/dist/btc/vault/build.d.ts +5 -0
  20. package/dist/btc/vault/build.js +136 -0
  21. package/dist/btc/vault/canonical-order.d.ts +10 -0
  22. package/dist/btc/vault/canonical-order.js +18 -0
  23. package/dist/btc/vault/from-contract-id.d.ts +9 -0
  24. package/dist/btc/vault/from-contract-id.js +74 -0
  25. package/dist/btc/vault/helpers.d.ts +73 -0
  26. package/dist/btc/vault/helpers.js +129 -0
  27. package/dist/btc/vault/index.d.ts +21 -0
  28. package/dist/btc/vault/index.js +21 -0
  29. package/dist/btc/vault/limits.d.ts +9 -0
  30. package/dist/btc/vault/limits.js +9 -0
  31. package/dist/btc/vault/participant-order.d.ts +69 -0
  32. package/dist/btc/vault/participant-order.js +135 -0
  33. package/dist/btc/vault/script.d.ts +13 -0
  34. package/dist/btc/vault/script.js +39 -0
  35. package/dist/btc/vault/spend.d.ts +63 -0
  36. package/dist/btc/vault/spend.js +93 -0
  37. package/dist/btc/vault/tx-size.d.ts +66 -0
  38. package/dist/btc/vault/tx-size.js +71 -0
  39. package/dist/btc/vault/types.d.ts +49 -0
  40. package/dist/btc/vault/types.js +1 -0
  41. package/dist/btc/vault/vault-policy.d.ts +109 -0
  42. package/dist/btc/vault/vault-policy.js +213 -0
  43. package/dist/btc/vault/vaultContractUtil.d.ts +80 -0
  44. package/dist/btc/vault/vaultContractUtil.js +87 -0
  45. package/dist/btc/vault/xpub.d.ts +28 -0
  46. package/dist/btc/vault/xpub.js +41 -0
  47. package/dist/btc/witness.d.ts +101 -0
  48. package/dist/btc/witness.js +439 -0
  49. package/dist/btc-policies/activation.d.ts +76 -0
  50. package/dist/btc-policies/activation.js +153 -0
  51. package/dist/btc-policies/index.d.ts +7 -0
  52. package/dist/btc-policies/index.js +8 -0
  53. package/dist/btc-policies/policy.d.ts +55 -0
  54. package/dist/btc-policies/policy.js +210 -0
  55. package/dist/evm/abis/index.d.ts +10 -0
  56. package/dist/evm/abis/index.js +11 -0
  57. package/dist/evm/abis/policies.d.ts +279 -0
  58. package/dist/evm/abis/policies.js +363 -0
  59. package/dist/evm/abis/sponsor-guard.d.ts +201 -0
  60. package/dist/evm/abis/sponsor-guard.js +263 -0
  61. package/dist/evm/abis/state.d.ts +989 -0
  62. package/dist/evm/abis/state.js +1290 -0
  63. package/dist/evm/abis/users.d.ts +408 -0
  64. package/dist/evm/abis/users.js +532 -0
  65. package/dist/evm/abis/vault-factory.d.ts +92 -0
  66. package/dist/evm/abis/vault-factory.js +126 -0
  67. package/dist/evm/abis/vault.d.ts +343 -0
  68. package/dist/evm/abis/vault.js +444 -0
  69. package/dist/evm/address.d.ts +2 -0
  70. package/dist/evm/address.js +4 -0
  71. package/dist/evm/calls/common.d.ts +30 -0
  72. package/dist/evm/calls/common.js +51 -0
  73. package/dist/evm/calls/index.d.ts +11 -0
  74. package/dist/evm/calls/index.js +4 -0
  75. package/dist/evm/calls/policies.d.ts +25 -0
  76. package/dist/evm/calls/policies.js +173 -0
  77. package/dist/evm/calls/users.d.ts +61 -0
  78. package/dist/evm/calls/users.js +353 -0
  79. package/dist/evm/calls/vault.d.ts +25 -0
  80. package/dist/evm/calls/vault.js +184 -0
  81. package/dist/evm/chain.d.ts +74 -0
  82. package/dist/evm/chain.js +48 -0
  83. package/dist/evm/company-name.d.ts +46 -0
  84. package/dist/evm/company-name.js +64 -0
  85. package/dist/evm/deployment.d.ts +47 -0
  86. package/dist/evm/deployment.js +110 -0
  87. package/dist/evm/eip712.d.ts +271 -0
  88. package/dist/evm/eip712.js +224 -0
  89. package/dist/evm/index.d.ts +20 -0
  90. package/dist/evm/index.js +21 -0
  91. package/dist/evm/reads.d.ts +64 -0
  92. package/dist/evm/reads.js +131 -0
  93. package/dist/evm/signatures.d.ts +57 -0
  94. package/dist/evm/signatures.js +105 -0
  95. package/dist/evm/simulate/balances.d.ts +76 -0
  96. package/dist/evm/simulate/balances.js +201 -0
  97. package/dist/evm/simulate/describe.d.ts +79 -0
  98. package/dist/evm/simulate/describe.js +188 -0
  99. package/dist/evm/simulate/index.d.ts +12 -0
  100. package/dist/evm/simulate/index.js +13 -0
  101. package/dist/evm/simulate/own-node.d.ts +62 -0
  102. package/dist/evm/simulate/own-node.js +122 -0
  103. package/dist/evm/simulate/request.d.ts +86 -0
  104. package/dist/evm/simulate/request.js +235 -0
  105. package/dist/evm/simulate/revert.d.ts +29 -0
  106. package/dist/evm/simulate/revert.js +192 -0
  107. package/dist/evm/simulate/run.d.ts +51 -0
  108. package/dist/evm/simulate/run.js +214 -0
  109. package/dist/evm/stocks/decode-stock-swap.d.ts +32 -0
  110. package/dist/evm/stocks/decode-stock-swap.js +79 -0
  111. package/dist/evm/stocks/encode-swap.d.ts +45 -0
  112. package/dist/evm/stocks/encode-swap.js +45 -0
  113. package/dist/evm/stocks/index.d.ts +9 -0
  114. package/dist/evm/stocks/index.js +10 -0
  115. package/dist/evm/stocks/quote.d.ts +19 -0
  116. package/dist/evm/stocks/quote.js +46 -0
  117. package/dist/evm/stocks/stock-pools.d.ts +61 -0
  118. package/dist/evm/stocks/stock-pools.js +179 -0
  119. package/dist/evm/submitters.d.ts +32 -0
  120. package/dist/evm/submitters.js +105 -0
  121. package/dist/evm/types.d.ts +146 -0
  122. package/dist/evm/types.js +128 -0
  123. package/dist/keys/index.d.ts +6 -0
  124. package/dist/keys/index.js +7 -0
  125. package/dist/keys/keys.d.ts +63 -0
  126. package/dist/keys/keys.js +80 -0
  127. package/dist/keys/xpub-ownership.d.ts +10 -0
  128. package/dist/keys/xpub-ownership.js +13 -0
  129. 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 {};