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.
Files changed (73) hide show
  1. package/.env.example +9 -0
  2. package/LICENSE +3 -3
  3. package/README.md +41 -12
  4. package/bin/bandkit.js +38 -0
  5. package/contracts/BandExecutor.sol +178 -0
  6. package/contracts/BandStrategy.sol +111 -0
  7. package/contracts/test/MockDexTarget.sol +29 -0
  8. package/contracts/test/MockErc20.sol +40 -0
  9. package/dist/BandContractPanel.d.ts +11 -0
  10. package/dist/BandContractPanel.d.ts.map +1 -0
  11. package/dist/BandContractPanel.js +95 -0
  12. package/dist/BandContractPanel.js.map +1 -0
  13. package/dist/BandPanel.d.ts +13 -0
  14. package/dist/BandPanel.d.ts.map +1 -0
  15. package/dist/BandPanel.js +142 -0
  16. package/dist/BandPanel.js.map +1 -0
  17. package/dist/bandExecutorAbi.d.ts +431 -0
  18. package/dist/bandExecutorAbi.d.ts.map +1 -0
  19. package/dist/bandExecutorAbi.js +558 -0
  20. package/dist/bandExecutorAbi.js.map +1 -0
  21. package/dist/bandExecutorBytecode.d.ts +2 -0
  22. package/dist/bandExecutorBytecode.d.ts.map +1 -0
  23. package/dist/bandExecutorBytecode.js +3 -0
  24. package/dist/bandExecutorBytecode.js.map +1 -0
  25. package/dist/bandStrategyAbi.d.ts +250 -0
  26. package/dist/bandStrategyAbi.d.ts.map +1 -0
  27. package/dist/bandStrategyAbi.js +323 -0
  28. package/dist/bandStrategyAbi.js.map +1 -0
  29. package/dist/bandStrategyBytecode.d.ts +2 -0
  30. package/dist/bandStrategyBytecode.d.ts.map +1 -0
  31. package/dist/bandStrategyBytecode.js +3 -0
  32. package/dist/bandStrategyBytecode.js.map +1 -0
  33. package/dist/index.d.ts +36 -0
  34. package/dist/index.d.ts.map +1 -0
  35. package/dist/index.js +36 -0
  36. package/dist/index.js.map +1 -0
  37. package/dist/rainbowkit.d.ts +7 -0
  38. package/dist/rainbowkit.d.ts.map +1 -0
  39. package/dist/rainbowkit.js +33 -0
  40. package/dist/rainbowkit.js.map +1 -0
  41. package/dist/tradingExecutor.d.ts +38 -0
  42. package/dist/tradingExecutor.d.ts.map +1 -0
  43. package/dist/tradingExecutor.js +125 -0
  44. package/dist/tradingExecutor.js.map +1 -0
  45. package/dist/useBandDashboard.d.ts +27 -0
  46. package/dist/useBandDashboard.d.ts.map +1 -0
  47. package/dist/useBandDashboard.js +35 -0
  48. package/dist/useBandDashboard.js.map +1 -0
  49. package/dist/useEthPriceTicker.d.ts +32 -0
  50. package/dist/useEthPriceTicker.d.ts.map +1 -0
  51. package/dist/useEthPriceTicker.js +95 -0
  52. package/dist/useEthPriceTicker.js.map +1 -0
  53. package/dist/useStrategyContract.d.ts +42 -0
  54. package/dist/useStrategyContract.d.ts.map +1 -0
  55. package/dist/useStrategyContract.js +258 -0
  56. package/dist/useStrategyContract.js.map +1 -0
  57. package/dist/useStrategyContractDeployment.d.ts +31 -0
  58. package/dist/useStrategyContractDeployment.d.ts.map +1 -0
  59. package/dist/useStrategyContractDeployment.js +229 -0
  60. package/dist/useStrategyContractDeployment.js.map +1 -0
  61. package/dist/useWalletEthBalance.d.ts +17 -0
  62. package/dist/useWalletEthBalance.d.ts.map +1 -0
  63. package/dist/useWalletEthBalance.js +98 -0
  64. package/dist/useWalletEthBalance.js.map +1 -0
  65. package/engine/browser-provider.js +25 -0
  66. package/engine/executor-searcher.js +138 -0
  67. package/engine/provider.js +32 -0
  68. package/engine/uniswap-v2-roundtrip.js +215 -0
  69. package/hardhat.config.cjs +27 -0
  70. package/package.json +75 -14
  71. package/scripts/deploy.cjs +31 -0
  72. package/index.d.ts +0 -2
  73. package/index.js +0 -2
