@getcofund/protocol 0.0.0-stage → 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 +414 -2
  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 +60 -3
@@ -0,0 +1,235 @@
1
+ import { concatHex, decodeFunctionResult, encodeAbiParameters, encodeFunctionData, keccak256, numberToHex, stringToHex, zeroHash, } from "viem";
2
+ import { sameAddress } from "../address.js";
3
+ import { WRAPPER_ENTRYPOINT } from "../eip712.js";
4
+ /**
5
+ * What a simulation sends: the vault's calldata, the one State storage
6
+ * override that lets it run before anyone signs, the reads that prove the
7
+ * override took for this call, and the `eth_simulateV1` parameters.
8
+ */
9
+ /** `CFHelpersStatev1.policies`: storage slot 4 (State is not a proxy; its layout is fixed for v1). */
10
+ export const STATE_POLICIES_SLOT = 4n;
11
+ /** `Policy.threshold`: the struct's fifth slot, after active, title, policyType and signerIds. */
12
+ const POLICY_THRESHOLD_OFFSET = 4n;
13
+ const UINT256 = 2n ** 256n;
14
+ /** A realistic cap for a preview; the block's limit would let a runaway call pass. */
15
+ export const SIMULATION_GAS = 3000000n;
16
+ const STATE = [
17
+ {
18
+ type: "function",
19
+ name: "policies",
20
+ stateMutability: "view",
21
+ inputs: [{ type: "bytes32" }, { type: "string" }],
22
+ outputs: [
23
+ { name: "active", type: "bool" },
24
+ { name: "title", type: "string" },
25
+ { name: "policyType", type: "string" },
26
+ { name: "threshold", type: "uint256" },
27
+ {
28
+ name: "transactionDetails",
29
+ type: "tuple",
30
+ components: [
31
+ { name: "wrapper", type: "address" },
32
+ { name: "func", type: "string" },
33
+ ],
34
+ },
35
+ {
36
+ name: "transferDetails",
37
+ type: "tuple",
38
+ components: [
39
+ { name: "maxAmount", type: "uint256" },
40
+ { name: "token", type: "address" },
41
+ { name: "recipients", type: "address[]" },
42
+ ],
43
+ },
44
+ { name: "isTransaction", type: "bool" },
45
+ ],
46
+ },
47
+ {
48
+ type: "function",
49
+ name: "clientVaults",
50
+ stateMutability: "view",
51
+ inputs: [{ type: "bytes32" }],
52
+ outputs: [
53
+ { name: "vaultAddress", type: "address" },
54
+ { name: "vaultTier", type: "string" },
55
+ ],
56
+ },
57
+ ];
58
+ const VAULT = [
59
+ {
60
+ type: "function",
61
+ name: "executeTransaction",
62
+ stateMutability: "nonpayable",
63
+ inputs: [
64
+ { name: "company", type: "string" },
65
+ { name: "policyId", type: "string" },
66
+ { name: "messageType", type: "string" },
67
+ { name: "wrapper", type: "address" },
68
+ { name: "func", type: "string" },
69
+ { name: "instructions", type: "bytes" },
70
+ {
71
+ name: "allowances",
72
+ type: "tuple[]",
73
+ components: [
74
+ { name: "asset", type: "address" },
75
+ { name: "amount", type: "uint256" },
76
+ ],
77
+ },
78
+ { name: "authId", type: "string" },
79
+ { name: "signatures", type: "bytes[]" },
80
+ ],
81
+ outputs: [{ type: "bool" }],
82
+ },
83
+ {
84
+ type: "function",
85
+ name: "state",
86
+ stateMutability: "view",
87
+ inputs: [],
88
+ outputs: [{ type: "address" }],
89
+ },
90
+ ];
91
+ /**
92
+ * The storage slot of `policies[clientId][policyId].threshold` in State:
93
+ * `keccak256(bytes(policyId) ‖ keccak256(clientId ‖ 4)) + 4`.
94
+ */
95
+ export function policyThresholdSlot(clientId, policyId) {
96
+ const inner = keccak256(encodeAbiParameters([{ type: "bytes32" }, { type: "uint256" }], [clientId, STATE_POLICIES_SLOT]));
97
+ const base = keccak256(concatHex([stringToHex(policyId), inner]));
98
+ return numberToHex((BigInt(base) + POLICY_THRESHOLD_OFFSET) % UINT256, {
99
+ size: 32,
100
+ });
101
+ }
102
+ /** The vault's `executeTransaction` calldata; `signatures` sorted by signer, or empty for a preview. */
103
+ export function encodeExecuteTransaction(fields, signatures) {
104
+ return encodeFunctionData({
105
+ abi: VAULT,
106
+ functionName: "executeTransaction",
107
+ args: [
108
+ fields.company,
109
+ fields.policyId,
110
+ fields.messageType,
111
+ fields.wrapper,
112
+ WRAPPER_ENTRYPOINT,
113
+ fields.instructions,
114
+ fields.allowances.map(({ asset, amount }) => ({
115
+ asset: asset,
116
+ amount: BigInt(amount),
117
+ })),
118
+ fields.authId,
119
+ signatures,
120
+ ],
121
+ });
122
+ }
123
+ /**
124
+ * The override, and the reads that prove it took for this call, in the same
125
+ * block before it: State's policy (active, bound to the wrapper, threshold 0),
126
+ * the vault's State, and State's vault for the cofund.
127
+ */
128
+ export function guardsOf(target) {
129
+ if (!target.skipSignatures) {
130
+ return { guards: [] };
131
+ }
132
+ const from = target.tx.from;
133
+ return {
134
+ overrides: {
135
+ [target.state]: {
136
+ stateDiff: {
137
+ [policyThresholdSlot(target.clientId, target.policyId)]: zeroHash,
138
+ },
139
+ },
140
+ },
141
+ guards: [
142
+ {
143
+ from,
144
+ to: target.state,
145
+ data: encodeFunctionData({
146
+ abi: STATE,
147
+ functionName: "policies",
148
+ args: [target.clientId, target.policyId],
149
+ }),
150
+ },
151
+ {
152
+ from,
153
+ to: target.vault,
154
+ data: encodeFunctionData({ abi: VAULT, functionName: "state" }),
155
+ },
156
+ {
157
+ from,
158
+ to: target.state,
159
+ data: encodeFunctionData({
160
+ abi: STATE,
161
+ functionName: "clientVaults",
162
+ args: [target.clientId],
163
+ }),
164
+ },
165
+ ],
166
+ };
167
+ }
168
+ const ran = (result) => result !== undefined && BigInt(result.status) === 1n;
169
+ /** The guards' verdict: null when the simulation is this call's, else why not. */
170
+ export function guardFailure(target, [policy, vaultState, clientVault]) {
171
+ if (!target.skipSignatures) {
172
+ return null;
173
+ }
174
+ if (!(ran(policy) && ran(vaultState) && ran(clientVault))) {
175
+ return "the policy, the vault or State could not be read";
176
+ }
177
+ const [active, , , threshold, transactionDetails] = decodeFunctionResult({
178
+ abi: STATE,
179
+ functionName: "policies",
180
+ data: policy.returnData,
181
+ });
182
+ if (threshold !== 0n) {
183
+ return "the signature checks could not be skipped on this chain (the threshold override did not take)";
184
+ }
185
+ if (!active) {
186
+ return "the policy is not active on chain";
187
+ }
188
+ if (!sameAddress(transactionDetails.wrapper, target.wrapper)) {
189
+ return "the policy on chain is bound to another wrapper";
190
+ }
191
+ const state = decodeFunctionResult({
192
+ abi: VAULT,
193
+ functionName: "state",
194
+ data: vaultState.returnData,
195
+ });
196
+ if (!sameAddress(state, target.state)) {
197
+ return "the vault answers to another State";
198
+ }
199
+ const [vaultAddress] = decodeFunctionResult({
200
+ abi: STATE,
201
+ functionName: "clientVaults",
202
+ data: clientVault.returnData,
203
+ });
204
+ return sameAddress(vaultAddress, target.vault)
205
+ ? null
206
+ : "State does not name the vault as the cofund's";
207
+ }
208
+ /** The call itself, capped at a preview's gas unless told otherwise. */
209
+ export function txCall(target) {
210
+ const gas = target.gas === undefined ? SIMULATION_GAS : target.gas;
211
+ return {
212
+ ...target.tx,
213
+ ...(gas !== null && { gas: numberToHex(gas) }),
214
+ };
215
+ }
216
+ /** The `eth_simulateV1` parameters: one block on top of `head`, no validation, native transfers as logs. */
217
+ export function simulateParams(head, overrides, calls) {
218
+ return [
219
+ {
220
+ blockStateCalls: [
221
+ {
222
+ // Contracts that read `basefee` or `gasprice` see the chain's, not 0.
223
+ ...(head.baseFeePerGas && {
224
+ blockOverrides: { baseFeePerGas: head.baseFeePerGas },
225
+ }),
226
+ ...(overrides && { stateOverrides: overrides }),
227
+ calls,
228
+ },
229
+ ],
230
+ validation: false,
231
+ traceTransfers: true,
232
+ },
233
+ head.number,
234
+ ];
235
+ }
@@ -0,0 +1,29 @@
1
+ import { type Abi } from "viem";
2
+ /**
3
+ * `text` as a reason Cofund shows: no control or formatting characters, no
4
+ * URLs (an RPC URL can carry a key), whitespace collapsed, at most 200
5
+ * characters, and never empty.
6
+ */
7
+ export declare function cleanReason(text: string): string;
8
+ /**
9
+ * Anything that names a custom error from its revert data: a viem `Abi`, or a
10
+ * decoder with ethers' `Interface.parseError`.
11
+ */
12
+ export type ErrorDecoder = Abi | {
13
+ parseError(data: string): {
14
+ name: string;
15
+ } | null;
16
+ };
17
+ /**
18
+ * Why `data` reverts: the revert string, the panic, a known error's name
19
+ * (the relay's, then the caller's own contracts' `errors`), else a short
20
+ * message with the selector; never the error's arguments (addresses,
21
+ * amounts). An unreviewed contract's (`unlisted`) own message is not shown:
22
+ * only an error's name, a panic or a selector.
23
+ */
24
+ export declare function revertReason(data: string, { unlisted, errors, }?: {
25
+ unlisted?: boolean;
26
+ errors?: ErrorDecoder[];
27
+ }): string;
28
+ /** A node failure as a short, clean reason: its message, never a URL. */
29
+ export declare function nodeFailure(error: unknown): string;
@@ -0,0 +1,192 @@
1
+ import { decodeAbiParameters, decodeErrorResult, hexToBytes, parseAbi, } from "viem";
2
+ /**
3
+ * Why a call reverts, and what a node said, made safe to show: revert data
4
+ * and node messages are anyone's text, and they reach the web, email, the CLI
5
+ * and webhooks.
6
+ */
7
+ /**
8
+ * The errors a relayed call can revert with, by name: the vault's and State's
9
+ * (their reverts bubble), the sponsor guard's, and OpenZeppelin's ERC-20,
10
+ * SafeERC20 and ECDSA errors.
11
+ */
12
+ const RELAY_ERRORS = parseAbi([
13
+ "error AmountExceedsPolicy()",
14
+ "error ECDSAInvalidSignature()",
15
+ "error ECDSAInvalidSignatureLength(uint256)",
16
+ "error ECDSAInvalidSignatureS(bytes32)",
17
+ "error InactivePolicy()",
18
+ "error InvalidAmount()",
19
+ "error InvalidCompany()",
20
+ "error InvalidPolicyType()",
21
+ "error InvalidSignature()",
22
+ "error InvalidSigner()",
23
+ "error InvalidToken()",
24
+ "error InvalidType()",
25
+ "error RecipientNotAllowed()",
26
+ "error SafeERC20FailedOperation(address)",
27
+ "error ThresholdNotMet()",
28
+ "error UnauthorizedCaller(string)",
29
+ "error KeyExists()",
30
+ "error InactiveUser()",
31
+ "error InvalidPolicy()",
32
+ "error InvalidClient()",
33
+ "error CallNotAllowed()",
34
+ "error GuardPaused()",
35
+ "error InvalidCall()",
36
+ "error NotASponsor()",
37
+ "error ERC20InsufficientBalance(address,uint256,uint256)",
38
+ "error ERC20InsufficientAllowance(address,uint256,uint256)",
39
+ "error ERC20InvalidSender(address)",
40
+ "error ERC20InvalidReceiver(address)",
41
+ "error ERC20InvalidApprover(address)",
42
+ "error ERC20InvalidSpender(address)",
43
+ ]);
44
+ /** `Error(string)`: a `require` or `revert` with a message. */
45
+ const ERROR_STRING = "0x08c379a0";
46
+ const MAX_REASON = 200;
47
+ const URLS = /\b(?:https?|wss?):\/\/\S+/gi;
48
+ const UNPRINTABLE = /[\p{C}]+/gu;
49
+ const SPACES = /\s+/g;
50
+ /**
51
+ * `text` as a reason Cofund shows: no control or formatting characters, no
52
+ * URLs (an RPC URL can carry a key), whitespace collapsed, at most 200
53
+ * characters, and never empty.
54
+ */
55
+ export function cleanReason(text) {
56
+ const clean = text
57
+ .replace(URLS, "<link>")
58
+ .replace(UNPRINTABLE, " ")
59
+ .replace(SPACES, " ")
60
+ .trim()
61
+ .slice(0, MAX_REASON)
62
+ .trim();
63
+ return clean || "no reason given";
64
+ }
65
+ /** The error `data` encodes, by name, from the first decoder that knows it. */
66
+ function errorName(data, decoders) {
67
+ for (const decoder of decoders) {
68
+ try {
69
+ if (Array.isArray(decoder)) {
70
+ return decodeErrorResult({ abi: decoder, data: data })
71
+ .errorName;
72
+ }
73
+ const parsed = decoder.parseError(data);
74
+ if (parsed) {
75
+ return parsed.name;
76
+ }
77
+ }
78
+ catch {
79
+ // Not this contract's.
80
+ }
81
+ }
82
+ return null;
83
+ }
84
+ /** ethers' names for Solidity's panic codes. */
85
+ const PANIC_REASONS = {
86
+ 0: "GENERIC_PANIC",
87
+ 1: "ASSERT_FALSE",
88
+ 17: "OVERFLOW",
89
+ 18: "DIVIDE_BY_ZERO",
90
+ 33: "ENUM_RANGE_ERROR",
91
+ 34: "BAD_STORAGE_DATA",
92
+ 49: "STACK_UNDERFLOW",
93
+ 50: "ARRAY_RANGE_ERROR",
94
+ 65: "OUT_OF_MEMORY",
95
+ 81: "UNINITIALIZED_FUNCTION_CALL",
96
+ };
97
+ const PANIC = "0x4e487b71";
98
+ const STRICT_UTF8 = new TextDecoder("utf-8", { fatal: true });
99
+ /**
100
+ * What a revert's data says on its own, as ethers reads a call exception: a
101
+ * `require` or `revert` message, a panic, nothing at all (`require(false)`),
102
+ * else a short message for data it cannot read.
103
+ */
104
+ function builtinReason(data) {
105
+ if (!data) {
106
+ return { reason: null, shortMessage: "missing revert data" };
107
+ }
108
+ const bytes = hexToBytes(data);
109
+ const shortMessage = "execution reverted";
110
+ if (bytes.length === 0) {
111
+ return {
112
+ reason: "require(false)",
113
+ shortMessage: `${shortMessage} (no data present; likely require(false) occurred`,
114
+ };
115
+ }
116
+ if (bytes.length % 32 !== 4) {
117
+ return {
118
+ reason: null,
119
+ shortMessage: `${shortMessage} (could not decode reason; invalid data length)`,
120
+ };
121
+ }
122
+ const selector = data.slice(0, 10).toLowerCase();
123
+ const args = `0x${data.slice(10)}`;
124
+ if (selector === ERROR_STRING) {
125
+ try {
126
+ const [raw] = decodeAbiParameters([{ type: "bytes" }], args);
127
+ return { reason: STRICT_UTF8.decode(hexToBytes(raw)), shortMessage };
128
+ }
129
+ catch {
130
+ return {
131
+ reason: null,
132
+ shortMessage: `${shortMessage} (could not decode reason; invalid string data)`,
133
+ };
134
+ }
135
+ }
136
+ if (selector === PANIC) {
137
+ try {
138
+ const [code] = decodeAbiParameters([{ type: "uint256" }], args);
139
+ const n = Number(code);
140
+ return {
141
+ reason: `Panic due to ${PANIC_REASONS[n] ?? "UNKNOWN"}(${n})`,
142
+ shortMessage,
143
+ };
144
+ }
145
+ catch {
146
+ return {
147
+ reason: null,
148
+ shortMessage: `${shortMessage} (could not decode panic code)`,
149
+ };
150
+ }
151
+ }
152
+ return {
153
+ reason: null,
154
+ shortMessage: `${shortMessage} (unknown custom error)`,
155
+ };
156
+ }
157
+ /**
158
+ * Why `data` reverts: the revert string, the panic, a known error's name
159
+ * (the relay's, then the caller's own contracts' `errors`), else a short
160
+ * message with the selector; never the error's arguments (addresses,
161
+ * amounts). An unreviewed contract's (`unlisted`) own message is not shown:
162
+ * only an error's name, a panic or a selector.
163
+ */
164
+ export function revertReason(data, { unlisted = false, errors = [], } = {}) {
165
+ const builtin = builtinReason(data);
166
+ const message = data.toLowerCase().startsWith(ERROR_STRING);
167
+ if (message && unlisted) {
168
+ return "reverted with a message from a contract Cofund hasn't reviewed";
169
+ }
170
+ if (builtin.reason !== null) {
171
+ return cleanReason(builtin.reason);
172
+ }
173
+ const name = errorName(data, [RELAY_ERRORS, ...errors]);
174
+ if (name) {
175
+ return name;
176
+ }
177
+ const selector = data.length >= 10 ? ` (${data.slice(0, 10)})` : "";
178
+ return cleanReason(`${builtin.shortMessage}${selector}`);
179
+ }
180
+ /** A node failure as a short, clean reason: its message, never a URL. */
181
+ export function nodeFailure(error) {
182
+ const nested = error?.error?.message;
183
+ let message = String(error);
184
+ if (typeof nested === "string") {
185
+ message = nested;
186
+ }
187
+ else if (error instanceof Error) {
188
+ message =
189
+ error.shortMessage ?? error.message;
190
+ }
191
+ return cleanReason(message);
192
+ }
@@ -0,0 +1,51 @@
1
+ import type { EvmReader } from "../chain.ts";
2
+ import { type CallEffect, type CallSimulation } from "../types.ts";
3
+ import { type BalanceLog, type HolderRoles, type TokenInfo } from "./balances.ts";
4
+ import { type SimulationTarget } from "./request.ts";
5
+ /**
6
+ * A call's effect, simulated before anyone signs it. The call runs as it
7
+ * would execute with `eth_simulateV1` on top of the latest block, its
8
+ * signature checks skipped by the threshold override (`request.ts`), the
9
+ * guards read in the same block; a second request of the same block reads
10
+ * every touched (token, holder) balance before and after it, so the changes
11
+ * are exact for rebasing and fee-on-transfer tokens too. It must run the same
12
+ * way both times. Nothing here holds a node: Cofund's server passes its
13
+ * provider's, an agent its own.
14
+ */
15
+ /** A JSON-RPC request to one node, as `send(method, params)`. */
16
+ export type JsonRpcSend = (method: string, params: unknown[]) => Promise<unknown>;
17
+ /** A node to simulate on: a reader (a viem public client, any EIP-1193 provider) or a `send`. */
18
+ export type EvmNode = EvmReader | JsonRpcSend;
19
+ /** `node` as a `send`. */
20
+ export declare function nodeSend(node: EvmNode): JsonRpcSend;
21
+ export type SimulationOptions = {
22
+ /** Cofund's token table: a listed token's symbol and decimals, null for any other. */
23
+ listedToken?: (address: string) => {
24
+ symbol: string;
25
+ decimals: number;
26
+ } | null;
27
+ /** The wrapper is one Cofund hasn't reviewed: its own revert messages are not shown. */
28
+ unlisted?: boolean;
29
+ now?: () => Date;
30
+ };
31
+ /**
32
+ * Simulate `target` on `node`: the call's balance changes, or why it
33
+ * reverts, or `unavailable` (the node failed, a guard did not hold, the call
34
+ * ran differently twice, or the result is not one the API can carry). Never
35
+ * throws.
36
+ */
37
+ export declare function simulateCall(node: EvmNode, target: SimulationTarget, options?: SimulationOptions): Promise<CallSimulation>;
38
+ /**
39
+ * What an executed call did, from its own logs (a sponsored operation's, not
40
+ * its bundle's): summed per holder, the tokens named by `token`. The native
41
+ * coin moves without a log, so its moves are not in it.
42
+ */
43
+ export declare function effectFromLogs(input: {
44
+ logs: BalanceLog[];
45
+ roles: HolderRoles;
46
+ block: {
47
+ number: bigint;
48
+ hash: string;
49
+ };
50
+ hash: string;
51
+ }, token: (address: string) => TokenInfo | null, now?: Date): CallEffect | null;