Skip to content

Commit 2dd09c5

Browse files
committed
Delegate EOA to SolverAccount using UserOp
- Delegate the filler EOA to the SolverAccount contract via EIP-7702 and submit a no-op UserOp through the bundler to activate delegation - Use Circle Paymaster (USDC permit, EIP-2612) to pay gas in USDC on supported chains; fall back to EntryPoint native-token deposit where Circle is not deployed (e.g. BSC) - remove aerodrome support - clamp filler output for safety: cap computed output at user-requested × (1 + maxOverfillBps), reject fx orders where output USD ≥ input USD, and halt after N consecutive clamp activations; defaults 100 bps / 3 via [simplex.overfillProtection] in filler config
1 parent 837f28b commit 2dd09c5

36 files changed

Lines changed: 1333 additions & 1282 deletions

evm/pnpm-lock.yaml

Lines changed: 16 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
// SPDX-License-Identifier: UNLICENSED
2+
pragma solidity ^0.8.17;
3+
4+
import "forge-std/Script.sol";
5+
import "stringutils/strings.sol";
6+
7+
import {IntentGatewayV2, Params} from "../src/apps/IntentGatewayV2.sol";
8+
import {SolverAccount} from "../src/utils/SolverAccount.sol";
9+
import {CallDispatcher} from "../src/utils/CallDispatcher.sol";
10+
import {VWAPOracle} from "../src/utils/VWAPOracle.sol";
11+
import {BaseScript} from "./BaseScript.sol";
12+
13+
contract DeployScript is BaseScript {
14+
using strings for *;
15+
16+
function deploy() internal override {
17+
// Load optional configuration from environment
18+
bool solverSelection = vm.envOr("SOLVER_SELECTION", false);
19+
uint256 surplusShareBps = vm.envOr("SURPLUS_SHARE_BPS", uint256(5000)); // 50% default
20+
uint256 protocolFeeBps = vm.envOr("PROTOCOL_FEE_BPS", uint256(0)); // 0% default
21+
22+
// Check if we should deploy CallDispatcher and VWAPOracle
23+
bool deployCallDispatcher = vm.envOr("DEPLOY_CALL_DISPATCHER", true);
24+
bool deployVWAPOracle = vm.envOr("DEPLOY_VWAP_ORACLE", true);
25+
26+
// Deploy CallDispatcher if needed
27+
address callDispatcherAddr;
28+
if (deployCallDispatcher) {
29+
CallDispatcher callDispatcher = new CallDispatcher{salt: salt}();
30+
callDispatcherAddr = address(callDispatcher);
31+
console.log("CallDispatcher deployed at:", callDispatcherAddr);
32+
} else {
33+
callDispatcherAddr = config.get("CALL_DISPATCHER").toAddress();
34+
console.log("Using existing CallDispatcher at:", callDispatcherAddr);
35+
}
36+
37+
// Deploy VWAPOracle (Price Oracle) if needed
38+
address vwapOracleAddr;
39+
if (deployVWAPOracle) {
40+
VWAPOracle vwapOracle = new VWAPOracle{salt: salt}(admin);
41+
vwapOracleAddr = address(vwapOracle);
42+
console.log("VWAPOracle deployed at:", vwapOracleAddr);
43+
} else {
44+
vwapOracleAddr = vm.envOr("PRICE_ORACLE", address(0));
45+
console.log("Using existing VWAPOracle at:", vwapOracleAddr);
46+
}
47+
48+
// Deploy IntentGatewayV2
49+
IntentGatewayV2 intentGatewayV2 = new IntentGatewayV2{salt: salt}(admin);
50+
console.log("IntentGatewayV2 deployed at:", address(intentGatewayV2));
51+
52+
// Set parameters on IntentGatewayV2
53+
intentGatewayV2.setParams(
54+
Params({
55+
host: HOST_ADDRESS,
56+
dispatcher: callDispatcherAddr,
57+
solverSelection: solverSelection,
58+
surplusShareBps: surplusShareBps,
59+
protocolFeeBps: protocolFeeBps,
60+
priceOracle: vwapOracleAddr
61+
})
62+
);
63+
64+
// Deploy SolverAccount
65+
SolverAccount solverAccount = new SolverAccount{salt: salt}(address(intentGatewayV2));
66+
console.log("SolverAccount deployed at:", address(solverAccount));
67+
68+
// Update config
69+
if (deployCallDispatcher) {
70+
config.set("CALL_DISPATCHER", callDispatcherAddr);
71+
}
72+
if (deployVWAPOracle) {
73+
config.set("VWAP_ORACLE", vwapOracleAddr);
74+
}
75+
config.set("INTENT_GATEWAY_V2", address(intentGatewayV2));
76+
config.set("SOLVER_ACCOUNT", address(solverAccount));
77+
78+
console.log("");
79+
console.log("=== Deployment Summary ===");
80+
console.log("CallDispatcher:", callDispatcherAddr);
81+
console.log("VWAPOracle:", vwapOracleAddr);
82+
console.log("IntentGatewayV2:", address(intentGatewayV2));
83+
console.log("SolverAccount:", address(solverAccount));
84+
85+
if (deployVWAPOracle) {
86+
console.log("");
87+
console.log("=== IMPORTANT: Post-deployment step ===");
88+
console.log("VWAPOracle needs initialization. Call VWAPOracle.init() with:");
89+
console.log(" - hostAddr:", HOST_ADDRESS);
90+
console.log(" - updates: TokenDecimalsUpdate[] for token decimals on remote chains");
91+
}
92+
}
93+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// SPDX-License-Identifier: UNLICENSED
2+
pragma solidity ^0.8.17;
3+
4+
import "forge-std/Script.sol";
5+
import "stringutils/strings.sol";
6+
7+
import {SolverAccount} from "../src/utils/SolverAccount.sol";
8+
import {BaseScript} from "./BaseScript.sol";
9+
10+
contract DeployScript is BaseScript {
11+
using strings for *;
12+
13+
function deploy() internal override {
14+
// Get IntentGatewayV2 address from config
15+
address intentGatewayV2 = config.get("INTENT_GATEWAY_V2").toAddress();
16+
17+
// Deploy SolverAccount
18+
SolverAccount solverAccount = new SolverAccount{salt: salt}(intentGatewayV2);
19+
20+
console.log("SolverAccount deployed at:", address(solverAccount));
21+
console.log(" IntentGatewayV2:", intentGatewayV2);
22+
23+
// Update config
24+
config.set("SOLVER_ACCOUNT", address(solverAccount));
25+
}
26+
}

evm/script/InitVWAPOracle.s.sol

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// SPDX-License-Identifier: UNLICENSED
2+
pragma solidity ^0.8.17;
3+
4+
import "forge-std/Script.sol";
5+
import "stringutils/strings.sol";
6+
7+
import {VWAPOracle} from "../src/utils/VWAPOracle.sol";
8+
import {BaseScript} from "./BaseScript.sol";
9+
10+
contract InitVWAPOracleScript is BaseScript {
11+
using strings for *;
12+
13+
function deploy() internal override {
14+
address vwapOracleAddr = config.get("VWAP_ORACLE").toAddress();
15+
address intentGatewayAddr = config.get("INTENT_GATEWAY_V2").toAddress();
16+
17+
VWAPOracle vwapOracle = VWAPOracle(vwapOracleAddr);
18+
19+
// Initialize with empty token decimals for now
20+
// Token decimals can be updated later via Hyperbridge governance
21+
VWAPOracle.TokenDecimalsUpdate[] memory updates = new VWAPOracle.TokenDecimalsUpdate[](0);
22+
23+
vwapOracle.init(HOST_ADDRESS, intentGatewayAddr, updates);
24+
25+
console.log("VWAPOracle initialized at:", vwapOracleAddr);
26+
console.log(" host:", HOST_ADDRESS);
27+
}
28+
}

evm/script/verify-contracts.sh

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
#!/bin/bash
2+
3+
# Load environment
4+
source .env.testnet
5+
6+
# Deployed addresses (same on both chains due to CREATE2 with same salt)
7+
CALL_DISPATCHER="0x876F1891982E260026630c233A4897160A281Fb8"
8+
VWAP_ORACLE="0xe73b283506eD9F86AA11C0086B5052B4E6Fa1686"
9+
INTENT_GATEWAY_V2="0xFbF50B2b32768127603cC9eF4b871574b881b8eD"
10+
SOLVER_ACCOUNT="0xd42EFC09607dA5577dfB7Ecc3E0756b0f45902E3"
11+
12+
# Chain ID: 97 = BSC Testnet, 80002 = Polygon Amoy
13+
CHAIN_ID=${1:-97}
14+
15+
echo "=== Verifying contracts on Chain ID: $CHAIN_ID ==="
16+
17+
# 1. Verify CallDispatcher (no constructor args)
18+
echo "Verifying CallDispatcher..."
19+
forge verify-contract \
20+
--chain-id $CHAIN_ID \
21+
--watch \
22+
$CALL_DISPATCHER \
23+
src/utils/CallDispatcher.sol:CallDispatcher
24+
25+
# 2. Verify VWAPOracle (constructor arg: admin)
26+
echo "Verifying VWAPOracle..."
27+
forge verify-contract \
28+
--chain-id $CHAIN_ID \
29+
--watch \
30+
--constructor-args $(cast abi-encode "constructor(address)" $ADMIN) \
31+
$VWAP_ORACLE \
32+
src/utils/VWAPOracle.sol:VWAPOracle
33+
34+
# 3. Verify IntentGatewayV2 (constructor arg: admin)
35+
echo "Verifying IntentGatewayV2..."
36+
forge verify-contract \
37+
--chain-id $CHAIN_ID \
38+
--watch \
39+
--constructor-args $(cast abi-encode "constructor(address)" $ADMIN) \
40+
$INTENT_GATEWAY_V2 \
41+
src/apps/IntentGatewayV2.sol:IntentGatewayV2
42+
43+
# 4. Verify SolverAccount (constructor arg: intentGatewayV2)
44+
echo "Verifying SolverAccount..."
45+
forge verify-contract \
46+
--chain-id $CHAIN_ID \
47+
--watch \
48+
--constructor-args $(cast abi-encode "constructor(address)" $INTENT_GATEWAY_V2) \
49+
$SOLVER_ACCOUNT \
50+
src/utils/SolverAccount.sol:SolverAccount
51+
52+
echo "=== Verification complete ==="

evm/test/HandlerV1ForkTest.sol

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
// Copyright (C) Polytope Labs Ltd.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
// Licensed under the Apache License, Version 2.0 (the "License");
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
//
8+
// http://www.apache.org/licenses/LICENSE-2.0
9+
//
10+
// Unless required by applicable law or agreed to in writing, software
11+
// distributed under the License is distributed on an "AS IS" BASIS,
12+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
// See the License for the specific language governing permissions and
14+
// limitations under the License.
15+
pragma solidity ^0.8.17;
16+
17+
import "forge-std/Test.sol";
18+
import {IHandler} from "@hyperbridge/core/interfaces/IHandler.sol";
19+
import {IHost} from "@hyperbridge/core/interfaces/IHost.sol";
20+
21+
contract HandlerV1ForkTest is Test {
22+
IHandler internal handler;
23+
IHost internal host;
24+
25+
// TODO: Add deployed addresses
26+
address constant HANDLER_ADDRESS = 0xDa9aa832cF1024862a23f9fDd47cC2358B7d549c;
27+
address constant HOST_ADDRESS = 0x8Af30d750a0Be06fA60A3Cc61e1EE3Ad5766fE86;
28+
29+
function setUp() public {
30+
// TODO: Set your Tron RPC URL env variable
31+
vm.createSelectFork("https://nile.trongrid.io/jsonrpc");
32+
33+
handler = IHandler(HANDLER_ADDRESS);
34+
host = IHost(HOST_ADDRESS);
35+
}
36+
37+
// function test_handlePostRequests() public {
38+
// // TODO: Add your calldata
39+
// bytes memory callData = hex"";
40+
41+
// (bool success, bytes memory returnData) = address(handler).call(callData);
42+
// if (!success) {
43+
// if (returnData.length > 0) {
44+
// assembly {
45+
// revert(add(returnData, 32), mload(returnData))
46+
// }
47+
// }
48+
// revert("Call failed");
49+
// }
50+
// }
51+
52+
53+
function test_helloWorld() public {
54+
console.log("Hello World");
55+
}
56+
}

sdk/packages/sdk/src/configs/ChainConfigService.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -187,8 +187,8 @@ export class ChainConfigService {
187187
return this.getConfig(chain)?.addresses.EntryPointV08!
188188
}
189189

190-
getCirclePaymasterV08Address(chain: string): HexString | undefined {
191-
return this.getConfig(chain)?.addresses.CirclePaymasterV08 as HexString | undefined
190+
getCirclePaymasterAddress(chain: string): HexString | undefined {
191+
return this.getConfig(chain)?.addresses.CirclePaymaster as HexString | undefined
192192
}
193193

194194
getHyperbridgeAddress(): string {

0 commit comments

Comments
 (0)