package/.env.example ADDED
@@ -0,0 +1,9 @@
1
+ MAINNET_RPC_URL=https://eth-mainnet.g.alchemy.com/v2/YOUR_KEY
2
+ DEPLOYER_PRIVATE_KEY=0xyour_deployer_private_key_without_funds_you_cannot_afford_to_risk
3
+ ENGINE_RPC_URL=https://ethereum.publicnode.com
4
+ ENGINE_PRIVATE_KEY=0xyour_engine_wallet_private_key_without_funds_you_cannot_afford_to_risk
5
+ EXECUTOR_ADDRESS=0xYourBandExecutor
6
+ TRADE_TOKEN=0xTokenToTrade
7
+ TRADE_VALUE_ETH=0.01
8
+ MIN_PROFIT_ETH=0
9
+ SLIPPAGE_BPS=50
package/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
- MIT License
1
+ MIT License
2
2
 
3
- Copyright (c) 2026 [TO_FILL]
3
+ Copyright (c) 2026
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
@@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
18
  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
19
  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
20
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,22 +1,51 @@
1
1
  # bandkit
2
2
 
3
- Open-source scaffolding for building Ethereum band-bound trading strategies.
3
+ Open-source scaffolding for building Ethereum range-bound trading strategies.
4
4
 
5
- > **This is a placeholder release.** The full package is in active development and will publish as `0.1.0`. Do not depend on this `0.0.1` release for anything other than reserving the name.
5
+ ## Install
6
6
 
7
- ## What it will be
7
+ ```bash
8
+ npm install bandkit wagmi viem @rainbow-me/rainbowkit @tanstack/react-query
9
+ ```
8
10
 
9
- `bandkit` is scaffolding for deploying a strategy contract, funding it, and routing pre-approved swap calldata through a restricted executor on Ethereum. It will provide React hooks for wallet, deployment, and engine state — designed to pair with `rainbowkit` and `wagmi`.
11
+ ## Lovable Prompt
10
12
 
11
- It will not include a trading strategy, signal generation, or risk management. The integrator brings the logic.
13
+ Paste this into Lovable when wiring the package into an existing RainbowKit/wagmi app:
12
14
 
13
- ## Coming in 0.1.0
15
+ ```text
16
+ Install bandkit with wagmi, viem, @rainbow-me/rainbowkit, and @tanstack/react-query.
14
17
 
15
- - Solidity strategy contract and restricted executor
16
- - React hooks (`useRangeDashboard`, `useStrategyContract`, etc.)
17
- - RainbowKit/wagmi config helper
18
- - Engine execution scaffold for owner-approved swap routes
18
+ Use the existing RainbowKit and wagmi providers if they already exist. Import createBandKitConfig and BandPanel from bandkit. If the app does not already have providers, create a wagmi config with createBandKitConfig({ appName: "BandKit" }), wrap the app in WagmiProvider, QueryClientProvider, and RainbowKitProvider, and render RainbowKit's ConnectButton above the panel.
19
19
 
20
- ## License
20
+ Render <BandPanel /> for the working wallet flow: connect wallet, deploy strategy contract, fund contract, activate engine, pause engine, and withdraw available ETH. Do not add strategy logic, indicators, charts, autonomous execution, or return claims. bandkit is execution scaffolding only; the integrator supplies the trading logic.
21
+ ```
21
22
 
