@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,188 @@
1
+ import { getAddress } from "viem";
2
+ import { SIMULATION_CAVEAT, UNLISTED_SIMULATION_WARNING, } from "../types.js";
3
+ import { cleanReason } from "./revert.js";
4
+ /**
5
+ * How every surface shows a simulation or an executed effect (the web review,
6
+ * the signing email, the CLI): one description, rendered by each in its own
7
+ * layout with its own amount format.
8
+ */
9
+ /** Every EVM chain Cofund runs on pays gas, and moves value, in ETH. */
10
+ export const NATIVE_SYMBOL = "ETH";
11
+ const NATIVE_DECIMALS = 18;
12
+ const TRAILING_ZEROS = /0+$/;
13
+ /**
14
+ * The CLI's format: every digit, as ethers writes units. A whole amount keeps
15
+ * one decimal ("1.0"), trailing zeros go, a token without decimals has no
16
+ * point.
17
+ */
18
+ export const formatUnitsAmount = (units, decimals) => {
19
+ const negative = units < 0n;
20
+ let digits = (negative ? -units : units).toString();
21
+ if (decimals === 0) {
22
+ return `${negative ? "-" : ""}${digits}`;
23
+ }
24
+ digits = digits.padStart(decimals + 1, "0");
25
+ const whole = digits.slice(0, digits.length - decimals);
26
+ const fraction = digits
27
+ .slice(digits.length - decimals)
28
+ .replace(TRAILING_ZEROS, "");
29
+ return `${negative ? "-" : ""}${whole}.${fraction || "0"}`;
30
+ };
31
+ /**
32
+ * An asset as approvers read it: the native coin and a token from Cofund's
33
+ * list by their symbol; any other token by its own claim, in quotes, with its
34
+ * address (a token's `symbol()` is anyone's to set).
35
+ */
36
+ export function assetLabel(asset) {
37
+ if (asset.kind === "native") {
38
+ return NATIVE_SYMBOL;
39
+ }
40
+ if (asset.listed && asset.symbol) {
41
+ return asset.symbol;
42
+ }
43
+ return asset.symbol ? `"${asset.symbol}" (${asset.address})` : asset.address;
44
+ }
45
+ /** "Vault", "Wrapper", or the holder's address. */
46
+ export function holderLabel(change) {
47
+ if (change.role === "vault") {
48
+ return "Vault";
49
+ }
50
+ return change.role === "wrapper" ? "Wrapper" : change.holder;
51
+ }
52
+ /** An amount of an asset; a token of unknown decimals in base units. `withAddress` adds a listed token's address. */
53
+ function amountOf(asset, units, format, withAddress = false) {
54
+ const decimals = asset.kind === "native" ? NATIVE_DECIMALS : asset.decimals;
55
+ const label = assetLabel(asset);
56
+ if (decimals === null) {
57
+ return `${units} base units of ${label}`;
58
+ }
59
+ const address = withAddress && asset.kind === "erc20" && asset.listed
60
+ ? ` (${asset.address})`
61
+ : "";
62
+ return `${format(units, decimals)} ${label}${address}`;
63
+ }
64
+ /** A change as a signed amount with its asset; none as "no change in" it. */
65
+ export function formatDelta(change, format, { withAddress = false } = {}) {
66
+ const delta = BigInt(change.delta);
67
+ if (delta === 0n) {
68
+ return `no change in ${assetLabel(change.asset)}`;
69
+ }
70
+ const sign = delta < 0n ? "-" : "+";
71
+ const units = delta < 0n ? -delta : delta;
72
+ return `${sign}${amountOf(change.asset, units, format, withAddress)}`;
73
+ }
74
+ /** The changes per holder, the vault first, then the wrapper, then the others as they came. */
75
+ export function changesByHolder(changes) {
76
+ const holders = new Map();
77
+ for (const change of changes) {
78
+ const key = change.holder.toLowerCase();
79
+ const entry = holders.get(key) ?? {
80
+ holder: change.holder,
81
+ role: change.role,
82
+ changes: [],
83
+ };
84
+ entry.changes.push(change);
85
+ holders.set(key, entry);
86
+ }
87
+ return [...holders.values()];
88
+ }
89
+ /** The vault's own changes in a successful simulation; none otherwise. */
90
+ export function vaultChanges(simulation) {
91
+ return simulation?.outcome === "success"
92
+ ? simulation.changes.filter(({ role }) => role === "vault")
93
+ : [];
94
+ }
95
+ /** The tokens simulations named, by address: a listing over a token's claim, a claim with decimals over one without. */
96
+ export function simulationTokens(...simulations) {
97
+ const known = new Map();
98
+ for (const simulation of simulations) {
99
+ const changes = simulation && "changes" in simulation ? simulation.changes : [];
100
+ for (const { asset } of changes) {
101
+ if (asset.kind !== "erc20") {
102
+ continue;
103
+ }
104
+ const address = asset.address.toLowerCase();
105
+ const seen = known.get(address);
106
+ if (!seen ||
107
+ (asset.listed && !seen.listed) ||
108
+ (seen.decimals === null && asset.decimals !== null)) {
109
+ known.set(address, asset);
110
+ }
111
+ }
112
+ }
113
+ return (address) => known.get(address.toLowerCase()) ?? null;
114
+ }
115
+ /**
116
+ * The hard bound every surface ends with: the vault approves exactly the
117
+ * signed allowances to the wrapper for this call and resets them, so per
118
+ * asset it can lose at most the allowance (the last one listed when an asset
119
+ * repeats). Null allowances (stored ones that do not parse) leave it unknown.
120
+ */
121
+ export function allowanceBound(allowances, tokens, format) {
122
+ if (allowances === null) {
123
+ return "The most this call can take from the vault is unavailable: its allowances could not be read.";
124
+ }
125
+ const last = new Map();
126
+ for (const allowance of allowances) {
127
+ last.set(allowance.asset.toLowerCase(), allowance);
128
+ }
129
+ const amounts = [...last.values()].map(({ asset, amount }) => amountOf({
130
+ kind: "erc20",
131
+ address: getAddress(asset),
132
+ ...(tokens(asset) ?? { symbol: null, decimals: null, listed: false }),
133
+ }, BigInt(amount), format));
134
+ return amounts.length === 0
135
+ ? "The vault approves nothing to this call: it can take nothing from the vault."
136
+ : `The most this call can take from the vault is what you approve: ${amounts.join(", ")}.`;
137
+ }
138
+ /** What every surface says after the effect: the caveat, and on an unlisted wrapper its warning. */
139
+ export function simulationCaveat(unlisted) {
140
+ return unlisted
141
+ ? [SIMULATION_CAVEAT, UNLISTED_SIMULATION_WARNING]
142
+ : [SIMULATION_CAVEAT];
143
+ }
144
+ /** "2026-10-15 14:00 UTC". */
145
+ export function utcDateTime(iso) {
146
+ return `${iso.slice(0, 10)} ${iso.slice(11, 16)} UTC`;
147
+ }
148
+ const INCOMPLETE = "It also moves NFTs, or more balances than were read: only these are shown.";
149
+ const NO_NATIVE_LOG = "Native ETH moves leave no log, so they are not shown here.";
150
+ /** `simulation` (or what an executed call did) described, amounts in `format`. */
151
+ export function describeSimulation(simulation, format, { withAddress = false } = {}) {
152
+ const at = utcDateTime(simulation.at);
153
+ if ("outcome" in simulation && simulation.outcome === "unavailable") {
154
+ const notice = `Could not simulate it: ${cleanReason(simulation.reason)}`;
155
+ return {
156
+ status: "unavailable",
157
+ block: null,
158
+ at,
159
+ holders: [],
160
+ notice,
161
+ notes: [],
162
+ };
163
+ }
164
+ const block = simulation.block.number;
165
+ if ("outcome" in simulation && simulation.outcome === "revert") {
166
+ const notice = `Would fail now: ${cleanReason(simulation.reason)}`;
167
+ return { status: "revert", block, at, holders: [], notice, notes: [] };
168
+ }
169
+ const executed = !("outcome" in simulation);
170
+ const holders = changesByHolder(simulation.changes).map((holder) => ({
171
+ label: holderLabel(holder),
172
+ address: holder.holder,
173
+ role: holder.role,
174
+ changes: holder.changes.map((c) => formatDelta(c, format, { withAddress })),
175
+ }));
176
+ const nothing = executed ? "No balance changed" : "No balance would change";
177
+ return {
178
+ status: executed ? "executed" : "success",
179
+ block,
180
+ at,
181
+ holders,
182
+ notice: holders.length === 0 ? nothing : null,
183
+ notes: [
184
+ ...(simulation.incomplete ? [INCOMPLETE] : []),
185
+ ...(executed ? [NO_NATIVE_LOG] : []),
186
+ ],
187
+ };
188
+ }
@@ -0,0 +1,12 @@
1
+ /**
2
+ * A protocol call's effect, simulated before anyone signs it (`simulateCall`,
3
+ * the same code Cofund's server and an agent's own node run), what an
4
+ * executed call did (`effectFromLogs`), and how every surface describes
5
+ * either.
6
+ */
7
+ export { type BalanceLog, RpcLogSchema, RpcQuantity, type TokenInfo, } from "./balances.ts";
8
+ export { type AmountFormat, allowanceBound, describeSimulation, formatUnitsAmount, type SimulationView, simulationCaveat, simulationTokens, type TokenLookup, vaultChanges, } from "./describe.ts";
9
+ export { compareSimulations, type OwnNode, type OwnNodeCall, type OwnRpc, SimulationMismatchError, simulateOnOwnNode, } from "./own-node.ts";
10
+ export { type ExecuteTransactionFields, encodeExecuteTransaction, policyThresholdSlot, type SimulationTarget, } from "./request.ts";
11
+ export { type ErrorDecoder, revertReason } from "./revert.ts";
12
+ export { type EvmNode, effectFromLogs, type JsonRpcSend, nodeSend, type SimulationOptions, simulateCall, } from "./run.ts";
@@ -0,0 +1,13 @@
1
+ /**
2
+ * A protocol call's effect, simulated before anyone signs it (`simulateCall`,
3
+ * the same code Cofund's server and an agent's own node run), what an
4
+ * executed call did (`effectFromLogs`), and how every surface describes
5
+ * either.
6
+ */
7
+ // biome-ignore lint/performance/noBarrelFile: the simulation part of the `./evm` entry
8
+ export { RpcLogSchema, RpcQuantity, } from "./balances.js";
9
+ export { allowanceBound, describeSimulation, formatUnitsAmount, simulationCaveat, simulationTokens, vaultChanges, } from "./describe.js";
10
+ export { compareSimulations, SimulationMismatchError, simulateOnOwnNode, } from "./own-node.js";
11
+ export { encodeExecuteTransaction, policyThresholdSlot, } from "./request.js";
12
+ export { revertReason } from "./revert.js";
13
+ export { effectFromLogs, nodeSend, simulateCall, } from "./run.js";
@@ -0,0 +1,62 @@
1
+ import type { EvmReader } from "../chain.ts";
2
+ import type { CallSimulation, ChainPins, EvmChainName } from "../types.ts";
3
+ import { type ExecuteTransactionFields } from "./request.ts";
4
+ import { type JsonRpcSend } from "./run.ts";
5
+ /**
6
+ * The agent's own check: a call simulated on the agent's own node with the
7
+ * same code, trusting Cofund for nothing but the request's own fields, and
8
+ * compared with the simulation Cofund reports.
9
+ */
10
+ /** A node of the caller's own: a URL, a reader (a viem public client), or a provider with `send`. */
11
+ export type OwnRpc = string | EvmReader | {
12
+ send: JsonRpcSend;
13
+ };
14
+ /** The caller's nodes per chain, and the address it simulates from: its own, an active member's. */
15
+ export type OwnNode = {
16
+ rpc: Partial<Record<EvmChainName, OwnRpc>>;
17
+ from: string;
18
+ };
19
+ /**
20
+ * How far two simulations of one call at different blocks may differ in an
21
+ * amount (basis points of the larger) before they disagree: interest accrues
22
+ * and prices move between blocks, but not by more than this in seconds.
23
+ */
24
+ export declare const SIMULATION_TOLERANCE_BPS = 100n;
25
+ /** Base units two simulations may differ by whatever the amount: a rebasing token's rounding. */
26
+ export declare const SIMULATION_DUST = 2n;
27
+ /** A vault's `executeTransaction`, to simulate: its chain, the vault, and the fields its members sign. */
28
+ export type OwnNodeCall = {
29
+ chain: EvmChainName;
30
+ vault: string;
31
+ /** `company` is the pinned one. */
32
+ fields: ExecuteTransactionFields;
33
+ /** The wrapper is one Cofund hasn't reviewed (`SimulationOptions.unlisted`). */
34
+ unlisted: boolean;
35
+ };
36
+ /**
37
+ * `call`'s simulation on the caller's own node: the vault called directly
38
+ * from `node.from` (the vault admits any active member as the broadcaster),
39
+ * under the pinned State, chain id and company. Never throws.
40
+ */
41
+ export declare function simulateOnOwnNode(call: OwnNodeCall, { pins, node }: {
42
+ pins: ChainPins;
43
+ node: OwnNode;
44
+ }): Promise<CallSimulation>;
45
+ type Ran = Exclude<CallSimulation, {
46
+ outcome: "unavailable";
47
+ }>;
48
+ /**
49
+ * Where two simulations of one call disagree beyond what a block's difference
50
+ * explains: a different outcome, or a holder's amount of an asset off by more
51
+ * than `SIMULATION_TOLERANCE_BPS` of the larger and more than dust (one in one
52
+ * and not the other included). Empty when they agree, both reverting
53
+ * included.
54
+ */
55
+ export declare function compareSimulations(cofund: Ran, own: Ran): string[];
56
+ /** Cofund's simulation of a call disagrees with the caller's own node's. */
57
+ export declare class SimulationMismatchError extends Error {
58
+ readonly own: CallSimulation;
59
+ readonly differences: string[];
60
+ constructor(own: CallSimulation, differences: string[]);
61
+ }
62
+ export {};
@@ -0,0 +1,122 @@
1
+ import { createPublicClient, http } from "viem";
2
+ import { cofundClientId } from "../eip712.js";
3
+ import { NATIVE_ASSET } from "./balances.js";
4
+ import { assetLabel, formatDelta, formatUnitsAmount, holderLabel, simulationTokens, } from "./describe.js";
5
+ import { encodeExecuteTransaction, } from "./request.js";
6
+ import { nodeSend, simulateCall } from "./run.js";
7
+ /**
8
+ * How far two simulations of one call at different blocks may differ in an
9
+ * amount (basis points of the larger) before they disagree: interest accrues
10
+ * and prices move between blocks, but not by more than this in seconds.
11
+ */
12
+ export const SIMULATION_TOLERANCE_BPS = 100n;
13
+ /** Base units two simulations may differ by whatever the amount: a rebasing token's rounding. */
14
+ export const SIMULATION_DUST = 2n;
15
+ /** `rpc` as a `send`: a URL over viem's HTTP transport, a reader's `request`, a provider's `send`. */
16
+ function ownSend(rpc) {
17
+ if (typeof rpc === "string") {
18
+ return nodeSend(createPublicClient({ transport: http(rpc, { retryCount: 0 }) }));
19
+ }
20
+ return "request" in rpc
21
+ ? nodeSend(rpc)
22
+ : (method, params) => rpc.send(method, params);
23
+ }
24
+ /**
25
+ * `call`'s simulation on the caller's own node: the vault called directly
26
+ * from `node.from` (the vault admits any active member as the broadcaster),
27
+ * under the pinned State, chain id and company. Never throws.
28
+ */
29
+ export async function simulateOnOwnNode(call, { pins, node }) {
30
+ const at = new Date().toISOString();
31
+ const pin = pins[call.chain];
32
+ const rpc = node.rpc[call.chain];
33
+ if (!(pin && rpc)) {
34
+ const reason = pin
35
+ ? `No node of yours is set for ${call.chain}`
36
+ : `No chain id and State are pinned for ${call.chain}`;
37
+ return { outcome: "unavailable", at, reason };
38
+ }
39
+ const send = ownSend(rpc);
40
+ try {
41
+ const chainId = Number(BigInt(String(await send("eth_chainId", []))));
42
+ if (chainId !== pin.chainId) {
43
+ const reason = `Your ${call.chain} node is on chain ${chainId}, not ${pin.chainId}`;
44
+ return { outcome: "unavailable", at, reason };
45
+ }
46
+ return await simulateCall(send, {
47
+ state: pin.state,
48
+ clientId: cofundClientId(call.fields.company),
49
+ policyId: call.fields.policyId,
50
+ vault: call.vault,
51
+ wrapper: call.fields.wrapper,
52
+ tx: {
53
+ from: node.from,
54
+ to: call.vault,
55
+ data: encodeExecuteTransaction(call.fields, []),
56
+ },
57
+ skipSignatures: true,
58
+ }, { unlisted: call.unlisted });
59
+ }
60
+ catch {
61
+ const reason = `Your ${call.chain} node could not be read`;
62
+ return { outcome: "unavailable", at, reason };
63
+ }
64
+ }
65
+ const changeKey = (change) => `${change.holder.toLowerCase()}|${change.asset.kind === "native"
66
+ ? NATIVE_ASSET
67
+ : change.asset.address.toLowerCase()}`;
68
+ const abs = (x) => (x < 0n ? -x : x);
69
+ /** Two amounts that differ by no more than a block's difference, or dust. */
70
+ function close(a, b) {
71
+ if (abs(a - b) <= SIMULATION_DUST) {
72
+ return true;
73
+ }
74
+ const sameSign = a > 0n === b > 0n && a < 0n === b < 0n;
75
+ const larger = abs(a) > abs(b) ? abs(a) : abs(b);
76
+ return sameSign && abs(a - b) * 10000n <= larger * SIMULATION_TOLERANCE_BPS;
77
+ }
78
+ /**
79
+ * Where two simulations of one call disagree beyond what a block's difference
80
+ * explains: a different outcome, or a holder's amount of an asset off by more
81
+ * than `SIMULATION_TOLERANCE_BPS` of the larger and more than dust (one in one
82
+ * and not the other included). Empty when they agree, both reverting
83
+ * included.
84
+ */
85
+ export function compareSimulations(cofund, own) {
86
+ if (cofund.outcome !== own.outcome) {
87
+ const said = (s) => s.outcome === "revert" ? `would revert (${s.reason})` : "succeeds";
88
+ return [`Cofund's simulation ${said(cofund)}; your node's ${said(own)}`];
89
+ }
90
+ if (cofund.outcome !== "success" || own.outcome !== "success") {
91
+ return [];
92
+ }
93
+ const theirs = new Map(cofund.changes.map((c) => [changeKey(c), c]));
94
+ const ours = new Map(own.changes.map((c) => [changeKey(c), c]));
95
+ const named = simulationTokens(cofund, own);
96
+ const differences = [];
97
+ for (const key of new Set([...theirs.keys(), ...ours.keys()])) {
98
+ const a = BigInt(theirs.get(key)?.delta ?? "0");
99
+ const b = BigInt(ours.get(key)?.delta ?? "0");
100
+ if (close(a, b)) {
101
+ continue;
102
+ }
103
+ const change = (theirs.get(key) ?? ours.get(key));
104
+ const asset = change.asset.kind === "erc20"
105
+ ? { ...change.asset, ...named(change.asset.address) }
106
+ : change.asset;
107
+ const show = (delta) => formatDelta({ asset, delta: delta.toString() }, formatUnitsAmount);
108
+ differences.push(`${holderLabel(change)} ${assetLabel(asset)}: Cofund ${show(a)}, your node ${show(b)}`);
109
+ }
110
+ return differences;
111
+ }
112
+ /** Cofund's simulation of a call disagrees with the caller's own node's. */
113
+ export class SimulationMismatchError extends Error {
114
+ own;
115
+ differences;
116
+ constructor(own, differences) {
117
+ super(`Cofund's simulation disagrees with your node's: ${differences.join("; ")}`);
118
+ this.name = "SimulationMismatchError";
119
+ this.own = own;
120
+ this.differences = differences;
121
+ }
122
+ }
@@ -0,0 +1,86 @@
1
+ import { type Hex } from "viem";
2
+ /**
3
+ * What a simulation sends: the vault's calldata, the one State storage
4
+ * override that lets it run before anyone signs, the reads that prove the
5
+ * override took for this call, and the `eth_simulateV1` parameters.
6
+ */
7
+ /** `CFHelpersStatev1.policies`: storage slot 4 (State is not a proxy; its layout is fixed for v1). */
8
+ export declare const STATE_POLICIES_SLOT = 4n;
9
+ /** A realistic cap for a preview; the block's limit would let a runaway call pass. */
10
+ export declare const SIMULATION_GAS = 3000000n;
11
+ /**
12
+ * The storage slot of `policies[clientId][policyId].threshold` in State:
13
+ * `keccak256(bytes(policyId) ‖ keccak256(clientId ‖ 4)) + 4`.
14
+ */
15
+ export declare function policyThresholdSlot(clientId: string, policyId: string): string;
16
+ /** The fields `executeTransaction` takes, as a request or a stored call carries them. */
17
+ export type ExecuteTransactionFields = {
18
+ company: string;
19
+ policyId: string;
20
+ messageType: string;
21
+ wrapper: string;
22
+ instructions: string;
23
+ allowances: {
24
+ asset: string;
25
+ amount: string;
26
+ }[];
27
+ authId: string;
28
+ };
29
+ /** The vault's `executeTransaction` calldata; `signatures` sorted by signer, or empty for a preview. */
30
+ export declare function encodeExecuteTransaction(fields: ExecuteTransactionFields, signatures: string[]): Hex;
31
+ /** What to simulate. */
32
+ export type SimulationTarget = {
33
+ /** The chain's State: the threshold override's target. */
34
+ state: string;
35
+ clientId: string;
36
+ policyId: string;
37
+ vault: string;
38
+ wrapper: string;
39
+ /** The transaction: the relay through the guard (Cofund), or the vault called by a member (an agent). */
40
+ tx: {
41
+ from: string;
42
+ to: string;
43
+ data: string;
44
+ };
45
+ /**
46
+ * True before the threshold: the calldata carries no signature, and the
47
+ * policy's threshold is overridden to 0. False with the real signatures:
48
+ * no override and no guard, the exact call as broadcast.
49
+ */
50
+ skipSignatures: boolean;
51
+ /** The transaction's gas; null for the node's own cap. */
52
+ gas?: bigint | null;
53
+ };
54
+ export type SimCall = {
55
+ from: string;
56
+ to: string;
57
+ data: string;
58
+ gas?: string;
59
+ };
60
+ export type StateOverrides = Record<string, {
61
+ stateDiff: Record<string, string>;
62
+ }>;
63
+ /** A call's result as the guard reads it. */
64
+ type ReadResult = {
65
+ status: string;
66
+ returnData: string;
67
+ };
68
+ /**
69
+ * The override, and the reads that prove it took for this call, in the same
70
+ * block before it: State's policy (active, bound to the wrapper, threshold 0),
71
+ * the vault's State, and State's vault for the cofund.
72
+ */
73
+ export declare function guardsOf(target: SimulationTarget): {
74
+ overrides?: StateOverrides;
75
+ guards: SimCall[];
76
+ };
77
+ /** The guards' verdict: null when the simulation is this call's, else why not. */
78
+ export declare function guardFailure(target: SimulationTarget, [policy, vaultState, clientVault]: ReadResult[]): string | null;
79
+ /** The call itself, capped at a preview's gas unless told otherwise. */
80
+ export declare function txCall(target: SimulationTarget): SimCall;
81
+ /** The `eth_simulateV1` parameters: one block on top of `head`, no validation, native transfers as logs. */
82
+ export declare function simulateParams(head: {
83
+ number: string;
84
+ baseFeePerGas?: string;
85
+ }, overrides: StateOverrides | undefined, calls: SimCall[]): [unknown, string];
86
+ export {};