recon-crypto-mcp 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 +153 -0
- package/dist/abis/aave-pool.d.ts +174 -0
- package/dist/abis/aave-pool.js +101 -0
- package/dist/abis/aave-pool.js.map +1 -0
- package/dist/abis/aave-ui-pool-data-provider.d.ts +232 -0
- package/dist/abis/aave-ui-pool-data-provider.js +107 -0
- package/dist/abis/aave-ui-pool-data-provider.js.map +1 -0
- package/dist/abis/access-control.d.ts +94 -0
- package/dist/abis/access-control.js +51 -0
- package/dist/abis/access-control.js.map +1 -0
- package/dist/abis/compound-comet.d.ts +120 -0
- package/dist/abis/compound-comet.js +84 -0
- package/dist/abis/compound-comet.js.map +1 -0
- package/dist/abis/eigenlayer-delegation-manager.d.ts +23 -0
- package/dist/abis/eigenlayer-delegation-manager.js +18 -0
- package/dist/abis/eigenlayer-delegation-manager.js.map +1 -0
- package/dist/abis/eigenlayer-strategy-manager.d.ts +53 -0
- package/dist/abis/eigenlayer-strategy-manager.js +47 -0
- package/dist/abis/eigenlayer-strategy-manager.js.map +1 -0
- package/dist/abis/erc20.d.ts +78 -0
- package/dist/abis/erc20.js +10 -0
- package/dist/abis/erc20.js.map +1 -0
- package/dist/abis/lido.d.ts +80 -0
- package/dist/abis/lido.js +60 -0
- package/dist/abis/lido.js.map +1 -0
- package/dist/abis/morpho-blue.d.ts +321 -0
- package/dist/abis/morpho-blue.js +193 -0
- package/dist/abis/morpho-blue.js.map +1 -0
- package/dist/abis/uniswap-pool.d.ts +70 -0
- package/dist/abis/uniswap-pool.js +53 -0
- package/dist/abis/uniswap-pool.js.map +1 -0
- package/dist/abis/uniswap-position-manager.d.ts +71 -0
- package/dist/abis/uniswap-position-manager.js +41 -0
- package/dist/abis/uniswap-position-manager.js.map +1 -0
- package/dist/config/cache.d.ts +12 -0
- package/dist/config/cache.js +12 -0
- package/dist/config/cache.js.map +1 -0
- package/dist/config/chains.d.ts +24 -0
- package/dist/config/chains.js +158 -0
- package/dist/config/chains.js.map +1 -0
- package/dist/config/contracts.d.ts +107 -0
- package/dist/config/contracts.js +123 -0
- package/dist/config/contracts.js.map +1 -0
- package/dist/config/user-config.d.ts +15 -0
- package/dist/config/user-config.js +93 -0
- package/dist/config/user-config.js.map +1 -0
- package/dist/data/apis/etherscan.d.ts +30 -0
- package/dist/data/apis/etherscan.js +109 -0
- package/dist/data/apis/etherscan.js.map +1 -0
- package/dist/data/cache.d.ts +18 -0
- package/dist/data/cache.js +47 -0
- package/dist/data/cache.js.map +1 -0
- package/dist/data/format.d.ts +6 -0
- package/dist/data/format.js +44 -0
- package/dist/data/format.js.map +1 -0
- package/dist/data/prices.d.ts +12 -0
- package/dist/data/prices.js +81 -0
- package/dist/data/prices.js.map +1 -0
- package/dist/data/rpc.d.ts +17 -0
- package/dist/data/rpc.js +68 -0
- package/dist/data/rpc.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +344 -0
- package/dist/index.js.map +1 -0
- package/dist/modules/balances/index.d.ts +20 -0
- package/dist/modules/balances/index.js +49 -0
- package/dist/modules/balances/index.js.map +1 -0
- package/dist/modules/balances/schemas.d.ts +32 -0
- package/dist/modules/balances/schemas.js +21 -0
- package/dist/modules/balances/schemas.js.map +1 -0
- package/dist/modules/bitcoin/index.d.ts +26 -0
- package/dist/modules/bitcoin/index.js +128 -0
- package/dist/modules/bitcoin/index.js.map +1 -0
- package/dist/modules/bitcoin/schemas.d.ts +49 -0
- package/dist/modules/bitcoin/schemas.js +51 -0
- package/dist/modules/bitcoin/schemas.js.map +1 -0
- package/dist/modules/bitcoin/send.d.ts +52 -0
- package/dist/modules/bitcoin/send.js +158 -0
- package/dist/modules/bitcoin/send.js.map +1 -0
- package/dist/modules/bitcoin/utxo.d.ts +99 -0
- package/dist/modules/bitcoin/utxo.js +116 -0
- package/dist/modules/bitcoin/utxo.js.map +1 -0
- package/dist/modules/compound/actions.d.ts +8 -0
- package/dist/modules/compound/actions.js +154 -0
- package/dist/modules/compound/actions.js.map +1 -0
- package/dist/modules/compound/index.d.ts +26 -0
- package/dist/modules/compound/index.js +141 -0
- package/dist/modules/compound/index.js.map +1 -0
- package/dist/modules/compound/schemas.d.ts +95 -0
- package/dist/modules/compound/schemas.js +37 -0
- package/dist/modules/compound/schemas.js.map +1 -0
- package/dist/modules/execution/index.d.ts +51 -0
- package/dist/modules/execution/index.js +271 -0
- package/dist/modules/execution/index.js.map +1 -0
- package/dist/modules/execution/schemas.d.ts +183 -0
- package/dist/modules/execution/schemas.js +88 -0
- package/dist/modules/execution/schemas.js.map +1 -0
- package/dist/modules/feedback/index.d.ts +28 -0
- package/dist/modules/feedback/index.js +161 -0
- package/dist/modules/feedback/index.js.map +1 -0
- package/dist/modules/feedback/rate-limit.d.ts +15 -0
- package/dist/modules/feedback/rate-limit.js +110 -0
- package/dist/modules/feedback/rate-limit.js.map +1 -0
- package/dist/modules/feedback/schemas.d.ts +41 -0
- package/dist/modules/feedback/schemas.js +40 -0
- package/dist/modules/feedback/schemas.js.map +1 -0
- package/dist/modules/morpho/actions.d.ts +8 -0
- package/dist/modules/morpho/actions.js +265 -0
- package/dist/modules/morpho/actions.js.map +1 -0
- package/dist/modules/morpho/index.d.ts +26 -0
- package/dist/modules/morpho/index.js +94 -0
- package/dist/modules/morpho/index.js.map +1 -0
- package/dist/modules/morpho/schemas.d.ts +130 -0
- package/dist/modules/morpho/schemas.js +34 -0
- package/dist/modules/morpho/schemas.js.map +1 -0
- package/dist/modules/portfolio/index.d.ts +3 -0
- package/dist/modules/portfolio/index.js +197 -0
- package/dist/modules/portfolio/index.js.map +1 -0
- package/dist/modules/portfolio/schemas.d.ts +20 -0
- package/dist/modules/portfolio/schemas.js +15 -0
- package/dist/modules/portfolio/schemas.js.map +1 -0
- package/dist/modules/positions/aave.d.ts +22 -0
- package/dist/modules/positions/aave.js +197 -0
- package/dist/modules/positions/aave.js.map +1 -0
- package/dist/modules/positions/actions.d.ts +18 -0
- package/dist/modules/positions/actions.js +205 -0
- package/dist/modules/positions/actions.js.map +1 -0
- package/dist/modules/positions/index.d.ts +37 -0
- package/dist/modules/positions/index.js +59 -0
- package/dist/modules/positions/index.js.map +1 -0
- package/dist/modules/positions/schemas.d.ts +55 -0
- package/dist/modules/positions/schemas.js +25 -0
- package/dist/modules/positions/schemas.js.map +1 -0
- package/dist/modules/positions/uniswap.d.ts +4 -0
- package/dist/modules/positions/uniswap.js +181 -0
- package/dist/modules/positions/uniswap.js.map +1 -0
- package/dist/modules/prices/index.d.ts +19 -0
- package/dist/modules/prices/index.js +22 -0
- package/dist/modules/prices/index.js.map +1 -0
- package/dist/modules/security/index.d.ts +26 -0
- package/dist/modules/security/index.js +13 -0
- package/dist/modules/security/index.js.map +1 -0
- package/dist/modules/security/permissions.d.ts +8 -0
- package/dist/modules/security/permissions.js +96 -0
- package/dist/modules/security/permissions.js.map +1 -0
- package/dist/modules/security/risk-score.d.ts +18 -0
- package/dist/modules/security/risk-score.js +116 -0
- package/dist/modules/security/risk-score.js.map +1 -0
- package/dist/modules/security/schemas.d.ts +31 -0
- package/dist/modules/security/schemas.js +16 -0
- package/dist/modules/security/schemas.js.map +1 -0
- package/dist/modules/security/verification.d.ts +4 -0
- package/dist/modules/security/verification.js +82 -0
- package/dist/modules/security/verification.js.map +1 -0
- package/dist/modules/shared/approval.d.ts +71 -0
- package/dist/modules/shared/approval.js +130 -0
- package/dist/modules/shared/approval.js.map +1 -0
- package/dist/modules/staking/actions.d.ts +22 -0
- package/dist/modules/staking/actions.js +100 -0
- package/dist/modules/staking/actions.js.map +1 -0
- package/dist/modules/staking/eigenlayer.d.ts +14 -0
- package/dist/modules/staking/eigenlayer.js +105 -0
- package/dist/modules/staking/eigenlayer.js.map +1 -0
- package/dist/modules/staking/index.d.ts +24 -0
- package/dist/modules/staking/index.js +59 -0
- package/dist/modules/staking/index.js.map +1 -0
- package/dist/modules/staking/lido.d.ts +14 -0
- package/dist/modules/staking/lido.js +113 -0
- package/dist/modules/staking/lido.js.map +1 -0
- package/dist/modules/staking/schemas.d.ts +34 -0
- package/dist/modules/staking/schemas.js +20 -0
- package/dist/modules/staking/schemas.js.map +1 -0
- package/dist/modules/swap/index.d.ts +47 -0
- package/dist/modules/swap/index.js +376 -0
- package/dist/modules/swap/index.js.map +1 -0
- package/dist/modules/swap/lifi.d.ts +17 -0
- package/dist/modules/swap/lifi.js +44 -0
- package/dist/modules/swap/lifi.js.map +1 -0
- package/dist/modules/swap/oneinch.d.ts +26 -0
- package/dist/modules/swap/oneinch.js +33 -0
- package/dist/modules/swap/oneinch.js.map +1 -0
- package/dist/modules/swap/schemas.d.ts +65 -0
- package/dist/modules/swap/schemas.js +46 -0
- package/dist/modules/swap/schemas.js.map +1 -0
- package/dist/setup.d.ts +2 -0
- package/dist/setup.js +257 -0
- package/dist/setup.js.map +1 -0
- package/dist/signing/pre-sign-check.d.ts +8 -0
- package/dist/signing/pre-sign-check.js +231 -0
- package/dist/signing/pre-sign-check.js.map +1 -0
- package/dist/signing/session.d.ts +28 -0
- package/dist/signing/session.js +26 -0
- package/dist/signing/session.js.map +1 -0
- package/dist/signing/tx-store.d.ts +29 -0
- package/dist/signing/tx-store.js +85 -0
- package/dist/signing/tx-store.js.map +1 -0
- package/dist/signing/walletconnect.d.ts +33 -0
- package/dist/signing/walletconnect.js +226 -0
- package/dist/signing/walletconnect.js.map +1 -0
- package/dist/types/index.d.ts +222 -0
- package/dist/types/index.js +18 -0
- package/dist/types/index.js.map +1 -0
- package/package.json +134 -0
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import { decodeFunctionData, toFunctionSelector } from "viem";
|
|
2
|
+
import { erc20Abi } from "../abis/erc20.js";
|
|
3
|
+
import { aavePoolAbi } from "../abis/aave-pool.js";
|
|
4
|
+
import { stETHAbi, lidoWithdrawalQueueAbi } from "../abis/lido.js";
|
|
5
|
+
import { eigenStrategyManagerAbi } from "../abis/eigenlayer-strategy-manager.js";
|
|
6
|
+
import { cometAbi } from "../abis/compound-comet.js";
|
|
7
|
+
import { morphoBlueAbi } from "../abis/morpho-blue.js";
|
|
8
|
+
import { uniswapPositionManagerAbi } from "../abis/uniswap-position-manager.js";
|
|
9
|
+
import { CONTRACTS } from "../config/contracts.js";
|
|
10
|
+
/**
|
|
11
|
+
* Returns the pinned Aave V3 Pool address for `chain`. We deliberately DO NOT
|
|
12
|
+
* resolve this via PoolAddressesProvider.getPool() at sign time: the pre-sign
|
|
13
|
+
* check is our defense against a hostile RPC, so it must not delegate a trust-
|
|
14
|
+
* root lookup to that same RPC. Pool addresses are frozen per chain since
|
|
15
|
+
* Aave V3 launched and have not rotated; see contracts.ts for the source.
|
|
16
|
+
*/
|
|
17
|
+
function pinnedAavePool(chain) {
|
|
18
|
+
return CONTRACTS[chain].aave.pool;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Independent pre-sign safety check. Runs in send_transaction AFTER the handle
|
|
22
|
+
* is redeemed and chain id is verified, immediately before the tx is handed to
|
|
23
|
+
* Ledger Live. The goal is a second line of defense against a compromised /
|
|
24
|
+
* prompt-injected agent: even if a prepare_* tool produced a misleading
|
|
25
|
+
* description, this check reasons about the raw calldata alone and refuses
|
|
26
|
+
* anything that doesn't match a known-safe shape.
|
|
27
|
+
*
|
|
28
|
+
* Threat model: the canonical prompt-injection attack against a wallet agent is
|
|
29
|
+
* convincing the model to sign an `approve(attacker, MAX)` or a direct
|
|
30
|
+
* `transfer(attacker, amount)` on some token. This check closes the approve
|
|
31
|
+
* vector outright (spender allowlist) and narrows the call-surface to
|
|
32
|
+
* contracts we've explicitly recognized.
|
|
33
|
+
*/
|
|
34
|
+
/** LiFi Diamond — deterministic address across all our chains. Stable since 2022. */
|
|
35
|
+
const LIFI_DIAMOND = "0x1231deb6f5749ef6ce6943a275a1d3e7486f4eae";
|
|
36
|
+
/** 4-byte selectors we treat as explicit allowlist entries. */
|
|
37
|
+
const SELECTOR = {
|
|
38
|
+
approve: toFunctionSelector("approve(address,uint256)").toLowerCase(),
|
|
39
|
+
transfer: toFunctionSelector("transfer(address,uint256)").toLowerCase(),
|
|
40
|
+
};
|
|
41
|
+
function computeSelectorsFromAbi(abi) {
|
|
42
|
+
const out = new Set();
|
|
43
|
+
for (const item of abi) {
|
|
44
|
+
if (item.type !== "function")
|
|
45
|
+
continue;
|
|
46
|
+
try {
|
|
47
|
+
out.add(toFunctionSelector(item).toLowerCase());
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
// Skip items that don't encode cleanly (shouldn't happen in our curated ABIs).
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return out;
|
|
54
|
+
}
|
|
55
|
+
const AAVE_SELECTORS = computeSelectorsFromAbi(aavePoolAbi);
|
|
56
|
+
const COMET_SELECTORS = computeSelectorsFromAbi(cometAbi);
|
|
57
|
+
const MORPHO_SELECTORS = computeSelectorsFromAbi(morphoBlueAbi);
|
|
58
|
+
const LIDO_STETH_SELECTORS = computeSelectorsFromAbi(stETHAbi);
|
|
59
|
+
const LIDO_QUEUE_SELECTORS = computeSelectorsFromAbi(lidoWithdrawalQueueAbi);
|
|
60
|
+
const EIGEN_SELECTORS = computeSelectorsFromAbi(eigenStrategyManagerAbi);
|
|
61
|
+
const UNISWAP_NPM_SELECTORS = computeSelectorsFromAbi(uniswapPositionManagerAbi);
|
|
62
|
+
const ERC20_SELECTORS = computeSelectorsFromAbi(erc20Abi);
|
|
63
|
+
async function classifyDestination(chain, to) {
|
|
64
|
+
const lo = to.toLowerCase();
|
|
65
|
+
// Aave V3 Pool — pinned from a hardcoded address, NOT a live RPC read.
|
|
66
|
+
const aavePool = pinnedAavePool(chain).toLowerCase();
|
|
67
|
+
if (lo === aavePool)
|
|
68
|
+
return { kind: "aave-v3-pool", allowedAbi: aavePoolAbi };
|
|
69
|
+
// Compound V3 Comet markets.
|
|
70
|
+
const compound = CONTRACTS[chain].compound;
|
|
71
|
+
if (compound) {
|
|
72
|
+
for (const addr of Object.values(compound)) {
|
|
73
|
+
if (lo === addr.toLowerCase()) {
|
|
74
|
+
return { kind: "compound-v3-comet", allowedAbi: cometAbi };
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
// Ethereum-only protocols.
|
|
79
|
+
if (chain === "ethereum") {
|
|
80
|
+
if (lo === CONTRACTS.ethereum.morpho.blue.toLowerCase()) {
|
|
81
|
+
return { kind: "morpho-blue", allowedAbi: morphoBlueAbi };
|
|
82
|
+
}
|
|
83
|
+
if (lo === CONTRACTS.ethereum.lido.stETH.toLowerCase()) {
|
|
84
|
+
return { kind: "lido-stETH", allowedAbi: stETHAbi };
|
|
85
|
+
}
|
|
86
|
+
if (lo === CONTRACTS.ethereum.lido.withdrawalQueue.toLowerCase()) {
|
|
87
|
+
return { kind: "lido-withdrawalQueue", allowedAbi: lidoWithdrawalQueueAbi };
|
|
88
|
+
}
|
|
89
|
+
if (lo === CONTRACTS.ethereum.eigenlayer.strategyManager.toLowerCase()) {
|
|
90
|
+
return { kind: "eigenlayer-strategyManager", allowedAbi: eigenStrategyManagerAbi };
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
// Uniswap V3 NonfungiblePositionManager (not currently written to by our prepare_*
|
|
94
|
+
// tools, but listed because it's in CONTRACTS and we may add LP mint/collect flows).
|
|
95
|
+
if (lo === CONTRACTS[chain].uniswap.positionManager.toLowerCase()) {
|
|
96
|
+
return { kind: "uniswap-v3-npm", allowedAbi: uniswapPositionManagerAbi };
|
|
97
|
+
}
|
|
98
|
+
// LiFi Diamond — accept but skip per-selector check (LiFi's ABI is huge and dynamic).
|
|
99
|
+
if (lo === LIFI_DIAMOND)
|
|
100
|
+
return { kind: "lifi-diamond", allowedAbi: null };
|
|
101
|
+
// Known ERC-20s (USDC, USDT, DAI, WETH, ...). Tokens ONLY — this path never
|
|
102
|
+
// covers a protocol contract that exposes transfer-like selectors, because
|
|
103
|
+
// the protocol branches above match first.
|
|
104
|
+
const tokens = CONTRACTS[chain].tokens;
|
|
105
|
+
if (tokens) {
|
|
106
|
+
for (const addr of Object.values(tokens)) {
|
|
107
|
+
if (lo === addr.toLowerCase())
|
|
108
|
+
return { kind: "known-erc20", allowedAbi: erc20Abi };
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
/** Spenders allowed for approve(spender, _). */
|
|
114
|
+
function buildSpenderAllowlist(chain) {
|
|
115
|
+
const out = new Set();
|
|
116
|
+
out.add(pinnedAavePool(chain).toLowerCase());
|
|
117
|
+
const compound = CONTRACTS[chain].compound;
|
|
118
|
+
if (compound)
|
|
119
|
+
for (const a of Object.values(compound))
|
|
120
|
+
out.add(a.toLowerCase());
|
|
121
|
+
if (chain === "ethereum") {
|
|
122
|
+
out.add(CONTRACTS.ethereum.morpho.blue.toLowerCase());
|
|
123
|
+
out.add(CONTRACTS.ethereum.lido.withdrawalQueue.toLowerCase());
|
|
124
|
+
out.add(CONTRACTS.ethereum.eigenlayer.strategyManager.toLowerCase());
|
|
125
|
+
}
|
|
126
|
+
out.add(CONTRACTS[chain].uniswap.positionManager.toLowerCase());
|
|
127
|
+
out.add(LIFI_DIAMOND);
|
|
128
|
+
return out;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Throws a descriptive error if `tx` looks unsafe to sign. Call synchronously
|
|
132
|
+
* before every WalletConnect submission. "Unsafe" is conservative: unknown
|
|
133
|
+
* destination + non-empty data, approves to non-allowlisted spenders, or
|
|
134
|
+
* selectors that don't belong to the contract we think we're calling.
|
|
135
|
+
*/
|
|
136
|
+
export async function assertTransactionSafe(tx) {
|
|
137
|
+
// 1) Pure native send — data must be empty. Allow the transfer; the user
|
|
138
|
+
// picks the recipient, and the Ledger screen shows it.
|
|
139
|
+
if (tx.data === "0x" || tx.data === "0x0" || tx.data === "0x00") {
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
if (tx.data.length < 10) {
|
|
143
|
+
throw new Error(`Pre-sign check: calldata (${tx.data}) is too short to carry a function selector. ` +
|
|
144
|
+
`Refusing to sign.`);
|
|
145
|
+
}
|
|
146
|
+
const selector = tx.data.slice(0, 10).toLowerCase();
|
|
147
|
+
const dest = await classifyDestination(tx.chain, tx.to);
|
|
148
|
+
// 2) approve(): the single highest-leverage attack vector. Spender MUST be on
|
|
149
|
+
// the protocol allowlist. Destination is whichever ERC-20 we're approving.
|
|
150
|
+
if (selector === SELECTOR.approve) {
|
|
151
|
+
if (!dest) {
|
|
152
|
+
throw new Error(`Pre-sign check: refusing approve() on ${tx.to} (${tx.chain}) — token is not in our ` +
|
|
153
|
+
`recognized set. If this is a legitimate token, add it to CONTRACTS[${tx.chain}].tokens.`);
|
|
154
|
+
}
|
|
155
|
+
// `to` must be a token (ERC-20 or a protocol token surface like stETH),
|
|
156
|
+
// not, say, the Aave Pool. approve() on the pool itself is nonsensical.
|
|
157
|
+
if (dest.kind !== "known-erc20" &&
|
|
158
|
+
dest.kind !== "lido-stETH" // stETH IS an ERC-20; approvals to spenders happen on it
|
|
159
|
+
) {
|
|
160
|
+
throw new Error(`Pre-sign check: refusing approve() on ${dest.kind} (${tx.to}) — approvals should ` +
|
|
161
|
+
`target ERC-20 tokens, not protocol contracts.`);
|
|
162
|
+
}
|
|
163
|
+
let spender;
|
|
164
|
+
try {
|
|
165
|
+
const decoded = decodeFunctionData({ abi: erc20Abi, data: tx.data });
|
|
166
|
+
spender = (decoded.args?.[0]).toLowerCase();
|
|
167
|
+
}
|
|
168
|
+
catch {
|
|
169
|
+
throw new Error(`Pre-sign check: could not decode approve() calldata on ${tx.to}. Refusing to sign.`);
|
|
170
|
+
}
|
|
171
|
+
const allowlist = buildSpenderAllowlist(tx.chain);
|
|
172
|
+
if (!allowlist.has(spender)) {
|
|
173
|
+
throw new Error(`Pre-sign check: refusing approve(spender=${spender}, ...) on ${tx.chain} — spender is ` +
|
|
174
|
+
`not in the protocol allowlist (Aave Pool, Compound Comet, Morpho Blue, Lido Queue, ` +
|
|
175
|
+
`EigenLayer, Uniswap NPM, LiFi Diamond). This is the canonical phishing/prompt-injection ` +
|
|
176
|
+
`pattern. If you need to approve a different spender, do it from the Ledger Live app directly.`);
|
|
177
|
+
}
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
// 3) transfer(): user-directed token move. Destination must still be a token
|
|
181
|
+
// we recognize (otherwise the agent is calling transfer() on an arbitrary
|
|
182
|
+
// contract with matching 4-byte — unlikely but worth rejecting).
|
|
183
|
+
if (selector === SELECTOR.transfer) {
|
|
184
|
+
if (!dest || (dest.kind !== "known-erc20" && dest.kind !== "lido-stETH")) {
|
|
185
|
+
throw new Error(`Pre-sign check: refusing transfer() on ${tx.to} (${tx.chain}) — token is not in our ` +
|
|
186
|
+
`recognized set. Add it to CONTRACTS[${tx.chain}].tokens if this is a legitimate asset.`);
|
|
187
|
+
}
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
// 4) Every other selector: must be a known protocol destination.
|
|
191
|
+
if (!dest) {
|
|
192
|
+
throw new Error(`Pre-sign check: refusing to sign against unknown contract ${tx.to} on ${tx.chain} ` +
|
|
193
|
+
`(selector ${selector}). Accepted destinations: Aave V3 Pool, Compound V3 Comet markets, ` +
|
|
194
|
+
`Morpho Blue, Lido (stETH/Queue), EigenLayer StrategyManager, Uniswap V3 NPM, LiFi Diamond, ` +
|
|
195
|
+
`and known ERC-20s. An unknown destination with non-empty calldata is exactly the shape of ` +
|
|
196
|
+
`a prompt-injection attack.`);
|
|
197
|
+
}
|
|
198
|
+
// 5) For destinations where we have a tight ABI, verify the selector is one
|
|
199
|
+
// of its functions. LiFi Diamond is the explicit exception (allowedAbi=null).
|
|
200
|
+
if (dest.allowedAbi === null)
|
|
201
|
+
return;
|
|
202
|
+
// Pick the right precomputed selector set.
|
|
203
|
+
const allowedSelectors = (() => {
|
|
204
|
+
switch (dest.kind) {
|
|
205
|
+
case "aave-v3-pool":
|
|
206
|
+
return AAVE_SELECTORS;
|
|
207
|
+
case "compound-v3-comet":
|
|
208
|
+
return COMET_SELECTORS;
|
|
209
|
+
case "morpho-blue":
|
|
210
|
+
return MORPHO_SELECTORS;
|
|
211
|
+
case "lido-stETH":
|
|
212
|
+
// stETH is both the Lido submit surface AND an ERC-20 (transfer/approve).
|
|
213
|
+
return new Set([...LIDO_STETH_SELECTORS, ...ERC20_SELECTORS]);
|
|
214
|
+
case "lido-withdrawalQueue":
|
|
215
|
+
return LIDO_QUEUE_SELECTORS;
|
|
216
|
+
case "eigenlayer-strategyManager":
|
|
217
|
+
return EIGEN_SELECTORS;
|
|
218
|
+
case "uniswap-v3-npm":
|
|
219
|
+
return UNISWAP_NPM_SELECTORS;
|
|
220
|
+
case "known-erc20":
|
|
221
|
+
return ERC20_SELECTORS;
|
|
222
|
+
case "lifi-diamond":
|
|
223
|
+
return null; // handled above
|
|
224
|
+
}
|
|
225
|
+
})();
|
|
226
|
+
if (allowedSelectors && !allowedSelectors.has(selector)) {
|
|
227
|
+
throw new Error(`Pre-sign check: selector ${selector} is not a known function on ${dest.kind} (${tx.to}). ` +
|
|
228
|
+
`Refusing to sign.`);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
//# sourceMappingURL=pre-sign-check.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pre-sign-check.js","sourceRoot":"","sources":["../../src/signing/pre-sign-check.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export interface SessionStatus {
|
|
2
|
+
paired: boolean;
|
|
3
|
+
accounts: `0x${string}`[];
|
|
4
|
+
topic?: string;
|
|
5
|
+
expiresAt?: number;
|
|
6
|
+
/** Peer-advertised app name (e.g. "Ledger Live"). Self-reported — NOT a trusted identity. */
|
|
7
|
+
wallet?: string;
|
|
8
|
+
/** Peer-advertised URL. Self-reported — NOT a trusted identity. */
|
|
9
|
+
peerUrl?: string;
|
|
10
|
+
/** Peer-advertised description. Self-reported — NOT a trusted identity. */
|
|
11
|
+
peerDescription?: string;
|
|
12
|
+
/**
|
|
13
|
+
* Guidance for the agent: WalletConnect peer metadata is self-reported and any
|
|
14
|
+
* app can claim to be "Ledger Live". Surface `wallet`/`peerUrl` to the user
|
|
15
|
+
* before sending a tx they can't physically verify on the Ledger device.
|
|
16
|
+
*/
|
|
17
|
+
peerTrustWarning: string;
|
|
18
|
+
/**
|
|
19
|
+
* Set when a local session record exists but the peer did not respond to the
|
|
20
|
+
* liveness ping on restore. The session may still be valid (peer just
|
|
21
|
+
* offline) or dead (relay didn't deliver a rejection in time) — callers
|
|
22
|
+
* should treat it as unverified and avoid submitting transactions until the
|
|
23
|
+
* peer comes back online or the user re-pairs.
|
|
24
|
+
*/
|
|
25
|
+
peerUnreachable?: boolean;
|
|
26
|
+
}
|
|
27
|
+
export declare const PEER_TRUST_WARNING: string;
|
|
28
|
+
export declare function getSessionStatus(): Promise<SessionStatus>;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { getConnectedAccounts, getCurrentSession, getSignClient, isPeerUnreachable, } from "./walletconnect.js";
|
|
2
|
+
export const PEER_TRUST_WARNING = "WalletConnect peer metadata is self-reported — any app can claim to be 'Ledger Live'. " +
|
|
3
|
+
"If the paired wallet/URL above is unexpected (e.g. not 'Ledger Live' / ledger.com), ask the user " +
|
|
4
|
+
"to confirm before calling send_transaction. The ultimate check is that the tx shows up on the " +
|
|
5
|
+
"user's physical Ledger device for on-screen approval.";
|
|
6
|
+
export async function getSessionStatus() {
|
|
7
|
+
await getSignClient(); // triggers restore + liveness check
|
|
8
|
+
const session = getCurrentSession();
|
|
9
|
+
if (!session)
|
|
10
|
+
return { paired: false, accounts: [], peerTrustWarning: PEER_TRUST_WARNING };
|
|
11
|
+
const accounts = await getConnectedAccounts();
|
|
12
|
+
const meta = session.peer?.metadata;
|
|
13
|
+
const unreachable = isPeerUnreachable();
|
|
14
|
+
return {
|
|
15
|
+
paired: true,
|
|
16
|
+
accounts,
|
|
17
|
+
topic: session.topic,
|
|
18
|
+
expiresAt: session.expiry * 1000,
|
|
19
|
+
...(meta?.name ? { wallet: meta.name } : {}),
|
|
20
|
+
...(meta?.url ? { peerUrl: meta.url } : {}),
|
|
21
|
+
...(meta?.description ? { peerDescription: meta.description } : {}),
|
|
22
|
+
peerTrustWarning: PEER_TRUST_WARNING,
|
|
23
|
+
...(unreachable ? { peerUnreachable: true } : {}),
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=session.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session.js","sourceRoot":"","sources":["../../src/signing/session.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,oBAAoB,EACpB,iBAAiB,EACjB,aAAa,EACb,iBAAiB,GAClB,MAAM,oBAAoB,CAAC;AA6B5B,MAAM,CAAC,MAAM,kBAAkB,GAC7B,wFAAwF;IACxF,mGAAmG;IACnG,gGAAgG;IAChG,uDAAuD,CAAC;AAE1D,MAAM,CAAC,KAAK,UAAU,gBAAgB;IACpC,MAAM,aAAa,EAAE,CAAC,CAAC,oCAAoC;IAC3D,MAAM,OAAO,GAAG,iBAAiB,EAAE,CAAC;IACpC,IAAI,CAAC,OAAO;QACV,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,CAAC;IAC/E,MAAM,QAAQ,GAAG,MAAM,oBAAoB,EAAE,CAAC;IAC9C,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC;IACpC,MAAM,WAAW,GAAG,iBAAiB,EAAE,CAAC;IACxC,OAAO;QACL,MAAM,EAAE,IAAI;QACZ,QAAQ;QACR,KAAK,EAAE,OAAO,CAAC,KAAK;QACpB,SAAS,EAAE,OAAO,CAAC,MAAM,GAAG,IAAI;QAChC,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC5C,GAAG,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC3C,GAAG,CAAC,IAAI,EAAE,WAAW,CAAC,CAAC,CAAC,EAAE,eAAe,EAAE,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACnE,gBAAgB,EAAE,kBAAkB;QACpC,GAAG,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,eAAe,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAClD,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { UnsignedTx } from "../types/index.js";
|
|
2
|
+
/**
|
|
3
|
+
* Recursively assign handles to `tx` and every node in its `.next` chain.
|
|
4
|
+
* Returns a new tx tree with `handle` populated on each node.
|
|
5
|
+
*
|
|
6
|
+
* Each handle stores ONLY the one tx node it names — not the full chain.
|
|
7
|
+
* The agent must call `send_transaction` once per handle, walking the chain
|
|
8
|
+
* explicitly. This makes every signature an independent, auditable event.
|
|
9
|
+
*/
|
|
10
|
+
export declare function issueHandles(tx: UnsignedTx): UnsignedTx;
|
|
11
|
+
/**
|
|
12
|
+
* Retrieve the tx named by `handle`, or throw if unknown/expired. Does NOT
|
|
13
|
+
* delete the entry — we need retry-on-device-disconnect to work, so the handle
|
|
14
|
+
* stays valid until either:
|
|
15
|
+
* (a) the tx is successfully submitted to the relay (caller invokes
|
|
16
|
+
* `retireHandle` after the WalletConnect request resolves), or
|
|
17
|
+
* (b) the TTL expires.
|
|
18
|
+
* Callers must call `retireHandle(handle)` on successful submission so replays
|
|
19
|
+
* fail loudly instead of silently re-submitting the same payload.
|
|
20
|
+
*/
|
|
21
|
+
export declare function consumeHandle(handle: string): UnsignedTx;
|
|
22
|
+
/**
|
|
23
|
+
* Mark a handle as used. Called after the tx has been successfully submitted
|
|
24
|
+
* so the same handle cannot replay the submission. Safe to call on a handle
|
|
25
|
+
* that was already pruned.
|
|
26
|
+
*/
|
|
27
|
+
export declare function retireHandle(handle: string): void;
|
|
28
|
+
/** Test-only: true if `handle` is still active (not retired, not expired). */
|
|
29
|
+
export declare function hasHandle(handle: string): boolean;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
/**
|
|
3
|
+
* In-memory registry of prepared transactions keyed by an opaque handle.
|
|
4
|
+
*
|
|
5
|
+
* Purpose: bind `send_transaction` to the exact tx built by a `prepare_*` tool.
|
|
6
|
+
* Without this, `send_transaction` accepts raw calldata — a prompt-injected
|
|
7
|
+
* agent (e.g. via a malicious Etherscan source comment or ENS reverse record)
|
|
8
|
+
* could convince the model to sign arbitrary bytes while the user thinks they
|
|
9
|
+
* approved the previewed tx.
|
|
10
|
+
*
|
|
11
|
+
* Each prepared tx and every node in its `.next` chain gets its own handle.
|
|
12
|
+
* The signing path looks up by handle and refuses anything not in the store.
|
|
13
|
+
*
|
|
14
|
+
* Lifetime: 15 minutes from issue, enough for a user to review and approve on
|
|
15
|
+
* their Ledger. Expired entries are lazily pruned on every access.
|
|
16
|
+
*/
|
|
17
|
+
const TX_TTL_MS = 15 * 60_000;
|
|
18
|
+
const store = new Map();
|
|
19
|
+
function prune(now = Date.now()) {
|
|
20
|
+
for (const [handle, entry] of store) {
|
|
21
|
+
if (entry.expiresAt < now)
|
|
22
|
+
store.delete(handle);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Recursively assign handles to `tx` and every node in its `.next` chain.
|
|
27
|
+
* Returns a new tx tree with `handle` populated on each node.
|
|
28
|
+
*
|
|
29
|
+
* Each handle stores ONLY the one tx node it names — not the full chain.
|
|
30
|
+
* The agent must call `send_transaction` once per handle, walking the chain
|
|
31
|
+
* explicitly. This makes every signature an independent, auditable event.
|
|
32
|
+
*/
|
|
33
|
+
export function issueHandles(tx) {
|
|
34
|
+
prune();
|
|
35
|
+
const now = Date.now();
|
|
36
|
+
const expiresAt = now + TX_TTL_MS;
|
|
37
|
+
const nextWithHandles = tx.next ? issueHandles(tx.next) : undefined;
|
|
38
|
+
const handle = randomUUID();
|
|
39
|
+
const withHandle = {
|
|
40
|
+
...tx,
|
|
41
|
+
handle,
|
|
42
|
+
...(nextWithHandles ? { next: nextWithHandles } : {}),
|
|
43
|
+
};
|
|
44
|
+
// Store a copy without `handle` on the stored value itself (not needed at
|
|
45
|
+
// lookup time) to avoid the tautology of storing the key inside the value.
|
|
46
|
+
const { handle: _h, next: _n, ...stored } = withHandle;
|
|
47
|
+
store.set(handle, {
|
|
48
|
+
tx: { ...stored, ...(nextWithHandles ? { next: nextWithHandles } : {}) },
|
|
49
|
+
expiresAt,
|
|
50
|
+
});
|
|
51
|
+
return withHandle;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Retrieve the tx named by `handle`, or throw if unknown/expired. Does NOT
|
|
55
|
+
* delete the entry — we need retry-on-device-disconnect to work, so the handle
|
|
56
|
+
* stays valid until either:
|
|
57
|
+
* (a) the tx is successfully submitted to the relay (caller invokes
|
|
58
|
+
* `retireHandle` after the WalletConnect request resolves), or
|
|
59
|
+
* (b) the TTL expires.
|
|
60
|
+
* Callers must call `retireHandle(handle)` on successful submission so replays
|
|
61
|
+
* fail loudly instead of silently re-submitting the same payload.
|
|
62
|
+
*/
|
|
63
|
+
export function consumeHandle(handle) {
|
|
64
|
+
prune();
|
|
65
|
+
const entry = store.get(handle);
|
|
66
|
+
if (!entry) {
|
|
67
|
+
throw new Error(`Unknown or expired tx handle. Prepared transactions expire after 15 minutes and ` +
|
|
68
|
+
`are single-use after a successful submission. Re-run the prepare_* tool to get a fresh handle.`);
|
|
69
|
+
}
|
|
70
|
+
return entry.tx;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Mark a handle as used. Called after the tx has been successfully submitted
|
|
74
|
+
* so the same handle cannot replay the submission. Safe to call on a handle
|
|
75
|
+
* that was already pruned.
|
|
76
|
+
*/
|
|
77
|
+
export function retireHandle(handle) {
|
|
78
|
+
store.delete(handle);
|
|
79
|
+
}
|
|
80
|
+
/** Test-only: true if `handle` is still active (not retired, not expired). */
|
|
81
|
+
export function hasHandle(handle) {
|
|
82
|
+
prune();
|
|
83
|
+
return store.has(handle);
|
|
84
|
+
}
|
|
85
|
+
//# sourceMappingURL=tx-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tx-store.js","sourceRoot":"","sources":["../../src/signing/tx-store.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAGzC;;;;;;;;;;;;;;GAcG;AACH,MAAM,SAAS,GAAG,EAAE,GAAG,MAAM,CAAC;AAO9B,MAAM,KAAK,GAAG,IAAI,GAAG,EAAoB,CAAC;AAE1C,SAAS,KAAK,CAAC,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE;IAC7B,KAAK,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,IAAI,KAAK,EAAE,CAAC;QACpC,IAAI,KAAK,CAAC,SAAS,GAAG,GAAG;YAAE,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IAClD,CAAC;AACH,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,YAAY,CAAC,EAAc;IACzC,KAAK,EAAE,CAAC;IACR,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IACvB,MAAM,SAAS,GAAG,GAAG,GAAG,SAAS,CAAC;IAElC,MAAM,eAAe,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,YAAY,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IACpE,MAAM,MAAM,GAAG,UAAU,EAAE,CAAC;IAC5B,MAAM,UAAU,GAAe;QAC7B,GAAG,EAAE;QACL,MAAM;QACN,GAAG,CAAC,eAAe,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,eAAe,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KACtD,CAAC;IACF,0EAA0E;IAC1E,2EAA2E;IAC3E,MAAM,EAAE,MAAM,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,GAAG,MAAM,EAAE,GAAG,UAAU,CAAC;IACvD,KAAK,CAAC,GAAG,CAAC,MAAM,EAAE;QAChB,EAAE,EAAE,EAAE,GAAG,MAAM,EAAE,GAAG,CAAC,eAAe,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,eAAe,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE;QACxE,SAAS;KACV,CAAC,CAAC;IACH,OAAO,UAAU,CAAC;AACpB,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,aAAa,CAAC,MAAc;IAC1C,KAAK,EAAE,CAAC;IACR,MAAM,KAAK,GAAG,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IAChC,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,MAAM,IAAI,KAAK,CACb,kFAAkF;YAChF,gGAAgG,CACnG,CAAC;IACJ,CAAC;IACD,OAAO,KAAK,CAAC,EAAE,CAAC;AAClB,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,YAAY,CAAC,MAAc;IACzC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;AACvB,CAAC;AAED,8EAA8E;AAC9E,MAAM,UAAU,SAAS,CAAC,MAAc;IACtC,KAAK,EAAE,CAAC;IACR,OAAO,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;AAC3B,CAAC"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { SignClient } from "@walletconnect/sign-client";
|
|
2
|
+
import type { SessionTypes } from "@walletconnect/types";
|
|
3
|
+
import { type UnsignedTx } from "../types/index.js";
|
|
4
|
+
/**
|
|
5
|
+
* EVM namespace requested when proposing a session. We deliberately omit
|
|
6
|
+
* `personal_sign` and `eth_signTypedData_v4` — no tool in this server produces
|
|
7
|
+
* either, so requesting them would be an over-broad capability grant. Blind
|
|
8
|
+
* typed-data signing is the canonical Permit2 / off-chain-order phishing
|
|
9
|
+
* surface; scoping the session away from it means a compromised process can't
|
|
10
|
+
* issue those requests against a live pairing without reconnecting (which the
|
|
11
|
+
* user would see prompted on their device).
|
|
12
|
+
*/
|
|
13
|
+
export declare const REQUIRED_NAMESPACES: {
|
|
14
|
+
eip155: {
|
|
15
|
+
methods: string[];
|
|
16
|
+
chains: string[];
|
|
17
|
+
events: string[];
|
|
18
|
+
};
|
|
19
|
+
};
|
|
20
|
+
export declare function isPeerUnreachable(): boolean;
|
|
21
|
+
export declare function getSignClient(): Promise<InstanceType<typeof SignClient>>;
|
|
22
|
+
export interface PairResult {
|
|
23
|
+
uri: string;
|
|
24
|
+
approval: Promise<SessionTypes.Struct>;
|
|
25
|
+
}
|
|
26
|
+
/** Create a new pairing + session proposal. The returned URI is what the user scans in Ledger Live. */
|
|
27
|
+
export declare function initiatePairing(): Promise<PairResult>;
|
|
28
|
+
export declare function getCurrentSession(): SessionTypes.Struct | null;
|
|
29
|
+
/** Return the list of EVM accounts exposed by the connected wallet (across all namespaces). */
|
|
30
|
+
export declare function getConnectedAccounts(): Promise<`0x${string}`[]>;
|
|
31
|
+
/** Send an `eth_sendTransaction` request. Ledger Live shows it, user signs on device, we get tx hash back. */
|
|
32
|
+
export declare function requestSendTransaction(tx: UnsignedTx): Promise<`0x${string}`>;
|
|
33
|
+
export declare function disconnect(): Promise<void>;
|