22
- MIT
23
+ ## Minimal React Usage
24
+
25
+ ```tsx
26
+ import "@rainbow-me/rainbowkit/styles.css";
27
+ import { ConnectButton, RainbowKitProvider } from "@rainbow-me/rainbowkit";
28
+ import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
29
+ import { WagmiProvider } from "wagmi";
30
+ import { BandPanel, createBandKitConfig } from "bandkit";
31
+
32
+ const queryClient = new QueryClient();
33
+ const config = createBandKitConfig({ appName: "BandKit" });
34
+
35
+ export function App() {
36
+ return (
37
+ <WagmiProvider config={config}>
38
+ <QueryClientProvider client={queryClient}>
39
+ <RainbowKitProvider>
40
+ <ConnectButton />
41
+ <BandPanel />
42
+ </RainbowKitProvider>
43
+ </QueryClientProvider>
44
+ </WagmiProvider>
45
+ );
46
+ }
47
+ ```
48
+
49
+ ## Scope
50
+
51
+ bandkit provides strategy contract deployment, contract funding, engine activation state, a restricted executor, and React hooks/components for the dashboard flow. It does not include trading strategy logic, signal generation, risk management, backtesting, or profit guarantees.
package/bin/bandkit.js ADDED
@@ -0,0 +1,38 @@
1
+ #!/usr/bin/env node
2
+
3
+ const command = process.argv[2] ?? "help";
4
+
5
+ if (command === "help" || command === "--help" || command === "-h") {
6
+ console.log(`
7
+ bandkit
8
+
9
+ Commands:
10
+ help Show this message
11
+ init Print the minimal Lovable integration checklist
12
+
13
+ Mainnet deploy:
14
+ npm install
15
+ cp .env.example .env
16
+ npm run compile:contracts
17
+ npx hardhat run scripts/deploy.cjs --network mainnet
18
+
19
+ Lovable install:
20
+ npm install bandkit wagmi viem @rainbow-me/rainbowkit @tanstack/react-query
21
+ `);
22
+ process.exit(0);
23
+ }
24
+
25
+ if (command === "init") {
26
+ console.log(`
27
+ 1. Deploy BandExecutor with your engine/operator wallet.
28
+ 2. Deploy contracts/BandStrategy.sol with the BandExecutor address as the strategy destination.
29
+ 3. Add VITE_BAND_STRATEGY_ADDRESS=<deployed_mainnet_address> to Lovable.
30
+ 4. Wrap your app in WagmiProvider, QueryClientProvider, and RainbowKitProvider.
31
+ 5. Render <BandPanel strategyContractAddress={import.meta.env.VITE_BAND_STRATEGY_ADDRESS} /> for Fund/Activate/Pause/Withdraw.
32
+ 6. Run npm run engine:simulate before npm run engine:execute.
33
+ `);
34
+ process.exit(0);
35
+ }
36
+
37
+ console.error(`Unknown command: ${command}`);
38
+ process.exit(1);
@@ -0,0 +1,178 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.24;
3
+
4
+ /// @title BandExecutor
5
+ /// @notice Restricted executor for off-chain simulated execution. It only calls owner-approved targets.
6
+ contract BandExecutor {
7
+ address public immutable owner;
8
+ address payable public immutable engineWallet;
9
+ bool public paused;
10
+
11
+ mapping(address target => bool approved) public approvedTargets;
12
+ mapping(address target => bytes32 codeHash) public approvedTargetCodeHash;
13
+ mapping(address target => mapping(bytes4 selector => bool approved)) public approvedSelectors;
14
+ mapping(address token => bool approved) public approvedTokens;
15
+
16
+ uint256 private locked = 1;
17
+
18
+ event ApprovedTargetSet(address indexed target, bool approved);
19
+ event ApprovedSelectorSet(address indexed target, bytes4 indexed selector, bool approved);
20
+ event ApprovedTokenSet(address indexed token, bool approved);
21
+ event TokenApprovalSet(address indexed token, address indexed spender, uint256 amount);
22
+ event PausedSet(bool paused);
23
+ event ExecutionSubmitted(address indexed operator, address indexed target, uint256 value, bytes32 calldataHash, uint256 balanceAfter);
24
+ event WithdrawnToEngine(uint256 amount);
25
+
26
+ error NotOwner();
27
+ error NotEngineOperator();
28
+ error Paused();
29
+ error ReentrantCall();
30
+ error TargetNotApproved();
31
+ error TargetCodeHashChanged();
32
+ error SelectorNotApproved();
33
+ error EmptyPayload();
34
+ error TokenNotApproved();
35
+ error InvalidArrayLength();
36
+ error NoTargetCode();
37
+ error ZeroAddress();
38
+ error ExternalCallFailed(bytes returnData);
39
+ error MinBalanceNotMet(uint256 balanceAfter, uint256 minBalanceAfter);
40
+ error EthTransferFailed();
41
+
42
+ modifier onlyOwner() {
43
+ if (msg.sender != owner) revert NotOwner();
44
+ _;
45
+ }
46
+
47
+ modifier onlyEngineOperator() {
48
+ if (msg.sender != owner && msg.sender != engineWallet) revert NotEngineOperator();
49
+ _;
50
+ }
51
+
52
+ modifier whenNotPaused() {
53
+ if (paused) revert Paused();
54
+ _;
55
+ }
56
+
57
+ modifier nonReentrant() {
58
+ if (locked != 1) revert ReentrantCall();
59
+ locked = 2;
60
+ _;
61
+ locked = 1;
62
+ }
63
+
64
+ constructor(address payable engineWallet_) {
65
+ if (engineWallet_ == address(0)) revert ZeroAddress();
66
+ owner = msg.sender;
67
+ engineWallet = engineWallet_;
68
+ }
69
+
70
+ receive() external payable {}
71
+
72
+ function setApprovedTarget(address target, bool approved) external onlyOwner {
73
+ if (target == address(0)) revert ZeroAddress();
74
+ if (approved && target.code.length == 0) revert NoTargetCode();
75
+
76
+ approvedTargets[target] = approved;
77
+ approvedTargetCodeHash[target] = approved ? target.codehash : bytes32(0);
78
+ emit ApprovedTargetSet(target, approved);
79
+ }
80
+
81
+ function setApprovedSelector(address target, bytes4 selector, bool approved) external onlyOwner {
82
+ ensureApprovedTarget(target);
83
+ approvedSelectors[target][selector] = approved;
84
+ emit ApprovedSelectorSet(target, selector, approved);
85
+ }
86
+
87
+ function ensureApprovedTarget(address target) internal view {
88
+ if (!approvedTargets[target]) revert TargetNotApproved();
89
+ if (target.code.length == 0) revert NoTargetCode();
90
+ if (target.codehash != approvedTargetCodeHash[target]) revert TargetCodeHashChanged();
91
+ }
92
+
93
+ function ensureApprovedPayload(address target, bytes calldata data) internal view {
94
+ if (data.length < 4) revert EmptyPayload();
95
+
96
+ bytes4 selector;
97
+ assembly {
98
+ selector := calldataload(data.offset)
99
+ }
100
+
101
+ if (!approvedSelectors[target][selector]) revert SelectorNotApproved();
102
+ }
103
+
104
+ function setApprovedToken(address token, bool approved) external onlyOwner {
105
+ if (token == address(0)) revert ZeroAddress();
106
+ approvedTokens[token] = approved;
107
+ emit ApprovedTokenSet(token, approved);
108
+ }
109
+
110
+ function setPaused(bool paused_) external onlyOwner {
111
+ paused = paused_;
112
+ emit PausedSet(paused_);
113
+ }
114
+
115
+ function approveToken(address token, address spender, uint256 amount) external onlyEngineOperator whenNotPaused {
116
+ if (!approvedTokens[token]) revert TokenNotApproved();
117
+ ensureApprovedTarget(spender);
118
+
119
+ (bool ok, bytes memory result) = token.call(
120
+ abi.encodeWithSignature("approve(address,uint256)", spender, amount)
121
+ );
122
+ if (!ok) revert ExternalCallFailed(result);
123
+
124
+ emit TokenApprovalSet(token, spender, amount);
125
+ }
126
+
127
+ function executeTrade(
128
+ address target,
129
+ uint256 value,
130
+ bytes calldata data,
131
+ uint256 minBalanceAfter
132
+ ) external payable onlyEngineOperator whenNotPaused nonReentrant returns (bytes memory returnData) {
133
+ ensureApprovedTarget(target);
134
+ ensureApprovedPayload(target, data);
135
+
136
+ (bool ok, bytes memory result) = target.call{ value: value }(data);
137
+ if (!ok) revert ExternalCallFailed(result);
138
+
139
+ uint256 balanceAfter = address(this).balance;
140
+ if (balanceAfter < minBalanceAfter) revert MinBalanceNotMet(balanceAfter, minBalanceAfter);
141
+
142
+ emit ExecutionSubmitted(msg.sender, target, value, keccak256(data), balanceAfter);
143
+ return result;
144
+ }
145
+
146
+ function executeBatch(
147
+ address[] calldata targets,
148
+ uint256[] calldata values,
149
+ bytes[] calldata payloads,
150
+ uint256 minBalanceAfter
151
+ ) external payable onlyEngineOperator whenNotPaused nonReentrant returns (bytes[] memory returnData) {
152
+ if (targets.length != values.length || targets.length != payloads.length) revert InvalidArrayLength();
153
+
154
+ returnData = new bytes[](targets.length);
155
+
156
+ for (uint256 i = 0; i < targets.length; i++) {
157
+ address target = targets[i];
158
+ ensureApprovedTarget(target);
159
+ ensureApprovedPayload(target, payloads[i]);
160
+
161
+ (bool ok, bytes memory result) = target.call{ value: values[i] }(payloads[i]);
162
+ if (!ok) revert ExternalCallFailed(result);
163
+
164
+ returnData[i] = result;
165
+ emit ExecutionSubmitted(msg.sender, target, values[i], keccak256(payloads[i]), address(this).balance);
166
+ }
167
+
168
+ uint256 balanceAfter = address(this).balance;
169
+ if (balanceAfter < minBalanceAfter) revert MinBalanceNotMet(balanceAfter, minBalanceAfter);
170
+ }
171
+
172
+ function withdrawToEngine(uint256 amount) external onlyEngineOperator nonReentrant {
173
+ (bool ok, ) = engineWallet.call{ value: amount }("");
174
+ if (!ok) revert EthTransferFailed();
175
+
176
+ emit WithdrawnToEngine(amount);
177
+ }
178
+ }
@@ -0,0 +1,111 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.24;
3
+
4
+ /// @title BandStrategy
5
+ /// @notice ETH strategy contract. Users can fund the contract, withdraw available funds, or allocate available funds to the configured strategy wallet or executor.
6
+ contract BandStrategy {
7
+ address public immutable owner;
8
+ address payable public immutable strategyWallet;
9
+ bool public depositsPaused;
10
+ uint256 public totalDeposits;
11
+ uint256 public totalAllocatedToStrategy;
12
+
13
+ mapping(address user => uint256 balance) public balances;
14
+ mapping(address user => bool enabled) public strategyActive;
15
+ mapping(address user => uint256 amount) public allocatedToStrategy;
16
+
17
+ uint256 private locked = 1;
18
+
19
+ event Deposited(address indexed user, uint256 amount);
20
+ event Withdrawn(address indexed user, uint256 amount);
21
+ event StrategyEngineActivated(address indexed user, uint256 amount);
22
+ event StrategyEngineDeactivated(address indexed user);
23
+ event DepositsPausedSet(bool paused);
24
+
25
+ error ZeroAmount();
26
+ error DepositsPaused();
27
+ error InsufficientBalance();
28
+ error EthTransferFailed();
29
+ error NotOwner();
30
+ error NoStrategyContractBalance();
31
+ error ReentrantCall();
32
+ error StrategyAlreadyActive();
33
+ error ZeroAddress();
34
+
35
+ modifier onlyOwner() {
36
+ if (msg.sender != owner) revert NotOwner();
37
+ _;
38
+ }
39
+
40
+ modifier nonReentrant() {
41
+ if (locked != 1) revert ReentrantCall();
42
+ locked = 2;
43
+ _;
44
+ locked = 1;
45
+ }
46
+
47
+ constructor(address payable strategyWallet_) {
48
+ if (strategyWallet_ == address(0)) revert ZeroAddress();
49
+ owner = msg.sender;
50
+ strategyWallet = strategyWallet_;
51
+ }
52
+
53
+ receive() external payable {
54
+ deposit();
55
+ }
56
+
57
+ function deposit() public payable {
58
+ if (depositsPaused) revert DepositsPaused();
59
+ if (msg.value == 0) revert ZeroAmount();
60
+
61
+ balances[msg.sender] += msg.value;
62
+ totalDeposits += msg.value;
63
+
64
+ emit Deposited(msg.sender, msg.value);
65
+ }
66
+
67
+ function withdraw(uint256 amount) public nonReentrant {
68
+ if (amount == 0) revert ZeroAmount();
69
+ if (balances[msg.sender] < amount) revert InsufficientBalance();
70
+
71
+ strategyActive[msg.sender] = false;
72
+ balances[msg.sender] -= amount;
73
+ totalDeposits -= amount;
74
+
75
+ (bool ok, ) = payable(msg.sender).call{value: amount}("");
76
+ if (!ok) revert EthTransferFailed();
77
+
78
+ emit Withdrawn(msg.sender, amount);
79
+ }
80
+
81
+ function withdrawAll() external {
82
+ withdraw(balances[msg.sender]);
83
+ }
84
+
85
+ function activateStrategyEngine() public nonReentrant {
86
+ uint256 amount = balances[msg.sender];
87
+ if (amount == 0) revert NoStrategyContractBalance();
88
+ if (strategyActive[msg.sender]) revert StrategyAlreadyActive();
89
+
90
+ balances[msg.sender] = 0;
91
+ totalDeposits -= amount;
92
+ strategyActive[msg.sender] = true;
93
+ allocatedToStrategy[msg.sender] += amount;
94
+ totalAllocatedToStrategy += amount;
95
+
96
+ (bool ok, ) = strategyWallet.call{value: amount}("");
97
+ if (!ok) revert EthTransferFailed();
98
+
99
+ emit StrategyEngineActivated(msg.sender, amount);
100
+ }
101
+
102
+ function deactivateStrategyEngine() public {
103
+ strategyActive[msg.sender] = false;
104
+ emit StrategyEngineDeactivated(msg.sender);
105
+ }
106
+
107
+ function setDepositsPaused(bool paused) external onlyOwner {
108
+ depositsPaused = paused;
109
+ emit DepositsPausedSet(paused);
110
+ }
111
+ }
@@ -0,0 +1,29 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.24;
3
+
4
+ contract MockDexTarget {
5
+ event Swapped(address indexed sender, uint256 value, uint256 profit);
6
+
7
+ receive() external payable {}
8
+
9
+ function swapWithProfit(uint256 profit) external payable {
10
+ emit Swapped(msg.sender, msg.value, profit);
11
+
12
+ if (profit > 0) {
13
+ (bool ok, ) = msg.sender.call{ value: msg.value + profit }("");
14
+ require(ok, "profit transfer failed");
15
+ }
16
+ }
17
+
18
+ function alwaysRevert() external pure {
19
+ revert("mock revert");
20
+ }
21
+
22
+ function pullToken(address token, uint256 amount) external {
23
+ (bool ok, bytes memory result) = token.call(
24
+ abi.encodeWithSignature("transferFrom(address,address,uint256)", msg.sender, address(this), amount)
25
+ );
26
+
27
+ require(ok && (result.length == 0 || abi.decode(result, (bool))), "token pull failed");
28
+ }
29
+ }
@@ -0,0 +1,40 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.24;
3
+
4
+ contract MockErc20 {
5
+ string public name = "Mock Token";
6
+ string public symbol = "MOCK";
7
+ uint8 public decimals = 18;
8
+ uint256 public totalSupply;
9
+
10
+ mapping(address account => uint256 balance) public balanceOf;
11
+ mapping(address owner => mapping(address spender => uint256 amount)) public allowance;
12
+
13
+ event Approval(address indexed owner, address indexed spender, uint256 amount);
14
+ event Transfer(address indexed from, address indexed to, uint256 amount);
15
+
16
+ function mint(address to, uint256 amount) external {
17
+ totalSupply += amount;
18
+ balanceOf[to] += amount;
19
+ emit Transfer(address(0), to, amount);
20
+ }
21
+
22
+ function approve(address spender, uint256 amount) external returns (bool) {
23
+ allowance[msg.sender][spender] = amount;
24
+ emit Approval(msg.sender, spender, amount);
25
+ return true;
26
+ }
27
+
28
+ function transferFrom(address from, address to, uint256 amount) external returns (bool) {
29
+ uint256 allowed = allowance[from][msg.sender];
30
+ require(allowed >= amount, "allowance");
31
+ require(balanceOf[from] >= amount, "balance");
32
+
33
+ allowance[from][msg.sender] = allowed - amount;
34
+ balanceOf[from] -= amount;
35
+ balanceOf[to] += amount;
36
+
37
+ emit Transfer(from, to, amount);
38
+ return true;
39
+ }
40
+ }
@@ -0,0 +1,11 @@
1
+ import type { Address } from "viem";
2
+ import { type StrategyContractAction } from "./useStrategyContract.js";
3
+ export type BandContractPanelProps = {
4
+ strategyContractAddress?: Address;
5
+ chainId?: number;
6
+ title?: string;
7
+ className?: string;
8
+ onSubmitted?: (hash: string, action: StrategyContractAction) => void;
9
+ };
10
+ export declare function BandContractPanel({ strategyContractAddress, chainId, title, className, onSubmitted }: BandContractPanelProps): import("react/jsx-runtime").JSX.Element;
11
+ //# sourceMappingURL=BandContractPanel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BandContractPanel.d.ts","sourceRoot":"","sources":["../src/BandContractPanel.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAEpC,OAAO,EAAuB,KAAK,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAE5F,MAAM,MAAM,sBAAsB,GAAG;IACnC,uBAAuB,CAAC,EAAE,OAAO,CAAC;IAClC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,sBAAsB,KAAK,IAAI,CAAC;CACtE,CAAC;AAoBF,wBAAgB,iBAAiB,CAAC,EAChC,uBAAuB,EACvB,OAAoB,EACpB,KAA2B,EAC3B,SAAS,EACT,WAAW,EACZ,EAAE,sBAAsB,2CAyJxB"}
@@ -0,0 +1,95 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useMemo, useState } from "react";
3
+ import { mainnet } from "wagmi/chains";
4
+ import { useStrategyContract } from "./useStrategyContract.js";
5
+ const buttonStyle = {
6
+ border: "1px solid #3b82f6",
7
+ borderRadius: 6,
8
+ background: "#2563eb",
9
+ color: "#ffffff",
10
+ cursor: "pointer",
11
+ fontWeight: 700,
12
+ minHeight: 42,
13
+ padding: "0 14px"
14
+ };
15
+ const secondaryButtonStyle = {
16
+ ...buttonStyle,
17
+ background: "#111827",
18
+ border: "1px solid #334155",
19
+ color: "#e5e7eb"
20
+ };
21
+ export function BandContractPanel({ strategyContractAddress, chainId = mainnet.id, title = "Strategy Contract", className, onSubmitted }) {
22
+ const [amount, setAmount] = useState("");
23
+ const [mode, setMode] = useState("deposit");
24
+ const [localError, setLocalError] = useState();
25
+ const strategyContract = useStrategyContract({ strategyContractAddress, chainId });
26
+ const status = useMemo(() => {
27
+ if (!strategyContractAddress)
28
+ return "Strategy contract address missing";
29
+ if (!strategyContract.isConnected)
30
+ return "Connect wallet";
31
+ if (!strategyContract.isCorrectChain)
32
+ return "Switch to Ethereum mainnet";
33
+ if (strategyContract.depositsPaused && mode === "deposit")
34
+ return "Deposits paused";
35
+ if (strategyContract.transactionStatus !== "idle")
36
+ return strategyContract.transactionStatusText;
37
+ return "Ready";
38
+ }, [
39
+ mode,
40
+ strategyContract.depositsPaused,
41
+ strategyContract.isConnected,
42
+ strategyContract.isCorrectChain,
43
+ strategyContract.transactionStatus,
44
+ strategyContract.transactionStatusText,
45
+ strategyContractAddress
46
+ ]);
47
+ const isBusy = strategyContract.isWritePending || strategyContract.isConfirming;
48
+ const canSubmit = Boolean(strategyContractAddress) &&
49
+ strategyContract.isConnected &&
50
+ !isBusy &&
51
+ (mode === "withdraw" || !strategyContract.depositsPaused);
52
+ async function submit(event) {
53
+ event.preventDefault();
54
+ setLocalError(undefined);
55
+ try {
56
+ const hash = mode === "deposit" ? await strategyContract.depositEth(amount) : await strategyContract.withdrawEth(amount);
57
+ setAmount("");
58
+ onSubmitted?.(hash, mode);
59
+ }
60
+ catch (error) {
61
+ setLocalError(error instanceof Error ? error.message : String(error));
62
+ }
63
+ }
64
+ async function withdrawAll() {
65
+ setLocalError(undefined);
66
+ try {
67
+ const hash = await strategyContract.withdrawAll();
68
+ setAmount("");
69
+ onSubmitted?.(hash, "withdrawAll");
70
+ }
71
+ catch (error) {
72
+ setLocalError(error instanceof Error ? error.message : String(error));
73
+ }
74
+ }
75
+ return (_jsxs("section", { className: className, style: {
76
+ background: "#020617",
77
+ border: "1px solid #1e293b",
78
+ borderRadius: 8,
79
+ boxShadow: "0 18px 60px rgba(2, 6, 23, 0.35)",
80
+ color: "#e5e7eb",
81
+ display: "grid",
82
+ gap: 16,
83
+ maxWidth: 460,
84
+ padding: 18
85
+ }, children: [_jsxs("header", { style: { display: "grid", gap: 6 }, children: [_jsx("h2", { style: { fontSize: 18, lineHeight: 1.2, margin: 0 }, children: title }), _jsxs("div", { style: { color: "#94a3b8", fontSize: 14 }, children: ["Status: ", status] })] }), _jsxs("div", { style: { display: "grid", gap: 8 }, children: [_jsxs("div", { style: { display: "flex", justifyContent: "space-between", gap: 12 }, children: [_jsx("span", { children: "Your contract balance" }), _jsxs("strong", { children: [strategyContract.balanceEth, " ETH"] })] }), _jsxs("div", { style: { display: "flex", justifyContent: "space-between", gap: 12 }, children: [_jsx("span", { children: "Total contract deposits" }), _jsxs("strong", { children: [strategyContract.totalDepositsEth, " ETH"] })] })] }), _jsxs("div", { style: { display: "grid", gridTemplateColumns: "1fr 1fr", gap: 8 }, children: [_jsx("button", { type: "button", onClick: () => setMode("deposit"), style: mode === "deposit" ? buttonStyle : secondaryButtonStyle, children: "Deposit" }), _jsx("button", { type: "button", onClick: () => setMode("withdraw"), style: mode === "withdraw" ? buttonStyle : secondaryButtonStyle, children: "Withdraw" })] }), _jsxs("form", { onSubmit: submit, style: { display: "grid", gap: 10 }, children: [_jsxs("label", { style: { display: "grid", gap: 6, fontSize: 14, fontWeight: 700 }, children: ["Amount ETH", _jsx("input", { inputMode: "decimal", min: "0", onChange: (event) => setAmount(event.target.value), placeholder: "0.01", step: "any", style: {
86
+ background: "#0f172a",
87
+ border: "1px solid #334155",
88
+ borderRadius: 6,
89
+ color: "#e5e7eb",
90
+ font: "inherit",
91
+ minHeight: 42,
92
+ padding: "0 12px"
93
+ }, type: "number", value: amount })] }), _jsx("button", { disabled: !canSubmit, style: { ...buttonStyle, opacity: canSubmit ? 1 : 0.5 }, type: "submit", children: mode === "deposit" ? "Deposit ETH" : "Withdraw ETH" }), mode === "withdraw" ? (_jsx("button", { disabled: !canSubmit || strategyContract.balanceWei === 0n, onClick: withdrawAll, style: secondaryButtonStyle, type: "button", children: "Withdraw All" })) : null] }), strategyContract.pendingHash ? (_jsx("a", { href: `https://etherscan.io/tx/${strategyContract.pendingHash}`, rel: "noreferrer", style: { color: "#60a5fa", fontSize: 14, overflowWrap: "anywhere" }, target: "_blank", children: "View transaction" })) : null, localError || strategyContract.error ? (_jsx("div", { style: { color: "#fca5a5", fontSize: 14 }, children: localError ?? strategyContract.error?.message })) : null] }));
94
+ }
95
+ //# sourceMappingURL=BandContractPanel.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BandContractPanel.js","sourceRoot":"","sources":["../src/BandContractPanel.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ import type { Address } from "viem";
2
+ import type { StrategyContractAction } from "./useStrategyContract.js";
3
+ export type BandPanelProps = {
4
+ strategyContractAddress?: Address;
5
+ strategyWalletAddress?: Address;
6
+ chainId?: number;
7
+ className?: string;
8
+ title?: string;
9
+ onSubmitted?: (hash: string, action: StrategyContractAction) => void;
10
+ onStrategyContractDeployed?: (address: Address) => void;
11
+ };
12
+ export declare function BandPanel({ strategyContractAddress, strategyWalletAddress, chainId, className, title, onSubmitted, onStrategyContractDeployed }: BandPanelProps): import("react/jsx-runtime").JSX.Element;
13
+ //# sourceMappingURL=BandPanel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BandPanel.d.ts","sourceRoot":"","sources":["../src/BandPanel.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAGpC,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAEvE,MAAM,MAAM,cAAc,GAAG;IAC3B,uBAAuB,CAAC,EAAE,OAAO,CAAC;IAClC,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,sBAAsB,KAAK,IAAI,CAAC;IACrE,0BAA0B,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC;CACzD,CAAC;AAiCF,wBAAgB,SAAS,CAAC,EACxB,uBAAuB,EACvB,qBAAqB,EACrB,OAAoB,EACpB,SAAS,EACT,KAAwB,EACxB,WAAW,EACX,0BAA0B,EAC3B,EAAE,cAAc,2CAsPhB"}