Skip to content

Commit 67e6676

Browse files
authored
Merge pull request #246 from BitGo/hypeevm-bigblocks
feat: added script to enable bigBlocks for hypeEVM
2 parents cf76a96 + ba6d908 commit 67e6676

File tree

8 files changed

+3205
-2281
lines changed

8 files changed

+3205
-2281
lines changed

.github/workflows/deploy_and_release.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ jobs:
5757
SEIEVM_EXPLORER_API_KEY: ${{ secrets.SEIEVM_EXPLORER_API_KEY }}
5858
KAIA_EXPLORER_API_KEY: ${{ secrets.KAIA_EXPLORER_API_KEY }}
5959
IP_EXPLORER_API_KEY: ${{ secrets.IP_EXPLORER_API_KEY }}
60+
HYPE_EVM_PRIVATE_KEY: ${{ secrets.HYPE_EVM_PRIVATE_KEY }}
6061

6162
get-network:
6263
runs-on: ubuntu-latest
@@ -131,6 +132,7 @@ jobs:
131132
SEIEVM_EXPLORER_API_KEY: ${{ secrets.SEIEVM_EXPLORER_API_KEY }}
132133
KAIA_EXPLORER_API_KEY: ${{ secrets.KAIA_EXPLORER_API_KEY }}
133134
IP_EXPLORER_API_KEY: ${{ secrets.IP_EXPLORER_API_KEY }}
135+
HYPE_EVM_PRIVATE_KEY: ${{ secrets.HYPE_EVM_PRIVATE_KEY }}
134136
- name: Update release notes
135137
uses: actions/github-script@v6
136138
with:
@@ -215,6 +217,7 @@ jobs:
215217
SEIEVM_EXPLORER_API_KEY: ${{ secrets.SEIEVM_EXPLORER_API_KEY }}
216218
KAIA_EXPLORER_API_KEY: ${{ secrets.KAIA_EXPLORER_API_KEY }}
217219
IP_EXPLORER_API_KEY: ${{ secrets.IP_EXPLORER_API_KEY }}
220+
HYPE_EVM_PRIVATE_KEY: ${{ secrets.HYPE_EVM_PRIVATE_KEY }}
218221
- name: Update release notes
219222
uses: actions/github-script@v6
220223
with:

