bandkit 0.0.1 → 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/.env.example +9 -0
- package/LICENSE +3 -3
- package/README.md +41 -12
- package/bin/bandkit.js +38 -0
- package/contracts/BandExecutor.sol +178 -0
- package/contracts/BandStrategy.sol +111 -0
- package/contracts/test/MockDexTarget.sol +29 -0
- package/contracts/test/MockErc20.sol +40 -0
- package/dist/BandContractPanel.d.ts +11 -0
- package/dist/BandContractPanel.d.ts.map +1 -0
- package/dist/BandContractPanel.js +95 -0
- package/dist/BandContractPanel.js.map +1 -0
- package/dist/BandPanel.d.ts +13 -0
- package/dist/BandPanel.d.ts.map +1 -0
- package/dist/BandPanel.js +142 -0
- package/dist/BandPanel.js.map +1 -0
- package/dist/bandExecutorAbi.d.ts +431 -0
- package/dist/bandExecutorAbi.d.ts.map +1 -0
- package/dist/bandExecutorAbi.js +558 -0
- package/dist/bandExecutorAbi.js.map +1 -0
- package/dist/bandExecutorBytecode.d.ts +2 -0
- package/dist/bandExecutorBytecode.d.ts.map +1 -0
- package/dist/bandExecutorBytecode.js +3 -0
- package/dist/bandExecutorBytecode.js.map +1 -0
- package/dist/bandStrategyAbi.d.ts +250 -0
- package/dist/bandStrategyAbi.d.ts.map +1 -0
- package/dist/bandStrategyAbi.js +323 -0
- package/dist/bandStrategyAbi.js.map +1 -0
- package/dist/bandStrategyBytecode.d.ts +2 -0
- package/dist/bandStrategyBytecode.d.ts.map +1 -0
- package/dist/bandStrategyBytecode.js +3 -0
- package/dist/bandStrategyBytecode.js.map +1 -0
- package/dist/index.d.ts +36 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +36 -0
- package/dist/index.js.map +1 -0
- package/dist/rainbowkit.d.ts +7 -0
- package/dist/rainbowkit.d.ts.map +1 -0
- package/dist/rainbowkit.js +33 -0
- package/dist/rainbowkit.js.map +1 -0
- package/dist/tradingExecutor.d.ts +38 -0
- package/dist/tradingExecutor.d.ts.map +1 -0
- package/dist/tradingExecutor.js +125 -0
- package/dist/tradingExecutor.js.map +1 -0
- package/dist/useBandDashboard.d.ts +27 -0
- package/dist/useBandDashboard.d.ts.map +1 -0
- package/dist/useBandDashboard.js +35 -0
- package/dist/useBandDashboard.js.map +1 -0
- package/dist/useEthPriceTicker.d.ts +32 -0
- package/dist/useEthPriceTicker.d.ts.map +1 -0
- package/dist/useEthPriceTicker.js +95 -0
- package/dist/useEthPriceTicker.js.map +1 -0
- package/dist/useStrategyContract.d.ts +42 -0
- package/dist/useStrategyContract.d.ts.map +1 -0
- package/dist/useStrategyContract.js +258 -0
- package/dist/useStrategyContract.js.map +1 -0
- package/dist/useStrategyContractDeployment.d.ts +31 -0
- package/dist/useStrategyContractDeployment.d.ts.map +1 -0
- package/dist/useStrategyContractDeployment.js +229 -0
- package/dist/useStrategyContractDeployment.js.map +1 -0
- package/dist/useWalletEthBalance.d.ts +17 -0
- package/dist/useWalletEthBalance.d.ts.map +1 -0
- package/dist/useWalletEthBalance.js +98 -0
- package/dist/useWalletEthBalance.js.map +1 -0
- package/engine/browser-provider.js +25 -0
- package/engine/executor-searcher.js +138 -0
- package/engine/provider.js +32 -0
- package/engine/uniswap-v2-roundtrip.js +215 -0
- package/hardhat.config.cjs +27 -0
- package/package.json +75 -14
- package/scripts/deploy.cjs +31 -0
- package/index.d.ts +0 -2
- package/index.js +0 -2
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { useCallback, useEffect, useState } from "react";
|
|
2
|
+
import { formatEther } from "viem";
|
|
3
|
+
import { useAccount } from "wagmi";
|
|
4
|
+
import { mainnet } from "wagmi/chains";
|
|
5
|
+
function getChromeWalletProvider() {
|
|
6
|
+
if (typeof window === "undefined")
|
|
7
|
+
return undefined;
|
|
8
|
+
return window.ethereum;
|
|
9
|
+
}
|
|
10
|
+
function chainIdToHex(chainId) {
|
|
11
|
+
return `0x${chainId.toString(16)}`;
|
|
12
|
+
}
|
|
13
|
+
function parseHexWei(value) {
|
|
14
|
+
return typeof value === "string" && value.startsWith("0x") ? BigInt(value) : 0n;
|
|
15
|
+
}
|
|
16
|
+
export function useWalletEthBalance(options = {}) {
|
|
17
|
+
const account = useAccount();
|
|
18
|
+
const address = options.address ?? account.address;
|
|
19
|
+
const chainId = options.chainId ?? mainnet.id;
|
|
20
|
+
const [balanceWei, setBalanceWei] = useState(0n);
|
|
21
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
22
|
+
const [error, setError] = useState();
|
|
23
|
+
const refetch = useCallback(async () => {
|
|
24
|
+
if (!address) {
|
|
25
|
+
setBalanceWei(0n);
|
|
26
|
+
setError(undefined);
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
const provider = getChromeWalletProvider();
|
|
30
|
+
if (!provider) {
|
|
31
|
+
setBalanceWei(0n);
|
|
32
|
+
setError(new Error("Chrome wallet provider is not available."));
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
setIsLoading(true);
|
|
36
|
+
try {
|
|
37
|
+
const providerChainId = await provider.request({ method: "eth_chainId" });
|
|
38
|
+
if (providerChainId !== chainIdToHex(chainId)) {
|
|
39
|
+
setBalanceWei(0n);
|
|
40
|
+
setError(new Error("Switch your Chrome wallet to Ethereum mainnet."));
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
const value = await provider.request({
|
|
44
|
+
method: "eth_getBalance",
|
|
45
|
+
params: [address, "latest"]
|
|
46
|
+
});
|
|
47
|
+
setBalanceWei(parseHexWei(value));
|
|
48
|
+
setError(undefined);
|
|
49
|
+
}
|
|
50
|
+
catch (caught) {
|
|
51
|
+
setBalanceWei(0n);
|
|
52
|
+
setError(caught instanceof Error ? caught : new Error(String(caught)));
|
|
53
|
+
}
|
|
54
|
+
finally {
|
|
55
|
+
setIsLoading(false);
|
|
56
|
+
}
|
|
57
|
+
}, [address, chainId]);
|
|
58
|
+
useEffect(() => {
|
|
59
|
+
void refetch();
|
|
60
|
+
}, [refetch]);
|
|
61
|
+
useEffect(() => {
|
|
62
|
+
if (!address)
|
|
63
|
+
return;
|
|
64
|
+
const intervalId = window.setInterval(() => {
|
|
65
|
+
void refetch();
|
|
66
|
+
}, 12_000);
|
|
67
|
+
return () => {
|
|
68
|
+
window.clearInterval(intervalId);
|
|
69
|
+
};
|
|
70
|
+
}, [address, refetch]);
|
|
71
|
+
useEffect(() => {
|
|
72
|
+
const provider = getChromeWalletProvider();
|
|
73
|
+
if (!provider?.on || !provider.removeListener)
|
|
74
|
+
return;
|
|
75
|
+
const refresh = () => {
|
|
76
|
+
void refetch();
|
|
77
|
+
};
|
|
78
|
+
provider.on("accountsChanged", refresh);
|
|
79
|
+
provider.on("chainChanged", refresh);
|
|
80
|
+
return () => {
|
|
81
|
+
provider.removeListener?.("accountsChanged", refresh);
|
|
82
|
+
provider.removeListener?.("chainChanged", refresh);
|
|
83
|
+
};
|
|
84
|
+
}, [refetch]);
|
|
85
|
+
return {
|
|
86
|
+
address,
|
|
87
|
+
balanceWei,
|
|
88
|
+
balanceEth: formatEther(balanceWei),
|
|
89
|
+
formatted: formatEther(balanceWei),
|
|
90
|
+
symbol: "ETH",
|
|
91
|
+
isLoading,
|
|
92
|
+
error,
|
|
93
|
+
refetch: () => {
|
|
94
|
+
void refetch();
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
//# sourceMappingURL=useWalletEthBalance.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useWalletEthBalance.js","sourceRoot":"","sources":["../src/useWalletEthBalance.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { createPublicClient, createWalletClient, custom } from "viem";
|
|
2
|
+
import { mainnet } from "viem/chains";
|
|
3
|
+
|
|
4
|
+
export function getBrowserEthereumProvider() {
|
|
5
|
+
if (typeof window === "undefined" || !window.ethereum) {
|
|
6
|
+
throw new Error("No browser wallet provider found. Open this from a wallet-enabled browser or provide ENGINE_RPC_URL.");
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
return window.ethereum;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function createBrowserPublicClient() {
|
|
13
|
+
return createPublicClient({
|
|
14
|
+
chain: mainnet,
|
|
15
|
+
transport: custom(getBrowserEthereumProvider())
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function createBrowserWalletClient(account) {
|
|
20
|
+
return createWalletClient({
|
|
21
|
+
account,
|
|
22
|
+
chain: mainnet,
|
|
23
|
+
transport: custom(getBrowserEthereumProvider())
|
|
24
|
+
});
|
|
25
|
+
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
formatEther,
|
|
4
|
+
isAddress,
|
|
5
|
+
parseAbi,
|
|
6
|
+
parseEther
|
|
7
|
+
} from "viem";
|
|
8
|
+
import { privateKeyToAccount } from "viem/accounts";
|
|
9
|
+
import { createEnginePublicClient, createEngineWalletClient, providerMode } from "./provider.js";
|
|
10
|
+
|
|
11
|
+
const executorAbi = parseAbi([
|
|
12
|
+
"function approvedSelectors(address target,bytes4 selector) view returns (bool)",
|
|
13
|
+
"function approvedTargets(address target) view returns (bool)",
|
|
14
|
+
"function engineWallet() view returns (address)",
|
|
15
|
+
"function executeTrade(address target,uint256 value,bytes data,uint256 minBalanceAfter) payable returns (bytes)",
|
|
16
|
+
"function paused() view returns (bool)"
|
|
17
|
+
]);
|
|
18
|
+
|
|
19
|
+
function requiredAddress(name) {
|
|
20
|
+
const value = process.env[name];
|
|
21
|
+
if (!value || !isAddress(value)) {
|
|
22
|
+
throw new Error(`${name} must be a valid Ethereum address.`);
|
|
23
|
+
}
|
|
24
|
+
return value;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function bigintEnv(name, fallback = 0n) {
|
|
28
|
+
const value = process.env[name];
|
|
29
|
+
if (!value) return fallback;
|
|
30
|
+
return BigInt(value);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function ethEnv(name, fallback = 0n) {
|
|
34
|
+
const value = process.env[name];
|
|
35
|
+
if (!value) return fallback;
|
|
36
|
+
return parseEther(value);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function hexEnv(name, fallback = "0x") {
|
|
40
|
+
const value = process.env[name] ?? fallback;
|
|
41
|
+
if (!/^0x([a-fA-F0-9]{2})*$/.test(value)) {
|
|
42
|
+
throw new Error(`${name} must be 0x-prefixed hex calldata.`);
|
|
43
|
+
}
|
|
44
|
+
return value;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const rpcUrl = process.env.ENGINE_RPC_URL;
|
|
48
|
+
const executorAddress = requiredAddress("EXECUTOR_ADDRESS");
|
|
49
|
+
const targetAddress = requiredAddress("TRADE_TARGET");
|
|
50
|
+
const calldata = hexEnv("TRADE_CALLDATA");
|
|
51
|
+
const calldataSelector = calldata.length >= 10 ? calldata.slice(0, 10) : undefined;
|
|
52
|
+
const tradeValue = ethEnv("TRADE_VALUE_ETH", bigintEnv("TRADE_VALUE_WEI"));
|
|
53
|
+
const minBalanceAfter = ethEnv("MIN_EXECUTOR_BALANCE_AFTER_ETH", bigintEnv("MIN_EXECUTOR_BALANCE_AFTER_WEI"));
|
|
54
|
+
const shouldExecute = process.env.ENGINE_EXECUTE === "true";
|
|
55
|
+
const dryRunAccount = process.env.ENGINE_ACCOUNT_ADDRESS;
|
|
56
|
+
const privateKey = process.env.ENGINE_PRIVATE_KEY;
|
|
57
|
+
|
|
58
|
+
const publicClient = createEnginePublicClient(rpcUrl);
|
|
59
|
+
|
|
60
|
+
const account = privateKey
|
|
61
|
+
? privateKeyToAccount(privateKey.startsWith("0x") ? privateKey : `0x${privateKey}`)
|
|
62
|
+
: dryRunAccount && isAddress(dryRunAccount)
|
|
63
|
+
? dryRunAccount
|
|
64
|
+
: undefined;
|
|
65
|
+
|
|
66
|
+
if (!account) {
|
|
67
|
+
throw new Error("Set ENGINE_PRIVATE_KEY for execution or ENGINE_ACCOUNT_ADDRESS for dry-run simulation.");
|
|
68
|
+
}
|
|
69
|
+
if (!calldataSelector) {
|
|
70
|
+
throw new Error("TRADE_CALLDATA must include a 4-byte function selector.");
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const [paused, engineWallet, targetApproved, selectorApproved, executorBalance] = await Promise.all([
|
|
74
|
+
publicClient.readContract({
|
|
75
|
+
address: executorAddress,
|
|
76
|
+
abi: executorAbi,
|
|
77
|
+
functionName: "paused"
|
|
78
|
+
}),
|
|
79
|
+
publicClient.readContract({
|
|
80
|
+
address: executorAddress,
|
|
81
|
+
abi: executorAbi,
|
|
82
|
+
functionName: "engineWallet"
|
|
83
|
+
}),
|
|
84
|
+
publicClient.readContract({
|
|
85
|
+
address: executorAddress,
|
|
86
|
+
abi: executorAbi,
|
|
87
|
+
functionName: "approvedTargets",
|
|
88
|
+
args: [targetAddress]
|
|
89
|
+
}),
|
|
90
|
+
publicClient.readContract({
|
|
91
|
+
address: executorAddress,
|
|
92
|
+
abi: executorAbi,
|
|
93
|
+
functionName: "approvedSelectors",
|
|
94
|
+
args: [targetAddress, calldataSelector]
|
|
95
|
+
}),
|
|
96
|
+
publicClient.getBalance({ address: executorAddress })
|
|
97
|
+
]);
|
|
98
|
+
|
|
99
|
+
console.log(`Executor: ${executorAddress}`);
|
|
100
|
+
console.log(`Provider mode: ${providerMode(rpcUrl)}`);
|
|
101
|
+
console.log(`Engine wallet: ${engineWallet}`);
|
|
102
|
+
console.log(`Target: ${targetAddress}`);
|
|
103
|
+
console.log(`Selector: ${calldataSelector}`);
|
|
104
|
+
console.log(`Executor balance: ${formatEther(executorBalance)} ETH`);
|
|
105
|
+
console.log(`Trade value: ${formatEther(tradeValue)} ETH`);
|
|
106
|
+
console.log(`Minimum balance after: ${formatEther(minBalanceAfter)} ETH`);
|
|
107
|
+
|
|
108
|
+
if (paused) throw new Error("Executor is paused.");
|
|
109
|
+
if (!targetApproved) throw new Error("Trade target is not approved by the executor owner.");
|
|
110
|
+
if (!selectorApproved) throw new Error("Trade selector is not approved for this target.");
|
|
111
|
+
|
|
112
|
+
const simulation = await publicClient.simulateContract({
|
|
113
|
+
account,
|
|
114
|
+
address: executorAddress,
|
|
115
|
+
abi: executorAbi,
|
|
116
|
+
functionName: "executeTrade",
|
|
117
|
+
args: [targetAddress, tradeValue, calldata, minBalanceAfter],
|
|
118
|
+
value: tradeValue
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
console.log("Simulation passed.");
|
|
122
|
+
|
|
123
|
+
if (!shouldExecute) {
|
|
124
|
+
console.log("Dry run only. Set ENGINE_EXECUTE=true and ENGINE_PRIVATE_KEY to send the transaction.");
|
|
125
|
+
process.exit(0);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (!privateKey) {
|
|
129
|
+
throw new Error("ENGINE_PRIVATE_KEY is required when ENGINE_EXECUTE=true.");
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const walletClient = createEngineWalletClient({
|
|
133
|
+
account,
|
|
134
|
+
rpcUrl
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
const hash = await walletClient.writeContract(simulation.request);
|
|
138
|
+
console.log(`Submitted trade transaction: ${hash}`);
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { createPublicClient, createWalletClient, custom, http } from "viem";
|
|
2
|
+
import { mainnet } from "viem/chains";
|
|
3
|
+
|
|
4
|
+
function getBrowserEthereumProvider() {
|
|
5
|
+
if (typeof window === "undefined" || !window.ethereum) return undefined;
|
|
6
|
+
return window.ethereum;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function createEnginePublicClient(rpcUrl) {
|
|
10
|
+
const browserProvider = getBrowserEthereumProvider();
|
|
11
|
+
|
|
12
|
+
return createPublicClient({
|
|
13
|
+
chain: mainnet,
|
|
14
|
+
transport: rpcUrl ? http(rpcUrl) : browserProvider ? custom(browserProvider) : http("https://ethereum.publicnode.com")
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function createEngineWalletClient({ account, rpcUrl }) {
|
|
19
|
+
const browserProvider = getBrowserEthereumProvider();
|
|
20
|
+
|
|
21
|
+
return createWalletClient({
|
|
22
|
+
account,
|
|
23
|
+
chain: mainnet,
|
|
24
|
+
transport: rpcUrl ? http(rpcUrl) : browserProvider ? custom(browserProvider) : http("https://ethereum.publicnode.com")
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function providerMode(rpcUrl) {
|
|
29
|
+
if (rpcUrl) return "http-rpc";
|
|
30
|
+
if (getBrowserEthereumProvider()) return "browser-wallet";
|
|
31
|
+
return "public-rpc-fallback";
|
|
32
|
+
}
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
encodeFunctionData,
|
|
4
|
+
formatEther,
|
|
5
|
+
isAddress,
|
|
6
|
+
maxUint256,
|
|
7
|
+
parseAbi,
|
|
8
|
+
parseEther
|
|
9
|
+
} from "viem";
|
|
10
|
+
import { privateKeyToAccount } from "viem/accounts";
|
|
11
|
+
import { createEnginePublicClient, createEngineWalletClient, providerMode } from "./provider.js";
|
|
12
|
+
|
|
13
|
+
const UNISWAP_V2_ROUTER = "0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D";
|
|
14
|
+
const WETH = "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2";
|
|
15
|
+
|
|
16
|
+
const executorAbi = parseAbi([
|
|
17
|
+
"function approvedSelectors(address target,bytes4 selector) view returns (bool)",
|
|
18
|
+
"function approvedTargets(address target) view returns (bool)",
|
|
19
|
+
"function approvedTokens(address token) view returns (bool)",
|
|
20
|
+
"function approveToken(address token,address spender,uint256 amount)",
|
|
21
|
+
"function engineWallet() view returns (address)",
|
|
22
|
+
"function executeBatch(address[] targets,uint256[] values,bytes[] payloads,uint256 minBalanceAfter) payable returns (bytes[])",
|
|
23
|
+
"function paused() view returns (bool)"
|
|
24
|
+
]);
|
|
25
|
+
|
|
26
|
+
const SWAP_EXACT_ETH_FOR_TOKENS_SELECTOR = "0x7ff36ab5";
|
|
27
|
+
const SWAP_EXACT_TOKENS_FOR_ETH_SELECTOR = "0x18cbafe5";
|
|
28
|
+
|
|
29
|
+
const erc20Abi = parseAbi([
|
|
30
|
+
"function allowance(address owner,address spender) view returns (uint256)"
|
|
31
|
+
]);
|
|
32
|
+
|
|
33
|
+
const routerAbi = parseAbi([
|
|
34
|
+
"function getAmountsOut(uint amountIn,address[] calldata path) view returns (uint[] memory amounts)",
|
|
35
|
+
"function swapExactETHForTokens(uint amountOutMin,address[] calldata path,address to,uint deadline) payable returns (uint[] memory amounts)",
|
|
36
|
+
"function swapExactTokensForETH(uint amountIn,uint amountOutMin,address[] calldata path,address to,uint deadline) returns (uint[] memory amounts)"
|
|
37
|
+
]);
|
|
38
|
+
|
|
39
|
+
function requiredAddress(name) {
|
|
40
|
+
const value = process.env[name];
|
|
41
|
+
if (!value || !isAddress(value)) throw new Error(`${name} must be a valid Ethereum address.`);
|
|
42
|
+
return value;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function optionalAddress(name, fallback) {
|
|
46
|
+
const value = process.env[name] ?? fallback;
|
|
47
|
+
if (!isAddress(value)) throw new Error(`${name} must be a valid Ethereum address.`);
|
|
48
|
+
return value;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function ethEnv(name, fallback) {
|
|
52
|
+
const value = process.env[name];
|
|
53
|
+
return value ? parseEther(value) : fallback;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function intEnv(name, fallback) {
|
|
57
|
+
const value = process.env[name];
|
|
58
|
+
return value ? Number.parseInt(value, 10) : fallback;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function applySlippage(value, slippageBps) {
|
|
62
|
+
return (value * BigInt(10_000 - slippageBps)) / 10_000n;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const rpcUrl = process.env.ENGINE_RPC_URL;
|
|
66
|
+
const executorAddress = requiredAddress("EXECUTOR_ADDRESS");
|
|
67
|
+
const tokenAddress = requiredAddress("TRADE_TOKEN");
|
|
68
|
+
const routerAddress = optionalAddress("ROUTER_ADDRESS", UNISWAP_V2_ROUTER);
|
|
69
|
+
const wethAddress = optionalAddress("WETH_ADDRESS", WETH);
|
|
70
|
+
const tradeValue = ethEnv("TRADE_VALUE_ETH", parseEther("0.01"));
|
|
71
|
+
const minProfit = ethEnv("MIN_PROFIT_ETH", 0n);
|
|
72
|
+
const slippageBps = intEnv("SLIPPAGE_BPS", 50);
|
|
73
|
+
const deadlineSeconds = intEnv("DEADLINE_SECONDS", 180);
|
|
74
|
+
const shouldExecute = process.env.ENGINE_EXECUTE === "true";
|
|
75
|
+
const privateKey = process.env.ENGINE_PRIVATE_KEY;
|
|
76
|
+
const dryRunAccount = process.env.ENGINE_ACCOUNT_ADDRESS;
|
|
77
|
+
|
|
78
|
+
if (slippageBps < 0 || slippageBps > 1_000) {
|
|
79
|
+
throw new Error("SLIPPAGE_BPS must be between 0 and 1000.");
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const publicClient = createEnginePublicClient(rpcUrl);
|
|
83
|
+
|
|
84
|
+
const account = privateKey
|
|
85
|
+
? privateKeyToAccount(privateKey.startsWith("0x") ? privateKey : `0x${privateKey}`)
|
|
86
|
+
: dryRunAccount && isAddress(dryRunAccount)
|
|
87
|
+
? dryRunAccount
|
|
88
|
+
: undefined;
|
|
89
|
+
|
|
90
|
+
if (!account) throw new Error("Set ENGINE_PRIVATE_KEY for execution or ENGINE_ACCOUNT_ADDRESS for dry-run simulation.");
|
|
91
|
+
|
|
92
|
+
const [paused, engineWallet, routerApproved, buySelectorApproved, sellSelectorApproved, tokenApproved, executorBalance] = await Promise.all([
|
|
93
|
+
publicClient.readContract({ address: executorAddress, abi: executorAbi, functionName: "paused" }),
|
|
94
|
+
publicClient.readContract({ address: executorAddress, abi: executorAbi, functionName: "engineWallet" }),
|
|
95
|
+
publicClient.readContract({ address: executorAddress, abi: executorAbi, functionName: "approvedTargets", args: [routerAddress] }),
|
|
96
|
+
publicClient.readContract({ address: executorAddress, abi: executorAbi, functionName: "approvedSelectors", args: [routerAddress, SWAP_EXACT_ETH_FOR_TOKENS_SELECTOR] }),
|
|
97
|
+
publicClient.readContract({ address: executorAddress, abi: executorAbi, functionName: "approvedSelectors", args: [routerAddress, SWAP_EXACT_TOKENS_FOR_ETH_SELECTOR] }),
|
|
98
|
+
publicClient.readContract({ address: executorAddress, abi: executorAbi, functionName: "approvedTokens", args: [tokenAddress] }),
|
|
99
|
+
publicClient.getBalance({ address: executorAddress })
|
|
100
|
+
]);
|
|
101
|
+
|
|
102
|
+
if (paused) throw new Error("Executor is paused.");
|
|
103
|
+
if (!routerApproved) throw new Error("Router is not approved in BandExecutor.");
|
|
104
|
+
if (!buySelectorApproved) throw new Error("Router buy selector is not approved in BandExecutor.");
|
|
105
|
+
if (!sellSelectorApproved) throw new Error("Router sell selector is not approved in BandExecutor.");
|
|
106
|
+
if (!tokenApproved) throw new Error("Trade token is not approved in BandExecutor.");
|
|
107
|
+
if (executorBalance < tradeValue) throw new Error("Executor balance is lower than TRADE_VALUE_ETH.");
|
|
108
|
+
|
|
109
|
+
const buyPath = [wethAddress, tokenAddress];
|
|
110
|
+
const sellPath = [tokenAddress, wethAddress];
|
|
111
|
+
const [buyQuote, sellQuote] = await Promise.all([
|
|
112
|
+
publicClient.readContract({ address: routerAddress, abi: routerAbi, functionName: "getAmountsOut", args: [tradeValue, buyPath] }),
|
|
113
|
+
publicClient.readContract({
|
|
114
|
+
address: routerAddress,
|
|
115
|
+
abi: routerAbi,
|
|
116
|
+
functionName: "getAmountsOut",
|
|
117
|
+
args: [
|
|
118
|
+
(await publicClient.readContract({
|
|
119
|
+
address: routerAddress,
|
|
120
|
+
abi: routerAbi,
|
|
121
|
+
functionName: "getAmountsOut",
|
|
122
|
+
args: [tradeValue, buyPath]
|
|
123
|
+
}))[1],
|
|
124
|
+
sellPath
|
|
125
|
+
]
|
|
126
|
+
})
|
|
127
|
+
]);
|
|
128
|
+
|
|
129
|
+
const expectedTokenOut = buyQuote[1];
|
|
130
|
+
const expectedEthOut = sellQuote[1];
|
|
131
|
+
const expectedProfit = expectedEthOut - tradeValue;
|
|
132
|
+
|
|
133
|
+
console.log(`Executor: ${executorAddress}`);
|
|
134
|
+
console.log(`Provider mode: ${providerMode(rpcUrl)}`);
|
|
135
|
+
console.log(`Engine wallet: ${engineWallet}`);
|
|
136
|
+
console.log(`Router: ${routerAddress}`);
|
|
137
|
+
console.log(`Token: ${tokenAddress}`);
|
|
138
|
+
console.log(`Executor balance: ${formatEther(executorBalance)} ETH`);
|
|
139
|
+
console.log(`Trade value: ${formatEther(tradeValue)} ETH`);
|
|
140
|
+
console.log(`Expected token out: ${expectedTokenOut.toString()}`);
|
|
141
|
+
console.log(`Expected ETH after round trip: ${formatEther(expectedEthOut)} ETH`);
|
|
142
|
+
console.log(`Expected profit before gas: ${formatEther(expectedProfit)} ETH`);
|
|
143
|
+
|
|
144
|
+
if (expectedProfit < minProfit) {
|
|
145
|
+
throw new Error(`Expected profit ${formatEther(expectedProfit)} ETH is below MIN_PROFIT_ETH ${formatEther(minProfit)} ETH.`);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const allowance = await publicClient.readContract({
|
|
149
|
+
address: tokenAddress,
|
|
150
|
+
abi: erc20Abi,
|
|
151
|
+
functionName: "allowance",
|
|
152
|
+
args: [executorAddress, routerAddress]
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
const walletClient = privateKey
|
|
156
|
+
? createEngineWalletClient({ account, rpcUrl })
|
|
157
|
+
: undefined;
|
|
158
|
+
|
|
159
|
+
if (allowance < expectedTokenOut) {
|
|
160
|
+
console.log("Executor token allowance is lower than expected token output.");
|
|
161
|
+
|
|
162
|
+
if (!shouldExecute || !walletClient) {
|
|
163
|
+
console.log("Dry run stopped before batch simulation. Run once with ENGINE_EXECUTE=true and ENGINE_PRIVATE_KEY to approve the router.");
|
|
164
|
+
process.exit(0);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const approvalHash = await walletClient.writeContract({
|
|
168
|
+
address: executorAddress,
|
|
169
|
+
abi: executorAbi,
|
|
170
|
+
functionName: "approveToken",
|
|
171
|
+
args: [tokenAddress, routerAddress, maxUint256]
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
console.log(`Submitted executor token approval: ${approvalHash}`);
|
|
175
|
+
await publicClient.waitForTransactionReceipt({ hash: approvalHash });
|
|
176
|
+
console.log("Executor token approval confirmed.");
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const deadline = BigInt(Math.floor(Date.now() / 1000) + deadlineSeconds);
|
|
180
|
+
const buyCalldata = encodeFunctionData({
|
|
181
|
+
abi: routerAbi,
|
|
182
|
+
functionName: "swapExactETHForTokens",
|
|
183
|
+
args: [applySlippage(expectedTokenOut, slippageBps), buyPath, executorAddress, deadline]
|
|
184
|
+
});
|
|
185
|
+
const sellCalldata = encodeFunctionData({
|
|
186
|
+
abi: routerAbi,
|
|
187
|
+
functionName: "swapExactTokensForETH",
|
|
188
|
+
args: [expectedTokenOut, applySlippage(expectedEthOut, slippageBps), sellPath, executorAddress, deadline]
|
|
189
|
+
});
|
|
190
|
+
const minBalanceAfter = executorBalance + minProfit;
|
|
191
|
+
|
|
192
|
+
const simulation = await publicClient.simulateContract({
|
|
193
|
+
account,
|
|
194
|
+
address: executorAddress,
|
|
195
|
+
abi: executorAbi,
|
|
196
|
+
functionName: "executeBatch",
|
|
197
|
+
args: [
|
|
198
|
+
[routerAddress, routerAddress],
|
|
199
|
+
[tradeValue, 0n],
|
|
200
|
+
[buyCalldata, sellCalldata],
|
|
201
|
+
minBalanceAfter
|
|
202
|
+
]
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
console.log(`Batch simulation passed. Minimum executor balance after: ${formatEther(minBalanceAfter)} ETH`);
|
|
206
|
+
|
|
207
|
+
if (!shouldExecute) {
|
|
208
|
+
console.log("Dry run only. Set ENGINE_EXECUTE=true and ENGINE_PRIVATE_KEY to execute.");
|
|
209
|
+
process.exit(0);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if (!walletClient) throw new Error("ENGINE_PRIVATE_KEY is required when ENGINE_EXECUTE=true.");
|
|
213
|
+
|
|
214
|
+
const hash = await walletClient.writeContract(simulation.request);
|
|
215
|
+
console.log(`Submitted round-trip transaction: ${hash}`);
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
require("dotenv/config");
|
|
2
|
+
require("@nomicfoundation/hardhat-ethers");
|
|
3
|
+
|
|
4
|
+
const MAINNET_RPC_URL = process.env.MAINNET_RPC_URL ?? "";
|
|
5
|
+
const DEPLOYER_PRIVATE_KEY = process.env.DEPLOYER_PRIVATE_KEY ?? "";
|
|
6
|
+
|
|
7
|
+
const accounts = DEPLOYER_PRIVATE_KEY ? [DEPLOYER_PRIVATE_KEY] : [];
|
|
8
|
+
|
|
9
|
+
/** @type {import("hardhat/config").HardhatUserConfig} */
|
|
10
|
+
module.exports = {
|
|
11
|
+
solidity: {
|
|
12
|
+
version: "0.8.24",
|
|
13
|
+
settings: {
|
|
14
|
+
optimizer: {
|
|
15
|
+
enabled: true,
|
|
16
|
+
runs: 200
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
networks: {
|
|
21
|
+
mainnet: {
|
|
22
|
+
url: MAINNET_RPC_URL,
|
|
23
|
+
accounts,
|
|
24
|
+
chainId: 1
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
};
|
package/package.json
CHANGED
|
@@ -1,27 +1,88 @@
|
|
|
1
|
-
|
|
1
|
+
{
|
|
2
2
|
"name": "bandkit",
|
|
3
|
-
"version": "0.0
|
|
4
|
-
"description": "Open-source scaffolding for Ethereum
|
|
5
|
-
"
|
|
6
|
-
"
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Open-source scaffolding for Ethereum range-bound trading strategies: strategy contract deployment, restricted executor, and React hooks. Strategy logic is not included.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "./dist/index.js",
|
|
8
|
+
"module": "./dist/index.js",
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"bin": {
|
|
11
|
+
"bandkit": "./bin/bandkit.js"
|
|
12
|
+
},
|
|
7
13
|
"files": [
|
|
8
|
-
"
|
|
9
|
-
"
|
|
14
|
+
"bin",
|
|
15
|
+
"engine",
|
|
16
|
+
"contracts",
|
|
17
|
+
"dist",
|
|
18
|
+
"scripts",
|
|
19
|
+
"hardhat.config.cjs",
|
|
20
|
+
".env.example",
|
|
10
21
|
"README.md",
|
|
11
22
|
"LICENSE"
|
|
12
23
|
],
|
|
24
|
+
"exports": {
|
|
25
|
+
".": {
|
|
26
|
+
"types": "./dist/index.d.ts",
|
|
27
|
+
"import": "./dist/index.js",
|
|
28
|
+
"default": "./dist/index.js"
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
"scripts": {
|
|
32
|
+
"engine:execute": "node engine/executor-searcher.js",
|
|
33
|
+
"engine:simulate": "node engine/executor-searcher.js",
|
|
34
|
+
"engine:uniswap-v2": "node engine/uniswap-v2-roundtrip.js",
|
|
35
|
+
"bot:execute": "node -e \"console.warn('bot:execute is deprecated. Use engine:execute.'); import('./engine/executor-searcher.js')\"",
|
|
36
|
+
"bot:simulate": "node -e \"console.warn('bot:simulate is deprecated. Use engine:simulate.'); import('./engine/executor-searcher.js')\"",
|
|
37
|
+
"bot:uniswap-v2": "node -e \"console.warn('bot:uniswap-v2 is deprecated. Use engine:uniswap-v2.'); import('./engine/uniswap-v2-roundtrip.js')\"",
|
|
38
|
+
"build": "tsc -p tsconfig.build.json",
|
|
39
|
+
"clean": "rimraf dist artifacts cache typechain-types",
|
|
40
|
+
"compile:contracts": "hardhat compile",
|
|
41
|
+
"deploy:mainnet": "hardhat run scripts/deploy.cjs --network mainnet",
|
|
42
|
+
"test": "hardhat test",
|
|
43
|
+
"typecheck": "tsc --noEmit"
|
|
44
|
+
},
|
|
45
|
+
"peerDependencies": {
|
|
46
|
+
"@rainbow-me/rainbowkit": "^2.2.0",
|
|
47
|
+
"@tanstack/react-query": "^5.0.0",
|
|
48
|
+
"react": "^18.2.0 || ^19.0.0",
|
|
49
|
+
"viem": "^2.0.0",
|
|
50
|
+
"wagmi": "^2.0.0"
|
|
51
|
+
},
|
|
52
|
+
"peerDependenciesMeta": {
|
|
53
|
+
"@rainbow-me/rainbowkit": {
|
|
54
|
+
"optional": true
|
|
55
|
+
},
|
|
56
|
+
"@tanstack/react-query": {
|
|
57
|
+
"optional": true
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
"devDependencies": {
|
|
61
|
+
"@nomicfoundation/hardhat-ethers": "3.0.8",
|
|
62
|
+
"@rainbow-me/rainbowkit": "^2.2.0",
|
|
63
|
+
"@tanstack/react-query": "^5.0.0",
|
|
64
|
+
"@types/node": "^22.0.0",
|
|
65
|
+
"@types/react": "^19.0.0",
|
|
66
|
+
"chai": "^6.2.2",
|
|
67
|
+
"dotenv": "^16.4.7",
|
|
68
|
+
"fp-ts": "^2.16.11",
|
|
69
|
+
"hardhat": "2.22.19",
|
|
70
|
+
"react": "^18.3.1",
|
|
71
|
+
"react-dom": "^18.3.1",
|
|
72
|
+
"rimraf": "^6.0.1",
|
|
73
|
+
"ts-node": "^10.9.2",
|
|
74
|
+
"typescript": "^5.7.0",
|
|
75
|
+
"viem": "^2.0.0",
|
|
76
|
+
"wagmi": "^2.0.0"
|
|
77
|
+
},
|
|
13
78
|
"keywords": [
|
|
14
79
|
"ethereum",
|
|
15
|
-
"uniswap",
|
|
16
80
|
"rainbowkit",
|
|
17
81
|
"wagmi",
|
|
82
|
+
"uniswap",
|
|
18
83
|
"range-trading",
|
|
84
|
+
"bollinger-bands",
|
|
19
85
|
"defi",
|
|
20
86
|
"open-source"
|
|
21
|
-
]
|
|
22
|
-
"license": "MIT",
|
|
23
|
-
"author": "",
|
|
24
|
-
"engines": {
|
|
25
|
-
"node": ">=18"
|
|
26
|
-
}
|
|
87
|
+
]
|
|
27
88
|
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
const hre = require("hardhat");
|
|
2
|
+
|
|
3
|
+
const { ethers, network } = hre;
|
|
4
|
+
|
|
5
|
+
async function main() {
|
|
6
|
+
if (network.name === "mainnet") {
|
|
7
|
+
console.log("Deploying BandStrategy to Ethereum mainnet.");
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const [deployer] = await ethers.getSigners();
|
|
11
|
+
const BandExecutor = await ethers.getContractFactory("BandExecutor");
|
|
12
|
+
const executor = await BandExecutor.deploy(deployer.address);
|
|
13
|
+
await executor.waitForDeployment();
|
|
14
|
+
|
|
15
|
+
const executorAddress = await executor.getAddress();
|
|
16
|
+
const BandStrategy = await ethers.getContractFactory("BandStrategy");
|
|
17
|
+
const strategyContract = await BandStrategy.deploy(executorAddress);
|
|
18
|
+
await strategyContract.waitForDeployment();
|
|
19
|
+
|
|
20
|
+
const strategyContractAddress = await strategyContract.getAddress();
|
|
21
|
+
console.log(`Engine wallet/operator: ${deployer.address}`);
|
|
22
|
+
console.log(`BandExecutor deployed to: ${executorAddress}`);
|
|
23
|
+
console.log(`BandStrategy deployed to: ${strategyContractAddress}`);
|
|
24
|
+
console.log(`Strategy destination: ${await strategyContract.strategyWallet()}`);
|
|
25
|
+
console.log("Use the strategy contract address as VITE_BAND_STRATEGY_ADDRESS in your Lovable app.");
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
main().catch((error) => {
|
|
29
|
+
console.error(error);
|
|
30
|
+
process.exitCode = 1;
|
|
31
|
+
});
|
package/index.d.ts
DELETED