@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,179 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HOOD: Robinhood Chain mainnet (4663) swap venue — USDG <> tokenized stocks
|
|
3
|
+
* through Uniswap v4, executed by the `GroupCashSwapWrapperV4` wrapper that
|
|
4
|
+
* has been live since July 2026. Everything Robinhood-specific hangs off this
|
|
5
|
+
* module so the feature can be lifted out in one piece.
|
|
6
|
+
*
|
|
7
|
+
* Every pool below was live-quoted through the V4 Quoter on Sept 6, 2026
|
|
8
|
+
* (100 USDG in). A pool that stops quoting means its fee / tickSpacing
|
|
9
|
+
* changed or liquidity left — re-probe before changing anything here. The
|
|
10
|
+
* token addresses match the seeded Token rows in
|
|
11
|
+
* `packages/database/prisma/src/eth-token-metadata.ts`; update both together.
|
|
12
|
+
*/
|
|
13
|
+
/** Quote and settlement token on Robinhood Chain (there is no USDC). */
|
|
14
|
+
export const USDG = {
|
|
15
|
+
symbol: "USDG",
|
|
16
|
+
name: "Global Dollar",
|
|
17
|
+
address: "0x5fc5360D0400a0Fd4f2af552ADD042D716F1d168",
|
|
18
|
+
decimals: 6,
|
|
19
|
+
};
|
|
20
|
+
/** Robinhood Stock Tokens are 18 decimals. */
|
|
21
|
+
export const STOCK_TOKEN_DECIMALS = 18;
|
|
22
|
+
/** Uniswap V4 Quoter on 4663 (`quoteExactInputSingle`). */
|
|
23
|
+
export const V4_QUOTER_ADDRESS = "0x8Dc178eFB8111BB0973Dd9d722ebeFF267c98F94";
|
|
24
|
+
/** Slippage bound signed into every swap: 1% for equities. */
|
|
25
|
+
export const STOCK_SWAP_SLIPPAGE_BPS = 100;
|
|
26
|
+
/**
|
|
27
|
+
* Stocks with a real USDG pool, in display order. NFLX is seeded as a token
|
|
28
|
+
* but has no pool, so it is deliberately absent: offering it would sign
|
|
29
|
+
* proposals that can only revert.
|
|
30
|
+
*/
|
|
31
|
+
export const STOCK_POOLS = {
|
|
32
|
+
AAPL: {
|
|
33
|
+
symbol: "AAPL",
|
|
34
|
+
name: "Apple Inc.",
|
|
35
|
+
label: "Apple",
|
|
36
|
+
color: "#A2AAAD",
|
|
37
|
+
address: "0xaF3D76f1834A1d425780943C99Ea8A608f8a93f9",
|
|
38
|
+
fee: 10_000,
|
|
39
|
+
tickSpacing: 200,
|
|
40
|
+
},
|
|
41
|
+
NVDA: {
|
|
42
|
+
symbol: "NVDA",
|
|
43
|
+
name: "NVIDIA Corp.",
|
|
44
|
+
label: "NVIDIA",
|
|
45
|
+
color: "#76B900",
|
|
46
|
+
address: "0xd0601CE157Db5bdC3162BbaC2a2C8aF5320D9EEC",
|
|
47
|
+
fee: 3000,
|
|
48
|
+
tickSpacing: 60,
|
|
49
|
+
},
|
|
50
|
+
MSFT: {
|
|
51
|
+
symbol: "MSFT",
|
|
52
|
+
name: "Microsoft Corp.",
|
|
53
|
+
label: "Microsoft",
|
|
54
|
+
color: "#00A4EF",
|
|
55
|
+
address: "0xe93237C50D904957Cf27E7B1133b510C669c2e74",
|
|
56
|
+
fee: 3000,
|
|
57
|
+
tickSpacing: 60,
|
|
58
|
+
},
|
|
59
|
+
TSLA: {
|
|
60
|
+
symbol: "TSLA",
|
|
61
|
+
name: "Tesla, Inc.",
|
|
62
|
+
label: "Tesla",
|
|
63
|
+
color: "#CC0000",
|
|
64
|
+
address: "0x322F0929c4625eD5bAd873c95208D54E1c003b2d",
|
|
65
|
+
fee: 3000,
|
|
66
|
+
tickSpacing: 60,
|
|
67
|
+
},
|
|
68
|
+
SPY: {
|
|
69
|
+
symbol: "SPY",
|
|
70
|
+
name: "SPDR S&P 500 ETF Trust",
|
|
71
|
+
label: "S&P 500 ETF",
|
|
72
|
+
color: "#7A60E5",
|
|
73
|
+
address: "0x117cc2133c37B721F49dE2A7a74833232B3B4C0C",
|
|
74
|
+
fee: 3000,
|
|
75
|
+
tickSpacing: 60,
|
|
76
|
+
etf: true,
|
|
77
|
+
},
|
|
78
|
+
AMZN: {
|
|
79
|
+
symbol: "AMZN",
|
|
80
|
+
name: "Amazon.com, Inc.",
|
|
81
|
+
label: "Amazon",
|
|
82
|
+
color: "#FF9900",
|
|
83
|
+
address: "0x12f190a9F9d7D37a250758b26824B97CE941bF54",
|
|
84
|
+
fee: 3000,
|
|
85
|
+
tickSpacing: 60,
|
|
86
|
+
},
|
|
87
|
+
GOOGL: {
|
|
88
|
+
symbol: "GOOGL",
|
|
89
|
+
name: "Alphabet Inc. Class A",
|
|
90
|
+
label: "Alphabet",
|
|
91
|
+
color: "#4285F4",
|
|
92
|
+
address: "0x2e0847E8910a9732eB3fb1bb4b70a580ADAD4FE3",
|
|
93
|
+
fee: 3000,
|
|
94
|
+
tickSpacing: 60,
|
|
95
|
+
},
|
|
96
|
+
META: {
|
|
97
|
+
symbol: "META",
|
|
98
|
+
name: "Meta Platforms, Inc.",
|
|
99
|
+
label: "Meta",
|
|
100
|
+
color: "#0081FB",
|
|
101
|
+
address: "0xc0D6457C16Cc70d6790Dd43521C899C87ce02f35",
|
|
102
|
+
fee: 3000,
|
|
103
|
+
tickSpacing: 60,
|
|
104
|
+
},
|
|
105
|
+
AMD: {
|
|
106
|
+
symbol: "AMD",
|
|
107
|
+
name: "Advanced Micro Devices",
|
|
108
|
+
label: "AMD",
|
|
109
|
+
color: "#ED1C24",
|
|
110
|
+
address: "0x86923f96303D656E4aa86D9d42D1e57ad2023fdC",
|
|
111
|
+
fee: 10_000,
|
|
112
|
+
tickSpacing: 200,
|
|
113
|
+
},
|
|
114
|
+
PLTR: {
|
|
115
|
+
symbol: "PLTR",
|
|
116
|
+
name: "Palantir Technologies",
|
|
117
|
+
label: "Palantir",
|
|
118
|
+
color: "#9CA3AF",
|
|
119
|
+
address: "0x894E1EC2D74FFE5AEF8Dc8A9e84686acCB964F2A",
|
|
120
|
+
fee: 10_000,
|
|
121
|
+
tickSpacing: 200,
|
|
122
|
+
},
|
|
123
|
+
ORCL: {
|
|
124
|
+
symbol: "ORCL",
|
|
125
|
+
name: "Oracle Corp.",
|
|
126
|
+
label: "Oracle",
|
|
127
|
+
color: "#F80000",
|
|
128
|
+
address: "0xb0992820E760d836549ba69BC7598b4af75dEE03",
|
|
129
|
+
fee: 10_000,
|
|
130
|
+
tickSpacing: 200,
|
|
131
|
+
},
|
|
132
|
+
MU: {
|
|
133
|
+
symbol: "MU",
|
|
134
|
+
name: "Micron Technology",
|
|
135
|
+
label: "Micron",
|
|
136
|
+
color: "#2D8CFF",
|
|
137
|
+
address: "0xfF080c8ce2E5feadaCa0Da81314Ae59D232d4afD",
|
|
138
|
+
fee: 10_000,
|
|
139
|
+
tickSpacing: 200,
|
|
140
|
+
},
|
|
141
|
+
SNDK: {
|
|
142
|
+
symbol: "SNDK",
|
|
143
|
+
name: "Sandisk Corp.",
|
|
144
|
+
label: "Sandisk",
|
|
145
|
+
color: "#E11D48",
|
|
146
|
+
address: "0xB90A19fF0Af67f7779afF50A882A9CfF42446400",
|
|
147
|
+
fee: 10_000,
|
|
148
|
+
tickSpacing: 200,
|
|
149
|
+
},
|
|
150
|
+
SPCX: {
|
|
151
|
+
symbol: "SPCX",
|
|
152
|
+
name: "Space Exploration Technologies",
|
|
153
|
+
label: "SpaceX",
|
|
154
|
+
color: "#C4C4C4",
|
|
155
|
+
address: "0x4a0E65A3EcceC6dBe60AE065F2e7bb85Fae35eEa",
|
|
156
|
+
fee: 10_000,
|
|
157
|
+
tickSpacing: 200,
|
|
158
|
+
},
|
|
159
|
+
};
|
|
160
|
+
export const TRADABLE_STOCK_SYMBOLS = Object.keys(STOCK_POOLS);
|
|
161
|
+
export function isTradableStock(symbol) {
|
|
162
|
+
return symbol in STOCK_POOLS;
|
|
163
|
+
}
|
|
164
|
+
/** Pool fee as a percentage string, e.g. "0.3%". */
|
|
165
|
+
export function poolFeeLabel(pool) {
|
|
166
|
+
return `${pool.fee / 10_000}%`;
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* `GroupCashSwapWrapperV4` on Robinhood Chain mainnet — the contract every
|
|
170
|
+
* swap policy is activated against and every swap calls. Live since July
|
|
171
|
+
* 2026 (GroupCash). Deliberately NOT read from NEXT_PUBLIC_STOCK_SWAP_WRAPPER:
|
|
172
|
+
* the Vercel rows for that variable predate mainnet and point at a retired
|
|
173
|
+
* testnet mock with no code on 4663, so honoring them would pin new swap
|
|
174
|
+
* policies to a dead address.
|
|
175
|
+
*/
|
|
176
|
+
export const STOCK_SWAP_WRAPPER_MAINNET = "0xf21C3C4Cb339bA038c0B374b55F47Fa22ce4d419";
|
|
177
|
+
export function stockSwapWrapperAddress() {
|
|
178
|
+
return STOCK_SWAP_WRAPPER_MAINNET;
|
|
179
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { type Hex } from "viem";
|
|
2
|
+
import type { EvmSubmitter, EvmWallet } from "./chain.ts";
|
|
3
|
+
import type { EvmDeployment } from "./deployment.ts";
|
|
4
|
+
/**
|
|
5
|
+
* The two submitters the package ships, both over a wallet the consumer
|
|
6
|
+
* gives it (a viem `WalletClient` with its account). Sponsoring through a
|
|
7
|
+
* gas manager is another `EvmSubmitter` the consumer writes.
|
|
8
|
+
*/
|
|
9
|
+
/** A call the chain refused: `reason` names the contract's error, `data` is the revert data. */
|
|
10
|
+
export declare class CallRevertedError extends Error {
|
|
11
|
+
readonly reason: string;
|
|
12
|
+
readonly data: Hex;
|
|
13
|
+
constructor(reason: string, data: Hex, options?: {
|
|
14
|
+
cause?: unknown;
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
/** The revert data an error carries anywhere in its causes: a viem error's, a node's, an ethers error's. */
|
|
18
|
+
export declare function revertDataOf(error: unknown): Hex | null;
|
|
19
|
+
/**
|
|
20
|
+
* The wallet sends the call itself and pays its gas. The policies helper and
|
|
21
|
+
* the signature-only users calls accept any sender; a vault accepts any
|
|
22
|
+
* active member of its cofund (or a Cofund admin); `createClient` and
|
|
23
|
+
* `addUser` refuse it unless the wallet is a Cofund admin.
|
|
24
|
+
*/
|
|
25
|
+
export declare function directSubmitter(wallet: EvmWallet): EvmSubmitter;
|
|
26
|
+
/**
|
|
27
|
+
* The call wrapped in `sponsorGuard.relay(target, data)` and sent by the
|
|
28
|
+
* wallet, which the deployment's guard must accept as a sponsor. The guard
|
|
29
|
+
* reaches the users and policies helpers and the cofunds' vaults, and is the
|
|
30
|
+
* sender `createClient` and `addUser` accept from the platform.
|
|
31
|
+
*/
|
|
32
|
+
export declare function guardSubmitter(wallet: EvmWallet, deployment: Pick<EvmDeployment, "sponsorGuard" | "chainId">): EvmSubmitter;
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { encodeFunctionData } from "viem";
|
|
2
|
+
import { policiesAbi } from "./abis/policies.js";
|
|
3
|
+
import { sponsorGuardAbi } from "./abis/sponsor-guard.js";
|
|
4
|
+
import { stateAbi } from "./abis/state.js";
|
|
5
|
+
import { usersAbi } from "./abis/users.js";
|
|
6
|
+
import { vaultAbi } from "./abis/vault.js";
|
|
7
|
+
import { revertReason } from "./simulate/revert.js";
|
|
8
|
+
/**
|
|
9
|
+
* The two submitters the package ships, both over a wallet the consumer
|
|
10
|
+
* gives it (a viem `WalletClient` with its account). Sponsoring through a
|
|
11
|
+
* gas manager is another `EvmSubmitter` the consumer writes.
|
|
12
|
+
*/
|
|
13
|
+
/** A call the chain refused: `reason` names the contract's error, `data` is the revert data. */
|
|
14
|
+
export class CallRevertedError extends Error {
|
|
15
|
+
reason;
|
|
16
|
+
data;
|
|
17
|
+
constructor(reason, data, options) {
|
|
18
|
+
super(`The call reverts: ${reason}`, options);
|
|
19
|
+
this.name = "CallRevertedError";
|
|
20
|
+
this.reason = reason;
|
|
21
|
+
this.data = data;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
const PROTOCOL_ERRORS = [
|
|
25
|
+
usersAbi,
|
|
26
|
+
policiesAbi,
|
|
27
|
+
vaultAbi,
|
|
28
|
+
sponsorGuardAbi,
|
|
29
|
+
stateAbi,
|
|
30
|
+
];
|
|
31
|
+
const HEX = /^0x(?:[0-9a-fA-F]{2})*$/;
|
|
32
|
+
/** The revert data an error carries anywhere in its causes: a viem error's, a node's, an ethers error's. */
|
|
33
|
+
export function revertDataOf(error) {
|
|
34
|
+
const seen = new Set();
|
|
35
|
+
let current = error;
|
|
36
|
+
while (current && typeof current === "object" && !seen.has(current)) {
|
|
37
|
+
seen.add(current);
|
|
38
|
+
const { data, cause, error: inner, } = current;
|
|
39
|
+
if (typeof data === "string" && HEX.test(data)) {
|
|
40
|
+
return data;
|
|
41
|
+
}
|
|
42
|
+
const nested = data?.data;
|
|
43
|
+
if (typeof nested === "string" && HEX.test(nested)) {
|
|
44
|
+
return nested;
|
|
45
|
+
}
|
|
46
|
+
current = cause ?? inner;
|
|
47
|
+
}
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
/** `wallet` sending `call`; a revert is a `CallRevertedError` naming the contract's error. */
|
|
51
|
+
async function send(wallet, call) {
|
|
52
|
+
const account = wallet.account;
|
|
53
|
+
if (!account) {
|
|
54
|
+
throw new Error("The wallet client has no account to send from");
|
|
55
|
+
}
|
|
56
|
+
try {
|
|
57
|
+
return await wallet.sendTransaction({
|
|
58
|
+
account,
|
|
59
|
+
chain: wallet.chain ?? null,
|
|
60
|
+
to: call.to,
|
|
61
|
+
data: call.data,
|
|
62
|
+
...(call.value !== undefined && { value: call.value }),
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
catch (error) {
|
|
66
|
+
const data = revertDataOf(error);
|
|
67
|
+
if (data === null) {
|
|
68
|
+
throw error;
|
|
69
|
+
}
|
|
70
|
+
throw new CallRevertedError(revertReason(data, { errors: PROTOCOL_ERRORS }), data, { cause: error });
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* The wallet sends the call itself and pays its gas. The policies helper and
|
|
75
|
+
* the signature-only users calls accept any sender; a vault accepts any
|
|
76
|
+
* active member of its cofund (or a Cofund admin); `createClient` and
|
|
77
|
+
* `addUser` refuse it unless the wallet is a Cofund admin.
|
|
78
|
+
*/
|
|
79
|
+
export function directSubmitter(wallet) {
|
|
80
|
+
return { send: (call) => send(wallet, call) };
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* The call wrapped in `sponsorGuard.relay(target, data)` and sent by the
|
|
84
|
+
* wallet, which the deployment's guard must accept as a sponsor. The guard
|
|
85
|
+
* reaches the users and policies helpers and the cofunds' vaults, and is the
|
|
86
|
+
* sender `createClient` and `addUser` accept from the platform.
|
|
87
|
+
*/
|
|
88
|
+
export function guardSubmitter(wallet, deployment) {
|
|
89
|
+
return {
|
|
90
|
+
send(call) {
|
|
91
|
+
if (wallet.chain && wallet.chain.id !== deployment.chainId) {
|
|
92
|
+
throw new Error(`The wallet is on chain ${wallet.chain.id}, the deployment on ${deployment.chainId}`);
|
|
93
|
+
}
|
|
94
|
+
return send(wallet, {
|
|
95
|
+
to: deployment.sponsorGuard,
|
|
96
|
+
data: encodeFunctionData({
|
|
97
|
+
abi: sponsorGuardAbi,
|
|
98
|
+
functionName: "relay",
|
|
99
|
+
args: [call.to, call.data],
|
|
100
|
+
}),
|
|
101
|
+
...(call.value !== undefined && { value: call.value }),
|
|
102
|
+
});
|
|
103
|
+
},
|
|
104
|
+
};
|
|
105
|
+
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What the protocol's EVM values look like, as zod schemas: addresses, hex,
|
|
3
|
+
* base units, chain names, and a call's effect as a simulation or its own
|
|
4
|
+
* logs show it. Every type is inferred from them.
|
|
5
|
+
*/
|
|
6
|
+
import { z } from "zod";
|
|
7
|
+
/** An EVM address in any casing. */
|
|
8
|
+
export declare const Address: z.ZodString;
|
|
9
|
+
export declare const Bytes32: z.ZodString;
|
|
10
|
+
/** 0x and whole bytes. */
|
|
11
|
+
export declare const Hex: z.ZodString;
|
|
12
|
+
export declare const BaseUnits: z.ZodString;
|
|
13
|
+
export declare const PositiveBaseUnits: z.ZodString;
|
|
14
|
+
/** Signed base units: a gain (positive) or a loss (negative); |x| fits a uint256. */
|
|
15
|
+
export declare const SignedBaseUnits: z.ZodString;
|
|
16
|
+
/** A token's decimals, as an ERC-20 reports them. */
|
|
17
|
+
export declare const TokenDecimals: z.ZodNumber;
|
|
18
|
+
/** A token's ticker. */
|
|
19
|
+
export declare const TokenSymbol: z.ZodString;
|
|
20
|
+
export declare const IsoDateTime: z.ZodISODateTime;
|
|
21
|
+
/** A human-readable refusal or failure. */
|
|
22
|
+
export declare const ErrorMessage: z.ZodString;
|
|
23
|
+
export declare const ChainNameSchema: z.ZodEnum<{
|
|
24
|
+
BTC: "BTC";
|
|
25
|
+
ETHEREUM: "ETHEREUM";
|
|
26
|
+
BASE: "BASE";
|
|
27
|
+
ARBITRUM: "ARBITRUM";
|
|
28
|
+
ROBINHOOD: "ROBINHOOD";
|
|
29
|
+
}>;
|
|
30
|
+
export type ChainName = z.infer<typeof ChainNameSchema>;
|
|
31
|
+
export declare const EvmChainNameSchema: z.ZodEnum<{
|
|
32
|
+
ETHEREUM: "ETHEREUM";
|
|
33
|
+
BASE: "BASE";
|
|
34
|
+
ARBITRUM: "ARBITRUM";
|
|
35
|
+
ROBINHOOD: "ROBINHOOD";
|
|
36
|
+
}>;
|
|
37
|
+
export type EvmChainName = z.infer<typeof EvmChainNameSchema>;
|
|
38
|
+
/** One deployment: the chain id and that chain's State. */
|
|
39
|
+
export type ChainPin = {
|
|
40
|
+
chainId: number;
|
|
41
|
+
state: string;
|
|
42
|
+
};
|
|
43
|
+
export type ChainPins = Partial<Record<EvmChainName, ChainPin>>;
|
|
44
|
+
/** One holder's net change in one asset: the vault, the call's wrapper, or anyone else. */
|
|
45
|
+
export declare const BalanceChangeSchema: z.ZodObject<{
|
|
46
|
+
holder: z.ZodString;
|
|
47
|
+
role: z.ZodEnum<{
|
|
48
|
+
vault: "vault";
|
|
49
|
+
wrapper: "wrapper";
|
|
50
|
+
other: "other";
|
|
51
|
+
}>;
|
|
52
|
+
asset: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
53
|
+
kind: z.ZodLiteral<"native">;
|
|
54
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
55
|
+
kind: z.ZodLiteral<"erc20">;
|
|
56
|
+
address: z.ZodString;
|
|
57
|
+
listed: z.ZodBoolean;
|
|
58
|
+
symbol: z.ZodNullable<z.ZodString>;
|
|
59
|
+
decimals: z.ZodNullable<z.ZodNumber>;
|
|
60
|
+
}, z.core.$strip>], "kind">;
|
|
61
|
+
delta: z.ZodString;
|
|
62
|
+
}, z.core.$strip>;
|
|
63
|
+
export type BalanceChange = z.infer<typeof BalanceChangeSchema>;
|
|
64
|
+
/**
|
|
65
|
+
* A call's effect as a simulation saw it: on success every balance it moves
|
|
66
|
+
* (`incomplete` when it also moved an NFT, or more balances than are read);
|
|
67
|
+
* on a revert, why; `unavailable` when the chain could not be read, or the
|
|
68
|
+
* simulation could not be trusted to be the call's.
|
|
69
|
+
*/
|
|
70
|
+
export declare const CallSimulationSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
71
|
+
outcome: z.ZodLiteral<"success">;
|
|
72
|
+
at: z.ZodISODateTime;
|
|
73
|
+
block: z.ZodObject<{
|
|
74
|
+
number: z.ZodString;
|
|
75
|
+
hash: z.ZodString;
|
|
76
|
+
}, z.core.$strip>;
|
|
77
|
+
gasUsed: z.ZodString;
|
|
78
|
+
changes: z.ZodArray<z.ZodObject<{
|
|
79
|
+
holder: z.ZodString;
|
|
80
|
+
role: z.ZodEnum<{
|
|
81
|
+
vault: "vault";
|
|
82
|
+
wrapper: "wrapper";
|
|
83
|
+
other: "other";
|
|
84
|
+
}>;
|
|
85
|
+
asset: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
86
|
+
kind: z.ZodLiteral<"native">;
|
|
87
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
88
|
+
kind: z.ZodLiteral<"erc20">;
|
|
89
|
+
address: z.ZodString;
|
|
90
|
+
listed: z.ZodBoolean;
|
|
91
|
+
symbol: z.ZodNullable<z.ZodString>;
|
|
92
|
+
decimals: z.ZodNullable<z.ZodNumber>;
|
|
93
|
+
}, z.core.$strip>], "kind">;
|
|
94
|
+
delta: z.ZodString;
|
|
95
|
+
}, z.core.$strip>>;
|
|
96
|
+
incomplete: z.ZodBoolean;
|
|
97
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
98
|
+
outcome: z.ZodLiteral<"revert">;
|
|
99
|
+
at: z.ZodISODateTime;
|
|
100
|
+
block: z.ZodObject<{
|
|
101
|
+
number: z.ZodString;
|
|
102
|
+
hash: z.ZodString;
|
|
103
|
+
}, z.core.$strip>;
|
|
104
|
+
reason: z.ZodString;
|
|
105
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
106
|
+
outcome: z.ZodLiteral<"unavailable">;
|
|
107
|
+
at: z.ZodISODateTime;
|
|
108
|
+
reason: z.ZodString;
|
|
109
|
+
}, z.core.$strip>], "outcome">;
|
|
110
|
+
export type CallSimulation = z.infer<typeof CallSimulationSchema>;
|
|
111
|
+
/**
|
|
112
|
+
* What an executed call did, summed from its own logs: ERC-20 transfers and
|
|
113
|
+
* WETH-style deposits and withdrawals (the native coin moves without a log).
|
|
114
|
+
*/
|
|
115
|
+
export declare const CallEffectSchema: z.ZodObject<{
|
|
116
|
+
at: z.ZodISODateTime;
|
|
117
|
+
block: z.ZodObject<{
|
|
118
|
+
number: z.ZodString;
|
|
119
|
+
hash: z.ZodString;
|
|
120
|
+
}, z.core.$strip>;
|
|
121
|
+
hash: z.ZodString;
|
|
122
|
+
changes: z.ZodArray<z.ZodObject<{
|
|
123
|
+
holder: z.ZodString;
|
|
124
|
+
role: z.ZodEnum<{
|
|
125
|
+
vault: "vault";
|
|
126
|
+
wrapper: "wrapper";
|
|
127
|
+
other: "other";
|
|
128
|
+
}>;
|
|
129
|
+
asset: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
130
|
+
kind: z.ZodLiteral<"native">;
|
|
131
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
132
|
+
kind: z.ZodLiteral<"erc20">;
|
|
133
|
+
address: z.ZodString;
|
|
134
|
+
listed: z.ZodBoolean;
|
|
135
|
+
symbol: z.ZodNullable<z.ZodString>;
|
|
136
|
+
decimals: z.ZodNullable<z.ZodNumber>;
|
|
137
|
+
}, z.core.$strip>], "kind">;
|
|
138
|
+
delta: z.ZodString;
|
|
139
|
+
}, z.core.$strip>>;
|
|
140
|
+
incomplete: z.ZodBoolean;
|
|
141
|
+
}, z.core.$strip>;
|
|
142
|
+
export type CallEffect = z.infer<typeof CallEffectSchema>;
|
|
143
|
+
/** What every surface showing a call's simulated effect says after it, before the allowance bound. */
|
|
144
|
+
export declare const SIMULATION_CAVEAT = "Simulated against the chain at that block. The real result can differ: prices and balances change, and a contract can behave differently when it runs for real.";
|
|
145
|
+
/** Added after the caveat on a call to an unlisted contract. */
|
|
146
|
+
export declare const UNLISTED_SIMULATION_WARNING = "Cofund hasn't reviewed this contract; it can show one result here and do another on execution.";
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What the protocol's EVM values look like, as zod schemas: addresses, hex,
|
|
3
|
+
* base units, chain names, and a call's effect as a simulation or its own
|
|
4
|
+
* logs show it. Every type is inferred from them.
|
|
5
|
+
*/
|
|
6
|
+
import { z } from "zod";
|
|
7
|
+
/** An EVM address in any casing. */
|
|
8
|
+
export const Address = z
|
|
9
|
+
.string()
|
|
10
|
+
.regex(/^0x[0-9a-fA-F]{40}$/, "an EVM address");
|
|
11
|
+
export const Bytes32 = z
|
|
12
|
+
.string()
|
|
13
|
+
.regex(/^0x[0-9a-fA-F]{64}$/, "32 bytes of hex");
|
|
14
|
+
/** 0x and whole bytes. */
|
|
15
|
+
export const Hex = z.string().regex(/^0x(?:[0-9a-fA-F]{2})*$/, "hex bytes");
|
|
16
|
+
const UINT256_MAX = 2n ** 256n - 1n;
|
|
17
|
+
/**
|
|
18
|
+
* A uint256: 78 digits at most, checked before it is parsed (the format check
|
|
19
|
+
* aborts, so `BigInt` never sees anything but digits).
|
|
20
|
+
*/
|
|
21
|
+
const fitsUint256 = (units) => BigInt(units) <= UINT256_MAX;
|
|
22
|
+
const MINUS = /^-/;
|
|
23
|
+
export const BaseUnits = z
|
|
24
|
+
.string()
|
|
25
|
+
.regex(/^(?:0|[1-9]\d{0,77})$/, {
|
|
26
|
+
message: "base units, a non-negative integer",
|
|
27
|
+
abort: true,
|
|
28
|
+
})
|
|
29
|
+
.refine(fitsUint256, "more than a uint256 holds");
|
|
30
|
+
export const PositiveBaseUnits = z
|
|
31
|
+
.string()
|
|
32
|
+
.regex(/^[1-9]\d{0,77}$/, {
|
|
33
|
+
message: "base units, a positive integer",
|
|
34
|
+
abort: true,
|
|
35
|
+
})
|
|
36
|
+
.refine(fitsUint256, "more than a uint256 holds");
|
|
37
|
+
/** Signed base units: a gain (positive) or a loss (negative); |x| fits a uint256. */
|
|
38
|
+
export const SignedBaseUnits = z
|
|
39
|
+
.string()
|
|
40
|
+
.regex(/^(?:0|-?[1-9]\d{0,77})$/, {
|
|
41
|
+
message: "signed base units",
|
|
42
|
+
abort: true,
|
|
43
|
+
})
|
|
44
|
+
.refine((units) => fitsUint256(units.replace(MINUS, "")), "more than a uint256 holds");
|
|
45
|
+
/** A token's decimals, as an ERC-20 reports them. */
|
|
46
|
+
export const TokenDecimals = z.number().int().min(0).max(36);
|
|
47
|
+
/** A token's ticker. */
|
|
48
|
+
export const TokenSymbol = z
|
|
49
|
+
.string()
|
|
50
|
+
.regex(/^[A-Za-z0-9.$_-]{1,32}$/, "a token symbol");
|
|
51
|
+
export const IsoDateTime = z.iso.datetime();
|
|
52
|
+
/** A human-readable refusal or failure. */
|
|
53
|
+
export const ErrorMessage = z.string().min(1);
|
|
54
|
+
export const ChainNameSchema = z.enum([
|
|
55
|
+
"ETHEREUM",
|
|
56
|
+
"BASE",
|
|
57
|
+
"ARBITRUM",
|
|
58
|
+
"ROBINHOOD",
|
|
59
|
+
"BTC",
|
|
60
|
+
]);
|
|
61
|
+
export const EvmChainNameSchema = ChainNameSchema.exclude(["BTC"]);
|
|
62
|
+
/**
|
|
63
|
+
* A balance a call moves: the native coin, or an ERC-20. A listed token's
|
|
64
|
+
* symbol and decimals are Cofund's token table's; any other's are the token's
|
|
65
|
+
* own claim (null when it is not a plain symbol, or the read failed).
|
|
66
|
+
*/
|
|
67
|
+
const SimulatedAsset = z.discriminatedUnion("kind", [
|
|
68
|
+
z.object({ kind: z.literal("native") }),
|
|
69
|
+
z.object({
|
|
70
|
+
kind: z.literal("erc20"),
|
|
71
|
+
address: Address,
|
|
72
|
+
listed: z.boolean(),
|
|
73
|
+
symbol: TokenSymbol.nullable(),
|
|
74
|
+
decimals: TokenDecimals.nullable(),
|
|
75
|
+
}),
|
|
76
|
+
]);
|
|
77
|
+
/** One holder's net change in one asset: the vault, the call's wrapper, or anyone else. */
|
|
78
|
+
export const BalanceChangeSchema = z.object({
|
|
79
|
+
holder: Address,
|
|
80
|
+
role: z.enum(["vault", "wrapper", "other"]),
|
|
81
|
+
asset: SimulatedAsset,
|
|
82
|
+
delta: SignedBaseUnits,
|
|
83
|
+
});
|
|
84
|
+
/** The block a simulation ran on top of: the chain's state at its end. */
|
|
85
|
+
const SimulatedBlock = z.object({ number: BaseUnits, hash: Bytes32 });
|
|
86
|
+
/**
|
|
87
|
+
* A call's effect as a simulation saw it: on success every balance it moves
|
|
88
|
+
* (`incomplete` when it also moved an NFT, or more balances than are read);
|
|
89
|
+
* on a revert, why; `unavailable` when the chain could not be read, or the
|
|
90
|
+
* simulation could not be trusted to be the call's.
|
|
91
|
+
*/
|
|
92
|
+
export const CallSimulationSchema = z.discriminatedUnion("outcome", [
|
|
93
|
+
z.object({
|
|
94
|
+
outcome: z.literal("success"),
|
|
95
|
+
at: IsoDateTime,
|
|
96
|
+
block: SimulatedBlock,
|
|
97
|
+
gasUsed: BaseUnits,
|
|
98
|
+
changes: z.array(BalanceChangeSchema).max(100),
|
|
99
|
+
incomplete: z.boolean(),
|
|
100
|
+
}),
|
|
101
|
+
z.object({
|
|
102
|
+
outcome: z.literal("revert"),
|
|
103
|
+
at: IsoDateTime,
|
|
104
|
+
block: SimulatedBlock,
|
|
105
|
+
reason: ErrorMessage,
|
|
106
|
+
}),
|
|
107
|
+
z.object({
|
|
108
|
+
outcome: z.literal("unavailable"),
|
|
109
|
+
at: IsoDateTime,
|
|
110
|
+
reason: ErrorMessage,
|
|
111
|
+
}),
|
|
112
|
+
]);
|
|
113
|
+
/**
|
|
114
|
+
* What an executed call did, summed from its own logs: ERC-20 transfers and
|
|
115
|
+
* WETH-style deposits and withdrawals (the native coin moves without a log).
|
|
116
|
+
*/
|
|
117
|
+
export const CallEffectSchema = z.object({
|
|
118
|
+
at: IsoDateTime,
|
|
119
|
+
block: SimulatedBlock,
|
|
120
|
+
/** The transaction that carried it; a sponsored operation's is its bundle. */
|
|
121
|
+
hash: Bytes32,
|
|
122
|
+
changes: z.array(BalanceChangeSchema).max(100),
|
|
123
|
+
incomplete: z.boolean(),
|
|
124
|
+
});
|
|
125
|
+
/** What every surface showing a call's simulated effect says after it, before the allowance bound. */
|
|
126
|
+
export const SIMULATION_CAVEAT = "Simulated against the chain at that block. The real result can differ: prices and balances change, and a contract can behave differently when it runs for real.";
|
|
127
|
+
/** Added after the caveat on a call to an unlisted contract. */
|
|
128
|
+
export const UNLISTED_SIMULATION_WARNING = "Cofund hasn't reviewed this contract; it can show one result here and do another on execution.";
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@getcofund/protocol/keys`: a member's keys, the signers derived from them,
|
|
3
|
+
* and the proof that a BTC xpub is the member's.
|
|
4
|
+
*/
|
|
5
|
+
// biome-ignore lint/performance/noBarrelFile: the package's `./keys` entry
|
|
6
|
+
export * from "./keys.js";
|
|
7
|
+
export * from "./xpub-ownership.js";
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { type PrivateKeyAccount } from "viem/accounts";
|
|
2
|
+
export type BtcNetwork = "mainnet" | "testnet";
|
|
3
|
+
/** A Bitcoin vault as verified when it was pinned: its address and the hash of its definition. */
|
|
4
|
+
export type BtcVaultPin = {
|
|
5
|
+
address: string;
|
|
6
|
+
definitionHash: string;
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* A member's signing keys, generated and kept on their machine. Cofund never
|
|
10
|
+
* sees them: the API receives addresses, xpubs and signatures only.
|
|
11
|
+
*
|
|
12
|
+
* Plain JSON written with mode 0600. `cofunds` pins, per cofund, the
|
|
13
|
+
* `company` string its payloads must carry, recorded when its invite was
|
|
14
|
+
* accepted.
|
|
15
|
+
*/
|
|
16
|
+
export type Keystore = {
|
|
17
|
+
version: 1;
|
|
18
|
+
evm: {
|
|
19
|
+
privateKey: string;
|
|
20
|
+
address: string;
|
|
21
|
+
};
|
|
22
|
+
btc: {
|
|
23
|
+
network: BtcNetwork;
|
|
24
|
+
mnemonic: string;
|
|
25
|
+
accountXpub: string;
|
|
26
|
+
};
|
|
27
|
+
cofunds: Record<string, {
|
|
28
|
+
company: string;
|
|
29
|
+
}>;
|
|
30
|
+
/** Bitcoin vaults verified and pinned by id (`pinVault`); absent in a keystore from before. */
|
|
31
|
+
vaults?: Record<string, BtcVaultPin>;
|
|
32
|
+
};
|
|
33
|
+
export declare function generateKeystore(network: BtcNetwork): Keystore;
|
|
34
|
+
/** The EVM key as a viem account: an EIP-712 signer (`TypedDataSigner`) and a wallet client's account. */
|
|
35
|
+
export declare function evmWallet(keystore: Keystore): PrivateKeyAccount;
|
|
36
|
+
/**
|
|
37
|
+
* The xpub and its ownership proof: a BIP-340 signature by the account's /0/0
|
|
38
|
+
* key — the leaf vaults use — over the challenge bound to `userId` and the xpub.
|
|
39
|
+
*/
|
|
40
|
+
export declare function btcOwnershipProof(keystore: Keystore, userId: string): {
|
|
41
|
+
xpub: string;
|
|
42
|
+
proof: string;
|
|
43
|
+
};
|
|
44
|
+
/** Signs a message as a Bitcoin Signed Message; returns the compact signature, hex. */
|
|
45
|
+
export type BtcMessageSigner = {
|
|
46
|
+
signMessage(message: string): Promise<string>;
|
|
47
|
+
};
|
|
48
|
+
/** The BTC key's Cofund leaf as a message signer: what a BTC policy approval is. */
|
|
49
|
+
export declare function btcMessageSigner(keystore: Keystore): BtcMessageSigner;
|
|
50
|
+
/**
|
|
51
|
+
* BIP-340 signatures by the Cofund leaf key at a tapleaf's derivation branch:
|
|
52
|
+
* what a BTC transfer's partials are. A keystore, an HSM or a remote signer.
|
|
53
|
+
* (A PSBT is signed through `./btc`'s `BtcLeafSigner`.)
|
|
54
|
+
*/
|
|
55
|
+
export type BtcLeafKeySigner = {
|
|
56
|
+
network: BtcNetwork;
|
|
57
|
+
/** The x-only public key at `branch`. */
|
|
58
|
+
leafKey(branch: number): Uint8Array;
|
|
59
|
+
/** A BIP-340 signature over a 32-byte sighash. */
|
|
60
|
+
signDigest(branch: number, digest: Uint8Array): Uint8Array;
|
|
61
|
+
};
|
|
62
|
+
/** The keystore's BTC key as a tapleaf signer; the seed is derived once, here. */
|
|
63
|
+
export declare function btcLeafSigner(keystore: Keystore): BtcLeafKeySigner;
|