README.md

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,17 @@ Note that this suite of contracts is an upgraded version of [eth-multisig-v2](ht
3737
- Wallets include a batch function to save on fees when sending multiple transactions.
3838
- SequenceId is now simply a strictly increasing nonce.
3939

40+
### BigBlocks Configuration
41+
42+
Some chains (currently HypeEVM mainnet and testnet) support BigBlocks functionality for optimized transaction processing. When deploying to these chains, the deployment script will automatically configure BigBlocks.
43+
44+
Requirements:
45+
- `HYPE_EVM_PRIVATE_KEY` environment variable must be set when deploying to HypeEVM chains
46+
- Add this to your `.env` file:
47+
```
48+
HYPE_EVM_PRIVATE_KEY=<Your HypeEVM private key>
49+
```
50+
4051
### Deployment
4152
The Wallet contract and forwarder contracts can each be deployed independently of each other, using the provided ForwarderFactory and WalletFactory.
4253
These factories employ two features to minimize the cost associated with deploying a new contract:
@@ -115,12 +126,13 @@ Find it at [contracts/WalletSimple.sol](contracts/WalletSimple.sol)
115126
A test suite is included through the use of the hardhat framework, providing coverage for methods in the wallet.
116127

117128
On first run:
118-
1. Create a file called `.env` in the root directory.
119-
2. Add the following variable:
129+
1. Create a file called `.env` in the root directory.
130+
2. Add the following variables:
120131
```
121132
TESTNET_PRIVATE_KEY_FOR_CONTRACT_DEPLOYMENT=<Your private key>
122133
MAINNET_PRIVATE_KEY_FOR_CONTRACT_DEPLOYMENT=<Your private key>
123134
PRIVATE_KEY_FOR_V4_CONTRACT_DEPLOYMENT=<Your private key>
135+
HYPE_EVM_PRIVATE_KEY=<Your HypeEVM private key> # Required for HypeEVM chains
124136
```
125137
Note: `<your private key>` can be from a wallet like Metamask.
126138

config/bigBlocksConfig.ts

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import { CHAIN_IDS } from './chainIds';
2+
const { HYPE_EVM_PRIVATE_KEY } = process.env;
3+
/**
4+
* Configuration for a chain that supports BigBlocks
5+
*/
6+
export interface BigBlocksChainConfig {
7+
/** Human-readable name of the network */
8+
name: string;
9+
/** Whether this is a testnet or mainnet */
10+
isTestnet: boolean;
11+
/** API URL for BigBlocks service */
12+
apiUrl: string;
13+
/** Chain ID for BigBlocks service */
14+
bigBlocksChainId: number;
15+
/** Environment variable key for private key */
16+
envKey: string | undefined;
17+
}
18+
19+
/**
20+
* Map of chain IDs to their BigBlocks configuration
21+
* Currently only supported on HypeEVM networks
22+
*/
23+
export const BIGBLOCKS_SUPPORTED_CHAINS: Record<number, BigBlocksChainConfig> =
24+
{
25+
[CHAIN_IDS.HYPEEVM]: {
26+
name: 'HypeEVM mainnet',
27+
isTestnet: false,
28+
apiUrl: 'https://api.hyperliquid.xyz/exchange',
29+
bigBlocksChainId: 1337,
30+
envKey: HYPE_EVM_PRIVATE_KEY
31+
},
32+
[CHAIN_IDS.HYPEEVM_TESTNET]: {
33+
name: 'HypeEVM Testnet',
34+
isTestnet: true,
35+
apiUrl: 'https://api.hyperliquid-testnet.xyz/exchange',
36+
bigBlocksChainId: 1337,
37+
envKey: HYPE_EVM_PRIVATE_KEY
38+
}
39+
};
40+
41+
/**
42+
* Check if a given chain ID supports BigBlocks
43+
* @param chainId The chain ID to check
44+
* @returns true if the chain supports BigBlocks, false otherwise
45+
*/
46+
export const isBigBlocksSupported = (chainId: number): boolean => {
47+
return chainId in BIGBLOCKS_SUPPORTED_CHAINS;
48+
};
49+
50+
/**
51+
* Get the BigBlocks configuration for a chain
52+
* @param chainId The chain ID to get configuration for
53+
* @returns The chain's BigBlocks configuration, or undefined if not supported
54+
*/
55+
export const getBigBlocksConfig = (
56+
chainId: number
57+
): BigBlocksChainConfig | undefined => {
58+
return BIGBLOCKS_SUPPORTED_CHAINS[chainId];
59+
};

package.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,11 @@
2626
"ethereum"
2727
],
2828
"dependencies": {
29+
"@ethersproject/bytes": "^5.8.0",
30+
"@ethersproject/wallet": "^5.8.0",
31+
"@msgpack/msgpack": "^3.1.2",
32+
"@noble/hashes": "^1.8.0",
33+
"@noble/secp256k1": "^2.3.0",
2934
"@openzeppelin/contracts": "5.0.0",
3035
"@truffle/hdwallet-provider": "^2.0.0",
3136
"bignumber.js": "^9.0.0",

scripts/deploy.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,11 @@ import {
88
saveOutput,
99
DeploymentAddresses
1010
} from '../deployUtils';
11+
import { enableBigBlocks } from './enableBigBlocks';
12+
import {
13+
getBigBlocksConfig,
14+
isBigBlocksSupported
15+
} from '../config/bigBlocksConfig';
1116

1217
const NONCE = {
1318
WALLET: 0,
@@ -16,6 +21,27 @@ const NONCE = {
1621
FORWARDER_FACTORY: 3
1722
};
1823

24+
/**
25+
* Configure BigBlocks for HypeEVM network
26+
*/
27+
async function setupBigBlocks(chainId: number): Promise<void> {
28+
const config = getBigBlocksConfig(chainId);
29+
if (!config) return;
30+
31+
if (!config.envKey) {
32+
throw new Error(`Please set the private key for ${config.name}.`);
33+
}
34+
35+
console.log(`Using BigBlocks on ${config.name}`);
36+
try {
37+
await enableBigBlocks(config.envKey, true, chainId);
38+
} catch (error) {
39+
throw new Error(
40+
`Failed to setup BigBlocks on ${config.name}: ${(error as Error).message}`
41+
);
42+
}
43+
}
44+
1945
async function main() {
2046
const feeData = await ethers.provider.getFeeData();
2147
type LegacyGasParams = {
@@ -45,6 +71,11 @@ async function main() {
4571
const chainConfig = await getChainConfig(chainId);
4672
let output: DeploymentAddresses = loadOutput();
4773

74+
if (isBigBlocksSupported(chainId)) {
75+
console.log('🔄 Setting up BigBlocks...');
76+
await setupBigBlocks(chainId);
77+
}
78+
4879
console.log(
4980
`🚀 Deployer: ${deployerAddress} (nonce: ${currentNonce}) on chain ${chainId}`
5081
);

scripts/enableBigBlocks.ts

Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
import { Wallet } from '@ethersproject/wallet';
2+
import { keccak_256 } from '@noble/hashes/sha3';
3+
import { encode } from '@msgpack/msgpack';
4+
import { arrayify } from '@ethersproject/bytes';
5+
import { hexToBytes, bytesToHex, concatBytes } from './secp256k1Wrapper';
6+
import { getBigBlocksConfig } from '../config/bigBlocksConfig';
7+
8+
interface Action {
9+
type: 'evmUserModify';
10+
usingBigBlocks: boolean;
11+
}
12+
13+
interface SignatureResult {
14+
r: string;
15+
s: string;
16+
v: number;
17+
}
18+
19+
interface ApiResponse {
20+
status: 'ok' | 'err';
21+
response?: string;
22+
}
23+
24+
interface L1ActionHashParams {
25+
action: Action;
26+
nonce: number;
27+
vaultAddress?: string;
28+
expiresAfter?: number;
29+
}
30+
31+
interface SignL1ActionParams {
32+
wallet: Wallet;
33+
action: Action;
34+
nonce: number;
35+
chainId: number;
36+
}
37+
38+
function getBigBlocksUrl(chainId: number): string {
39+
const config = getBigBlocksConfig(chainId);
40+
if (!config) throw new Error(`Chain ${chainId} does not support BigBlocks`);
41+
return config.apiUrl;
42+
}
43+
44+
/**
45+
* Helper: encode nonce to 8 bytes
46+
*/
47+
function toUint64Bytes(n: number): Uint8Array {
48+
const bytes = new Uint8Array(8);
49+
new DataView(bytes.buffer).setBigUint64(0, BigInt(n));
50+
return bytes;
51+
}
52+
53+
/**
54+
* Sort action keys to match SDK format
55+
*/
56+
function sortAction(action: Action): Action {
57+
return {
58+
type: action.type,
59+
usingBigBlocks: action.usingBigBlocks
60+
};
61+
}
62+
63+
/**
64+
* Create the connectionId hash from action, nonce, etc.
65+
*/
66+
async function createL1ActionHash({
67+
action,
68+
nonce,
69+
vaultAddress,
70+
expiresAfter
71+
}: L1ActionHashParams): Promise<string> {
72+
const actionBytes = encode(action);
73+
const nonceBytes = toUint64Bytes(nonce);
74+
const vaultMarker = vaultAddress ? new Uint8Array([1]) : new Uint8Array([0]);
75+
const vaultBytes = vaultAddress
76+
? await hexToBytes(vaultAddress.slice(2))
77+
: new Uint8Array();
78+
const expiresMarker =
79+
expiresAfter !== undefined ? new Uint8Array([0]) : new Uint8Array();
80+
const expiresBytes =
81+
expiresAfter !== undefined ? toUint64Bytes(expiresAfter) : new Uint8Array();
82+
83+
const bytes = await concatBytes(
84+
actionBytes,
85+
nonceBytes,
86+
vaultMarker,
87+
vaultBytes,
88+
expiresMarker,
89+
expiresBytes
90+
);
91+
92+
const hash = keccak_256(bytes);
93+
return `0x${await bytesToHex(hash)}`;
94+
}
95+
96+
/**
97+
* EIP-712 signer for the L1 action
98+
*/
99+
async function signL1Action({
100+
wallet,
101+
action,
102+
nonce,
103+
chainId
104+
}: SignL1ActionParams): Promise<SignatureResult> {
105+
const connectionId = await createL1ActionHash({ action, nonce });
106+
const config = getBigBlocksConfig(chainId);
107+
if (!config) throw new Error(`Chain ${chainId} does not support BigBlocks`);
108+
109+
const domain = {
110+
name: 'Exchange',
111+
version: '1',
112+
chainId: config.bigBlocksChainId,
113+
verifyingContract: '0x0000000000000000000000000000000000000000'
114+
};
115+
116+
const types = {
117+
Agent: [
118+
{ name: 'source', type: 'string' },
119+
{ name: 'connectionId', type: 'bytes32' }
120+
]
121+
};
122+
123+
const message = {
124+
source: config.isTestnet ? 'b' : 'a',
125+
connectionId: arrayify(connectionId)
126+
};
127+
128+
const signature = await wallet._signTypedData(domain, types, message);
129+
130+
const r = '0x' + signature.slice(2, 66);
131+
const s = '0x' + signature.slice(66, 130);
132+
const v = parseInt(signature.slice(130, 132), 16);
133+
134+
return { r, s, v };
135+
}
136+
137+
/**
138+
* Main function to enable/disable BigBlocks
139+
*/
140+
export async function enableBigBlocks(
141+
privateKey: string,
142+
enable: boolean = true,
143+
chainId: number
144+
): Promise<ApiResponse> {
145+
const config = getBigBlocksConfig(chainId);
146+
if (!config) throw new Error(`Chain ${chainId} does not support BigBlocks`);
147+
try {
148+
const wallet = new Wallet(privateKey);
149+
const nonce = Date.now();
150+
151+
const action: Action = {
152+
type: 'evmUserModify',
153+
usingBigBlocks: enable
154+
};
155+
156+
const signature = await signL1Action({
157+
wallet,
158+
action,
159+
nonce,
160+
chainId
161+
});
162+
163+
const apiUrl = getBigBlocksUrl(chainId);
164+
const res = await fetch(apiUrl, {
165+
method: 'POST',
166+
headers: {
167+
'Content-Type': 'application/json',
168+
'Accept-Encoding': 'gzip, deflate, br, zstd'
169+
},
170+
body: JSON.stringify({
171+
action: sortAction(action),
172+
nonce,
173+
signature
174+
})
175+
});
176+
177+
if (!res.ok) {
178+
const text = await res.text();
179+
throw new Error(`HTTP ${res.status}: ${text}`);
180+
}
181+
182+
const result = (await res.json()) as ApiResponse;
183+
console.log(`Response from BigBlocks API: ${JSON.stringify(result)}`);
184+
if (result.status === 'err') {
185+
throw new Error(`API error: ${result.response}`);
186+
}
187+
188+
console.log(
189+
`✅ BigBlocks ${
190+
enable ? 'enabled' : 'disabled'
191+
} successfully for wallet ${wallet.address} on ${config.name}`
192+
);
193+
return result;
194+
} catch (err) {
195+
console.error('❌ Error:', (err as Error).message);
196+
throw err;
197+
}
198+
}

0 commit comments

Comments
 (0)