@across-protocol/contracts 3.0.17 → 3.0.18
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/dist/scripts/svm/addressToPublicKey.d.ts +1 -0
- package/dist/scripts/svm/addressToPublicKey.js +20 -0
- package/dist/scripts/svm/bridgeLiabilityToHubPool.d.ts +28 -0
- package/dist/scripts/svm/bridgeLiabilityToHubPool.js +231 -0
- package/dist/scripts/svm/closeRelayerPdas.js +6 -7
- package/dist/scripts/svm/enableRoute.js +7 -1
- package/dist/scripts/svm/executeRebalanceToHubPool.d.ts +32 -0
- package/dist/scripts/svm/executeRebalanceToHubPool.js +355 -0
- package/dist/scripts/svm/executeRebalanceToSpokePool.d.ts +1 -0
- package/dist/scripts/svm/executeRebalanceToSpokePool.js +253 -0
- package/dist/scripts/svm/proposeRebalanceToHubPool.d.ts +27 -0
- package/dist/scripts/svm/proposeRebalanceToHubPool.js +117 -0
- package/dist/scripts/svm/proposeRebalanceToSpokePool.d.ts +1 -0
- package/dist/scripts/svm/proposeRebalanceToSpokePool.js +101 -0
- package/dist/scripts/svm/publicKeyToAddress.d.ts +1 -0
- package/dist/scripts/svm/publicKeyToAddress.js +20 -0
- package/dist/scripts/svm/queryDeposits.js +1 -0
- package/dist/scripts/svm/queryFills.js +11 -12
- package/dist/scripts/svm/queryRoute.js +7 -1
- package/dist/scripts/svm/queryState.js +1 -0
- package/dist/scripts/svm/remoteHubPoolPauseDeposits.d.ts +1 -0
- package/dist/scripts/svm/remoteHubPoolPauseDeposits.js +205 -0
- package/dist/scripts/svm/simpleDeposit.js +7 -1
- package/dist/scripts/svm/simpleFill.js +5 -4
- package/dist/scripts/svm/utils/constants.d.ts +4 -0
- package/dist/scripts/svm/utils/constants.js +8 -1
- package/dist/scripts/svm/utils/helpers.d.ts +31 -0
- package/dist/scripts/svm/utils/helpers.js +50 -1
- package/dist/scripts/svm/utils/poolRebalanceTree.d.ts +22 -0
- package/dist/scripts/svm/utils/poolRebalanceTree.js +20 -0
- package/dist/src/SvmUtils.d.ts +24 -2
- package/dist/src/SvmUtils.js +107 -26
- package/dist/target/types/svm_spoke.d.ts +47 -42
- package/dist/tasks/enableL1TokenAcrossEcosystem.js +3 -33
- package/dist/tasks/types.d.ts +2 -0
- package/dist/tasks/types.js +2 -0
- package/dist/tasks/utils.d.ts +12 -0
- package/dist/tasks/utils.js +34 -0
- package/dist/test/svm/SvmSpoke.Bundle.js +15 -16
- package/dist/test/svm/SvmSpoke.Deposit.js +18 -26
- package/dist/test/svm/SvmSpoke.Fill.AcrossPlus.js +1 -1
- package/dist/test/svm/SvmSpoke.Fill.js +13 -14
- package/dist/test/svm/SvmSpoke.Ownership.js +10 -16
- package/dist/test/svm/SvmSpoke.RefundClaims.js +9 -8
- package/dist/test/svm/SvmSpoke.Routes.js +10 -6
- package/dist/test/svm/SvmSpoke.SlowFill.AcrossPlus.js +59 -2
- package/dist/test/svm/SvmSpoke.SlowFill.js +23 -18
- package/dist/test/svm/SvmSpoke.TokenBridge.js +3 -5
- package/dist/test/svm/SvmSpoke.common.js +2 -1
- package/dist/test/svm/utils.d.ts +4 -5
- package/dist/test/svm/utils.js +24 -18
- package/package.json +2 -2
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Script: Execute USDC Rebalance to Hub Pool
|
|
4
|
+
*
|
|
5
|
+
* This script executes a previously proposed root bundle on the Hub Pool to rebalance USDC from the Solana Spoke Pool
|
|
6
|
+
* to the Ethereum Hub Pool. It handles CCTP attestations, relayer refund leaf execution, and prepares pending
|
|
7
|
+
* liabilities for bridging back to the Hub Pool.
|
|
8
|
+
*
|
|
9
|
+
* Required Environment Variables:
|
|
10
|
+
* - TESTNET: (Optional) Set to "true" to use Sepolia; defaults to mainnet.
|
|
11
|
+
* - MNEMONIC: Wallet mnemonic to sign the Ethereum transaction.
|
|
12
|
+
* - HUB_POOL_ADDRESS: Ethereum address of the Hub Pool.
|
|
13
|
+
* - NODE_URL_1: Ethereum RPC URL for mainnet (ignored if TESTNET=true).
|
|
14
|
+
* - NODE_URL_11155111: Ethereum RPC URL for Sepolia (ignored if TESTNET=false).
|
|
15
|
+
*
|
|
16
|
+
* Required Arguments:
|
|
17
|
+
* - `--netSendAmount`: The unscaled amount of USDC to rebalance (e.g., for USDC with 6 decimals, 1 = 0.000001 USDC).
|
|
18
|
+
* - `--resumeRemoteTx`: (Optional) Hash of a previously submitted remote transaction to resume.
|
|
19
|
+
*
|
|
20
|
+
* Example Usage:
|
|
21
|
+
* TESTNET=true \
|
|
22
|
+
* NODE_URL_11155111=$NODE_URL_11155111 \
|
|
23
|
+
* MNEMONIC=$MNEMONIC \
|
|
24
|
+
* HUB_POOL_ADDRESS=$HUB_POOL_ADDRESS \
|
|
25
|
+
* anchor run executeRebalanceToHubPool \
|
|
26
|
+
* --provider.cluster "devnet" \
|
|
27
|
+
* --provider.wallet $SOLANA_PKEY_PATH \
|
|
28
|
+
* -- --netSendAmount 7
|
|
29
|
+
*
|
|
30
|
+
* Note:
|
|
31
|
+
* - Ensure all required environment variables are properly configured.
|
|
32
|
+
*/
|
|
33
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
34
|
+
if (k2 === undefined) k2 = k;
|
|
35
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
36
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
37
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
38
|
+
}
|
|
39
|
+
Object.defineProperty(o, k2, desc);
|
|
40
|
+
}) : (function(o, m, k, k2) {
|
|
41
|
+
if (k2 === undefined) k2 = k;
|
|
42
|
+
o[k2] = m[k];
|
|
43
|
+
}));
|
|
44
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
45
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
46
|
+
}) : function(o, v) {
|
|
47
|
+
o["default"] = v;
|
|
48
|
+
});
|
|
49
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
50
|
+
if (mod && mod.__esModule) return mod;
|
|
51
|
+
var result = {};
|
|
52
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
53
|
+
__setModuleDefault(result, mod);
|
|
54
|
+
return result;
|
|
55
|
+
};
|
|
56
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
57
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
58
|
+
};
|
|
59
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
60
|
+
const anchor = __importStar(require("@coral-xyz/anchor"));
|
|
61
|
+
const anchor_1 = require("@coral-xyz/anchor");
|
|
62
|
+
const spl_token_1 = require("@solana/spl-token");
|
|
63
|
+
const web3_js_1 = require("@solana/web3.js");
|
|
64
|
+
// eslint-disable-next-line camelcase
|
|
65
|
+
const ethers_1 = require("ethers");
|
|
66
|
+
const yargs_1 = __importDefault(require("yargs"));
|
|
67
|
+
const helpers_1 = require("yargs/helpers");
|
|
68
|
+
const constants_1 = require("../../utils/constants");
|
|
69
|
+
// eslint-disable-next-line camelcase
|
|
70
|
+
const typechain_1 = require("../../typechain");
|
|
71
|
+
const constants_2 = require("./utils/constants");
|
|
72
|
+
const helpers_2 = require("./utils/helpers");
|
|
73
|
+
const common_1 = require("@uma/common");
|
|
74
|
+
const cctpHelpers_1 = require("../../test/svm/cctpHelpers");
|
|
75
|
+
const utils_1 = require("../../test/svm/utils");
|
|
76
|
+
// Set up Solana provider.
|
|
77
|
+
const provider = anchor_1.AnchorProvider.env();
|
|
78
|
+
anchor.setProvider(provider);
|
|
79
|
+
// Get Solana programs.
|
|
80
|
+
const svmSpokeIdl = require("../../target/idl/svm_spoke.json");
|
|
81
|
+
const svmSpokeProgram = new anchor_1.Program(svmSpokeIdl, provider);
|
|
82
|
+
const messageTransmitterIdl = require("../../target/idl/message_transmitter.json");
|
|
83
|
+
const messageTransmitterProgram = new anchor_1.Program(messageTransmitterIdl, provider);
|
|
84
|
+
const [messageTransmitterState] = web3_js_1.PublicKey.findProgramAddressSync([Buffer.from("message_transmitter")], messageTransmitterProgram.programId);
|
|
85
|
+
const [authorityPda] = web3_js_1.PublicKey.findProgramAddressSync([Buffer.from("message_transmitter_authority"), svmSpokeProgram.programId.toBuffer()], messageTransmitterProgram.programId);
|
|
86
|
+
const [selfAuthority] = web3_js_1.PublicKey.findProgramAddressSync([Buffer.from("self_authority")], svmSpokeProgram.programId);
|
|
87
|
+
const [eventAuthority] = web3_js_1.PublicKey.findProgramAddressSync([Buffer.from("__event_authority")], svmSpokeProgram.programId);
|
|
88
|
+
// Set up Ethereum provider and signer.
|
|
89
|
+
const nodeURL = process.env.TESTNET === "true" ? (0, common_1.getNodeUrl)("sepolia", true) : (0, common_1.getNodeUrl)("mainnet", true);
|
|
90
|
+
const ethersProvider = new ethers_1.ethers.providers.JsonRpcProvider(nodeURL);
|
|
91
|
+
const ethersSigner = ethers_1.ethers.Wallet.fromMnemonic((0, helpers_2.requireEnv)("MNEMONIC")).connect(ethersProvider);
|
|
92
|
+
// Get the HubPool contract instance.
|
|
93
|
+
const hubPoolAddress = ethers_1.ethers.utils.getAddress((0, helpers_2.requireEnv)("HUB_POOL_ADDRESS"));
|
|
94
|
+
const hubPool = typechain_1.HubPool__factory.connect(hubPoolAddress, ethersProvider);
|
|
95
|
+
// CCTP domains.
|
|
96
|
+
const ethereumDomain = 0; // Ethereum
|
|
97
|
+
// Parse arguments
|
|
98
|
+
const argv = (0, yargs_1.default)((0, helpers_1.hideBin)(process.argv))
|
|
99
|
+
// Net send amount is always required as we need it to calculate the relayer refund leaf.
|
|
100
|
+
.option("netSendAmount", { type: "string", demandOption: true, describe: "Net send amount to Hub Pool" })
|
|
101
|
+
.option("resumeRemoteTx", { type: "string", demandOption: false, describe: "Resume receiving remote tx" }).argv;
|
|
102
|
+
async function executeRebalanceToHubPool() {
|
|
103
|
+
const resolvedArgv = await argv;
|
|
104
|
+
const seed = constants_2.SOLANA_SPOKE_STATE_SEED; // Seed is always 0 for the state account PDA in public networks.
|
|
105
|
+
const netSendAmount = ethers_1.BigNumber.from(resolvedArgv.netSendAmount);
|
|
106
|
+
const resumeRemoteTx = resolvedArgv.resumeRemoteTx;
|
|
107
|
+
// Resolve Solana cluster, EVM chain ID, Iris API URL and USDC addresses.
|
|
108
|
+
let isDevnet;
|
|
109
|
+
const solanaRpcEndpoint = provider.connection.rpcEndpoint;
|
|
110
|
+
if (solanaRpcEndpoint.includes("devnet"))
|
|
111
|
+
isDevnet = true;
|
|
112
|
+
else if (solanaRpcEndpoint.includes("mainnet"))
|
|
113
|
+
isDevnet = false;
|
|
114
|
+
else
|
|
115
|
+
throw new Error(`Unsupported solanaCluster endpoint: ${solanaRpcEndpoint}`);
|
|
116
|
+
const solanaCluster = isDevnet ? "devnet" : "mainnet";
|
|
117
|
+
const solanaChainId = (0, helpers_2.getSolanaChainId)(solanaCluster);
|
|
118
|
+
const irisApiUrl = isDevnet ? constants_2.CIRCLE_IRIS_API_URL_DEVNET : constants_2.CIRCLE_IRIS_API_URL_MAINNET;
|
|
119
|
+
const supportedEvmChainId = isDevnet ? constants_1.CHAIN_IDs.SEPOLIA : constants_1.CHAIN_IDs.MAINNET; // Sepolia is bridged to devnet, Ethereum to mainnet in CCTP.
|
|
120
|
+
const evmChainId = (await ethersProvider.getNetwork()).chainId;
|
|
121
|
+
if (evmChainId !== supportedEvmChainId) {
|
|
122
|
+
throw new Error(`Chain ID ${evmChainId} does not match expected Solana cluster ${solanaCluster}`);
|
|
123
|
+
}
|
|
124
|
+
const svmUsdc = isDevnet ? constants_2.SOLANA_USDC_DEVNET : constants_2.SOLANA_USDC_MAINNET;
|
|
125
|
+
const [statePda, _] = web3_js_1.PublicKey.findProgramAddressSync([Buffer.from("state"), seed.toArrayLike(Buffer, "le", 8)], svmSpokeProgram.programId);
|
|
126
|
+
const state = await svmSpokeProgram.account.state.fetch(statePda);
|
|
127
|
+
const [rootBundlePda] = getRootBundlePda(state.rootBundleId, seed);
|
|
128
|
+
console.log("Executing rebalance to hub pool...");
|
|
129
|
+
console.table([
|
|
130
|
+
{ Property: "isTestnet", Value: process.env.TESTNET === "true" },
|
|
131
|
+
{ Property: "originChainId", Value: evmChainId.toString() },
|
|
132
|
+
{ Property: "targetChainId", Value: solanaChainId.toString() },
|
|
133
|
+
{ Property: "hubPoolAddress", Value: hubPool.address },
|
|
134
|
+
{ Property: "svmSpokeProgramProgramId", Value: svmSpokeProgram.programId.toString() },
|
|
135
|
+
{ Property: "svmProviderPublicKey", Value: provider.wallet.publicKey.toString() },
|
|
136
|
+
{ Property: "netSendAmount (formatted)", Value: (0, helpers_2.formatUsdc)(netSendAmount) },
|
|
137
|
+
]);
|
|
138
|
+
// Send executeRootBundle call from Ethereum, unless resuming a remote transaction.
|
|
139
|
+
let remoteTxHash;
|
|
140
|
+
if (!resumeRemoteTx) {
|
|
141
|
+
remoteTxHash = await executeRootBalanceOnHubPool(solanaChainId);
|
|
142
|
+
}
|
|
143
|
+
else
|
|
144
|
+
remoteTxHash = resumeRemoteTx;
|
|
145
|
+
// Fetch attestation from CCTP attestation service.
|
|
146
|
+
const attestationResponse = await (0, cctpHelpers_1.getMessages)(remoteTxHash, ethereumDomain, irisApiUrl);
|
|
147
|
+
const { attestation, message } = attestationResponse.messages[0];
|
|
148
|
+
console.log("CCTP attestation response:", attestationResponse.messages[0]);
|
|
149
|
+
// Accounts in CCTP message_transmitter receive_message instruction.
|
|
150
|
+
const nonce = (0, cctpHelpers_1.decodeMessageHeader)(Buffer.from(message.replace("0x", ""), "hex")).nonce;
|
|
151
|
+
const usedNonces = (await messageTransmitterProgram.methods
|
|
152
|
+
.getNoncePda({
|
|
153
|
+
nonce: new anchor_1.BN(nonce.toString()),
|
|
154
|
+
sourceDomain: ethereumDomain,
|
|
155
|
+
})
|
|
156
|
+
.accounts({
|
|
157
|
+
messageTransmitter: messageTransmitterState,
|
|
158
|
+
})
|
|
159
|
+
.view());
|
|
160
|
+
const receiveMessageAccounts = {
|
|
161
|
+
payer: provider.wallet.publicKey,
|
|
162
|
+
caller: provider.wallet.publicKey,
|
|
163
|
+
authorityPda,
|
|
164
|
+
messageTransmitter: messageTransmitterState,
|
|
165
|
+
usedNonces,
|
|
166
|
+
receiver: svmSpokeProgram.programId,
|
|
167
|
+
systemProgram: web3_js_1.SystemProgram.programId,
|
|
168
|
+
};
|
|
169
|
+
// accountMetas list to pass to remaining accounts when receiving message via CCTP.
|
|
170
|
+
const remainingAccounts = [];
|
|
171
|
+
// state in HandleReceiveMessage accounts (used for remote domain and sender authentication).
|
|
172
|
+
remainingAccounts.push({
|
|
173
|
+
isSigner: false,
|
|
174
|
+
isWritable: false,
|
|
175
|
+
pubkey: statePda,
|
|
176
|
+
});
|
|
177
|
+
// self_authority in HandleReceiveMessage accounts, also signer in self-invoked CPIs.
|
|
178
|
+
remainingAccounts.push({
|
|
179
|
+
isSigner: false,
|
|
180
|
+
isWritable: false,
|
|
181
|
+
pubkey: selfAuthority,
|
|
182
|
+
});
|
|
183
|
+
// program in HandleReceiveMessage accounts.
|
|
184
|
+
remainingAccounts.push({
|
|
185
|
+
isSigner: false,
|
|
186
|
+
isWritable: false,
|
|
187
|
+
pubkey: svmSpokeProgram.programId,
|
|
188
|
+
});
|
|
189
|
+
// payer
|
|
190
|
+
remainingAccounts.push({
|
|
191
|
+
isSigner: true,
|
|
192
|
+
isWritable: true,
|
|
193
|
+
pubkey: provider.wallet.publicKey,
|
|
194
|
+
});
|
|
195
|
+
// state in self-invoked CPIs (state can change as a result of remote call).
|
|
196
|
+
remainingAccounts.push({
|
|
197
|
+
isSigner: false,
|
|
198
|
+
isWritable: true,
|
|
199
|
+
pubkey: statePda,
|
|
200
|
+
});
|
|
201
|
+
// root_bundle
|
|
202
|
+
remainingAccounts.push({
|
|
203
|
+
isSigner: false,
|
|
204
|
+
isWritable: true,
|
|
205
|
+
pubkey: rootBundlePda,
|
|
206
|
+
});
|
|
207
|
+
// system_program
|
|
208
|
+
remainingAccounts.push({
|
|
209
|
+
isSigner: false,
|
|
210
|
+
isWritable: false,
|
|
211
|
+
pubkey: web3_js_1.SystemProgram.programId,
|
|
212
|
+
});
|
|
213
|
+
// event_authority in self-invoked CPIs (appended by Anchor with event_cpi macro).
|
|
214
|
+
remainingAccounts.push({
|
|
215
|
+
isSigner: false,
|
|
216
|
+
isWritable: true,
|
|
217
|
+
pubkey: eventAuthority,
|
|
218
|
+
});
|
|
219
|
+
// program
|
|
220
|
+
remainingAccounts.push({
|
|
221
|
+
isSigner: false,
|
|
222
|
+
isWritable: true,
|
|
223
|
+
pubkey: svmSpokeProgram.programId,
|
|
224
|
+
});
|
|
225
|
+
// Receive remote message on Solana.
|
|
226
|
+
console.log("Receiving message on Solana...");
|
|
227
|
+
const receiveMessageTx = await messageTransmitterProgram.methods
|
|
228
|
+
.receiveMessage({
|
|
229
|
+
message: Buffer.from(message.replace("0x", ""), "hex"),
|
|
230
|
+
attestation: Buffer.from(attestation.replace("0x", ""), "hex"),
|
|
231
|
+
})
|
|
232
|
+
.accounts(receiveMessageAccounts)
|
|
233
|
+
.remainingAccounts(remainingAccounts)
|
|
234
|
+
.rpc();
|
|
235
|
+
console.log("\nReceived remote message");
|
|
236
|
+
console.log("Your transaction signature", receiveMessageTx);
|
|
237
|
+
const finalState = await svmSpokeProgram.account.state.fetch(statePda);
|
|
238
|
+
// The state stores the next root bundle ID, so we need to subtract one to get the last executed root bundle ID.
|
|
239
|
+
const lastRootBundleId = finalState.rootBundleId - 1;
|
|
240
|
+
// Reconstruct the merkle tree for the relayer refund leaf.
|
|
241
|
+
const { merkleTree, leaves } = (0, helpers_2.constructSimpleRebalanceTreeToHubPool)(netSendAmount, solanaChainId, new web3_js_1.PublicKey(svmUsdc));
|
|
242
|
+
const [rootBundlePdaNew] = getRootBundlePda(lastRootBundleId, seed);
|
|
243
|
+
console.log(`Executing relayer refund leaf for root bundle ID: ${lastRootBundleId}`);
|
|
244
|
+
await executeRelayerRefundLeaf(provider.wallet, svmSpokeProgram, statePda, rootBundlePdaNew, leaves[0], merkleTree, new web3_js_1.PublicKey(svmUsdc), lastRootBundleId);
|
|
245
|
+
console.log("✅ executed relayer refund leaf");
|
|
246
|
+
const [transferLiability] = web3_js_1.PublicKey.findProgramAddressSync([Buffer.from("transfer_liability"), new web3_js_1.PublicKey(svmUsdc).toBuffer()], svmSpokeProgram.programId);
|
|
247
|
+
const liability = await svmSpokeProgram.account.transferLiability.fetch(transferLiability);
|
|
248
|
+
console.log(`Pending transfer liability: ${(0, helpers_2.formatUsdc)(ethers_1.BigNumber.from(liability.pendingToHubPool.toString()))} USDC`);
|
|
249
|
+
console.log("You can now send the pending liability to hub pool with bridgeLiabilityToHubPool.ts");
|
|
250
|
+
}
|
|
251
|
+
function getRootBundlePda(rootBundleId, seed) {
|
|
252
|
+
const rootBundleIdBuffer = Buffer.alloc(4);
|
|
253
|
+
rootBundleIdBuffer.writeUInt32LE(rootBundleId);
|
|
254
|
+
return web3_js_1.PublicKey.findProgramAddressSync([Buffer.from("root_bundle"), seed.toArrayLike(Buffer, "le", 8), rootBundleIdBuffer], svmSpokeProgram.programId);
|
|
255
|
+
}
|
|
256
|
+
async function executeRootBalanceOnHubPool(solanaChainId) {
|
|
257
|
+
// Reconstruct the merkle tree for the pool rebalance.
|
|
258
|
+
const { poolRebalanceLeaf, poolRebalanceTree } = (0, helpers_2.constructEmptyPoolRebalanceTree)(solanaChainId, 0);
|
|
259
|
+
// Make sure the proposal liveness has passed, it has not been executed and rebalance root matches.
|
|
260
|
+
const currentRootBundleProposal = await hubPool.connect(ethersSigner).callStatic.rootBundleProposal();
|
|
261
|
+
if (currentRootBundleProposal.challengePeriodEndTimestamp > (await hubPool.callStatic.getCurrentTime()).toNumber())
|
|
262
|
+
throw new Error("Not passed liveness");
|
|
263
|
+
if (!currentRootBundleProposal.claimedBitMap.isZero())
|
|
264
|
+
throw new Error("Already claimed");
|
|
265
|
+
if (currentRootBundleProposal.poolRebalanceRoot !== poolRebalanceTree.getHexRoot())
|
|
266
|
+
throw new Error("Rebalance root mismatch");
|
|
267
|
+
// Execute the rebalance bundle on the HubPool.
|
|
268
|
+
const tx = await hubPool.connect(ethersSigner).executeRootBundle(solanaChainId, 0, // groupIndex
|
|
269
|
+
poolRebalanceLeaf.bundleLpFees, poolRebalanceLeaf.netSendAmounts, poolRebalanceLeaf.runningBalances, poolRebalanceLeaf.leafId, poolRebalanceLeaf.l1Tokens, poolRebalanceTree.getHexProof(poolRebalanceLeaf));
|
|
270
|
+
console.log(`✅ submitted tx hash: ${tx.hash}`);
|
|
271
|
+
await tx.wait();
|
|
272
|
+
console.log("✅ tx confirmed");
|
|
273
|
+
return tx.hash;
|
|
274
|
+
}
|
|
275
|
+
async function executeRelayerRefundLeaf(signer, program, statePda, rootBundle, relayerRefundLeaf, merkleTree, inputToken, rootBundleId) {
|
|
276
|
+
// Execute the single leaf
|
|
277
|
+
const proof = merkleTree.getProof(relayerRefundLeaf).map((p) => Array.from(p));
|
|
278
|
+
const leaf = relayerRefundLeaf;
|
|
279
|
+
const vault = (0, spl_token_1.getAssociatedTokenAddressSync)(inputToken, statePda, true, spl_token_1.TOKEN_PROGRAM_ID, spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID);
|
|
280
|
+
// Derive the transferLiability PDA
|
|
281
|
+
const [transferLiability] = web3_js_1.PublicKey.findProgramAddressSync([Buffer.from("transfer_liability"), inputToken.toBuffer()], program.programId);
|
|
282
|
+
// Load the instruction parameters
|
|
283
|
+
const proofAsNumbers = proof.map((p) => Array.from(p));
|
|
284
|
+
console.log("loading execute relayer refund leaf params...");
|
|
285
|
+
const [instructionParams] = web3_js_1.PublicKey.findProgramAddressSync([Buffer.from("instruction_params"), signer.publicKey.toBuffer()], program.programId);
|
|
286
|
+
const staticAccounts = {
|
|
287
|
+
instructionParams,
|
|
288
|
+
state: statePda,
|
|
289
|
+
rootBundle: rootBundle,
|
|
290
|
+
signer: signer.publicKey,
|
|
291
|
+
vault: vault,
|
|
292
|
+
tokenProgram: spl_token_1.TOKEN_PROGRAM_ID,
|
|
293
|
+
mint: inputToken,
|
|
294
|
+
transferLiability,
|
|
295
|
+
systemProgram: anchor.web3.SystemProgram.programId,
|
|
296
|
+
// Appended by Acnhor `event_cpi` macro:
|
|
297
|
+
eventAuthority: web3_js_1.PublicKey.findProgramAddressSync([Buffer.from("__event_authority")], program.programId)[0],
|
|
298
|
+
program: program.programId,
|
|
299
|
+
};
|
|
300
|
+
const refundAccounts = [];
|
|
301
|
+
// Consolidate all above addresses into a single array for the Address Lookup Table (ALT).
|
|
302
|
+
const [lookupTableInstruction, lookupTableAddress] = await web3_js_1.AddressLookupTableProgram.createLookupTable({
|
|
303
|
+
authority: signer.publicKey,
|
|
304
|
+
payer: signer.publicKey,
|
|
305
|
+
recentSlot: await provider.connection.getSlot(),
|
|
306
|
+
});
|
|
307
|
+
// Submit the ALT creation transaction
|
|
308
|
+
await anchor.web3.sendAndConfirmTransaction(provider.connection, new anchor.web3.Transaction().add(lookupTableInstruction), [anchor.AnchorProvider.env().wallet.payer], { skipPreflight: true });
|
|
309
|
+
const lookupAddresses = [...Object.values(staticAccounts), ...refundAccounts];
|
|
310
|
+
// Create the transaction with the compute budget expansion instruction & use extended ALT account.
|
|
311
|
+
// Extend the ALT with all accounts
|
|
312
|
+
const maxExtendedAccounts = 30; // Maximum number of accounts that can be added to ALT in a single transaction.
|
|
313
|
+
for (let i = 0; i < lookupAddresses.length; i += maxExtendedAccounts) {
|
|
314
|
+
const extendInstruction = web3_js_1.AddressLookupTableProgram.extendLookupTable({
|
|
315
|
+
lookupTable: lookupTableAddress,
|
|
316
|
+
authority: signer.publicKey,
|
|
317
|
+
payer: signer.publicKey,
|
|
318
|
+
addresses: lookupAddresses.slice(i, i + maxExtendedAccounts),
|
|
319
|
+
});
|
|
320
|
+
await anchor.web3.sendAndConfirmTransaction(provider.connection, new anchor.web3.Transaction().add(extendInstruction), [anchor.AnchorProvider.env().wallet.payer], { skipPreflight: true });
|
|
321
|
+
}
|
|
322
|
+
// Fetch the AddressLookupTableAccount
|
|
323
|
+
const lookupTableAccount = (await provider.connection.getAddressLookupTable(lookupTableAddress)).value;
|
|
324
|
+
if (!lookupTableAccount) {
|
|
325
|
+
throw new Error("AddressLookupTableAccount not fetched");
|
|
326
|
+
}
|
|
327
|
+
await (0, utils_1.loadExecuteRelayerRefundLeafParams)(program, signer.publicKey, rootBundleId, leaf, proofAsNumbers);
|
|
328
|
+
console.log(`loaded execute relayer refund leaf params ${instructionParams}. \nExecuting relayer refund leaf...`);
|
|
329
|
+
const executeInstruction = await program.methods
|
|
330
|
+
.executeRelayerRefundLeaf()
|
|
331
|
+
.accounts(staticAccounts)
|
|
332
|
+
.remainingAccounts([])
|
|
333
|
+
.instruction();
|
|
334
|
+
// Create the versioned transaction
|
|
335
|
+
const computeBudgetInstruction = web3_js_1.ComputeBudgetProgram.setComputeUnitLimit({ units: 500_000 });
|
|
336
|
+
const versionedTx = new web3_js_1.VersionedTransaction(new web3_js_1.TransactionMessage({
|
|
337
|
+
payerKey: signer.publicKey,
|
|
338
|
+
recentBlockhash: (await provider.connection.getLatestBlockhash()).blockhash,
|
|
339
|
+
instructions: [computeBudgetInstruction, executeInstruction],
|
|
340
|
+
}).compileToV0Message([lookupTableAccount]));
|
|
341
|
+
// Sign and submit the versioned transaction
|
|
342
|
+
versionedTx.sign([anchor.AnchorProvider.env().wallet.payer]);
|
|
343
|
+
const tx = await provider.connection.sendTransaction(versionedTx);
|
|
344
|
+
console.log(`Execute relayer refund leaf transaction sent: ${tx}`);
|
|
345
|
+
// Close the instruction parameters account
|
|
346
|
+
console.log("Closing instruction params...");
|
|
347
|
+
await new Promise((resolve) => setTimeout(resolve, 15000)); // Wait for the previous transaction to be processed.
|
|
348
|
+
const closeInstructionParamsTx = await program.methods.closeInstructionParams()
|
|
349
|
+
.accounts({ signer: signer.publicKey, instructionParams: instructionParams })
|
|
350
|
+
.rpc();
|
|
351
|
+
console.log(`Close instruction params transaction sent: ${closeInstructionParamsTx}`);
|
|
352
|
+
// Note we cant close the lookup table account as it needs to be both deactivated and expired at to do this.
|
|
353
|
+
}
|
|
354
|
+
// Run the executeRebalanceToHubPool function
|
|
355
|
+
executeRebalanceToHubPool();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// This script executes root bundle on HubPool that rebalances tokens to Solana Spoke Pool. Required environment:
|
|
3
|
+
// - ETHERS_PROVIDER_URL: Ethereum RPC provider URL.
|
|
4
|
+
// - ETHERS_MNEMONIC: Mnemonic of the wallet that will sign the sending transaction on Ethereum
|
|
5
|
+
// - HUB_POOL_ADDRESS: Hub Pool address
|
|
6
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
7
|
+
if (k2 === undefined) k2 = k;
|
|
8
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
9
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
10
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
11
|
+
}
|
|
12
|
+
Object.defineProperty(o, k2, desc);
|
|
13
|
+
}) : (function(o, m, k, k2) {
|
|
14
|
+
if (k2 === undefined) k2 = k;
|
|
15
|
+
o[k2] = m[k];
|
|
16
|
+
}));
|
|
17
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
18
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
19
|
+
}) : function(o, v) {
|
|
20
|
+
o["default"] = v;
|
|
21
|
+
});
|
|
22
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
23
|
+
if (mod && mod.__esModule) return mod;
|
|
24
|
+
var result = {};
|
|
25
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
26
|
+
__setModuleDefault(result, mod);
|
|
27
|
+
return result;
|
|
28
|
+
};
|
|
29
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
30
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
31
|
+
};
|
|
32
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
33
|
+
const anchor = __importStar(require("@coral-xyz/anchor"));
|
|
34
|
+
const anchor_1 = require("@coral-xyz/anchor");
|
|
35
|
+
const web3_js_1 = require("@solana/web3.js");
|
|
36
|
+
const spl_token_1 = require("@solana/spl-token");
|
|
37
|
+
// eslint-disable-next-line camelcase
|
|
38
|
+
const constants_1 = require("../../utils/constants");
|
|
39
|
+
const yargs_1 = __importDefault(require("yargs"));
|
|
40
|
+
const helpers_1 = require("yargs/helpers");
|
|
41
|
+
const SvmUtils_1 = require("../../src/SvmUtils");
|
|
42
|
+
const ethers_1 = require("ethers");
|
|
43
|
+
// eslint-disable-next-line camelcase
|
|
44
|
+
const typechain_1 = require("../../typechain");
|
|
45
|
+
const constants_2 = require("./utils/constants");
|
|
46
|
+
const poolRebalanceTree_1 = require("./utils/poolRebalanceTree");
|
|
47
|
+
const cctpHelpers_1 = require("../../test/svm/cctpHelpers");
|
|
48
|
+
// Set up Solana provider.
|
|
49
|
+
const provider = anchor_1.AnchorProvider.env();
|
|
50
|
+
anchor.setProvider(provider);
|
|
51
|
+
// Get Solana programs.
|
|
52
|
+
const svmSpokeIdl = require("../../target/idl/svm_spoke.json");
|
|
53
|
+
const svmSpokeProgram = new anchor_1.Program(svmSpokeIdl, provider);
|
|
54
|
+
const messageTransmitterIdl = require("../../target/idl/message_transmitter.json");
|
|
55
|
+
const messageTransmitterProgram = new anchor_1.Program(messageTransmitterIdl, provider);
|
|
56
|
+
const tokenMessengerMinterIdl = require("../../target/idl/token_messenger_minter.json");
|
|
57
|
+
const tokenMessengerMinterProgram = new anchor_1.Program(tokenMessengerMinterIdl, provider);
|
|
58
|
+
// Set up Ethereum provider.
|
|
59
|
+
if (!process.env.ETHERS_PROVIDER_URL) {
|
|
60
|
+
throw new Error("Environment variable ETHERS_PROVIDER_URL is not set");
|
|
61
|
+
}
|
|
62
|
+
const ethersProvider = new ethers_1.ethers.providers.JsonRpcProvider(process.env.ETHERS_PROVIDER_URL);
|
|
63
|
+
if (!process.env.ETHERS_MNEMONIC) {
|
|
64
|
+
throw new Error("Environment variable ETHERS_MNEMONIC is not set");
|
|
65
|
+
}
|
|
66
|
+
const ethersSigner = ethers_1.ethers.Wallet.fromMnemonic(process.env.ETHERS_MNEMONIC).connect(ethersProvider);
|
|
67
|
+
// Get the HubPool contract instance.
|
|
68
|
+
if (!process.env.HUB_POOL_ADDRESS) {
|
|
69
|
+
throw new Error("Environment variable HUB_POOL_ADDRESS is not set");
|
|
70
|
+
}
|
|
71
|
+
const hubPoolAddress = ethers_1.ethers.utils.getAddress(process.env.HUB_POOL_ADDRESS);
|
|
72
|
+
const hubPool = typechain_1.HubPool__factory.connect(hubPoolAddress, ethersProvider);
|
|
73
|
+
// CCTP domains.
|
|
74
|
+
const remoteDomain = 0; // Ethereum
|
|
75
|
+
// Parse arguments
|
|
76
|
+
const argv = (0, yargs_1.default)((0, helpers_1.hideBin)(process.argv))
|
|
77
|
+
.option("netSendAmount", { type: "string", demandOption: false, describe: "Net send amount to spoke" })
|
|
78
|
+
.option("resumeRemoteTx", { type: "string", demandOption: false, describe: "Resume receiving remote tx" })
|
|
79
|
+
.check((argv) => {
|
|
80
|
+
if (argv.netSendAmount !== undefined && argv.resumeRemoteTx !== undefined) {
|
|
81
|
+
throw new Error("Options --netSendAmount and --resumeRemoteTx are mutually exclusive");
|
|
82
|
+
}
|
|
83
|
+
if (argv.netSendAmount === undefined && argv.resumeRemoteTx === undefined) {
|
|
84
|
+
throw new Error("One of the options --netSendAmount or --resumeRemoteTx is required");
|
|
85
|
+
}
|
|
86
|
+
return true;
|
|
87
|
+
}).argv;
|
|
88
|
+
async function executeRebalanceToSpokePool() {
|
|
89
|
+
const resolvedArgv = await argv;
|
|
90
|
+
const seed = new anchor_1.BN(0); // Seed is always 0 for the state account PDA in public networks.
|
|
91
|
+
const netSendAmount = resolvedArgv.netSendAmount ? ethers_1.BigNumber.from(resolvedArgv.netSendAmount) : ethers_1.BigNumber.from(0);
|
|
92
|
+
const resumeRemoteTx = resolvedArgv.resumeRemoteTx;
|
|
93
|
+
// Resolve Solana cluster, EVM chain ID, Iris API URL and USDC addresses.
|
|
94
|
+
let isDevnet;
|
|
95
|
+
const solanaRpcEndpoint = provider.connection.rpcEndpoint;
|
|
96
|
+
if (solanaRpcEndpoint.includes("devnet"))
|
|
97
|
+
isDevnet = true;
|
|
98
|
+
else if (solanaRpcEndpoint.includes("mainnet"))
|
|
99
|
+
isDevnet = false;
|
|
100
|
+
else
|
|
101
|
+
throw new Error(`Unsupported solanaCluster endpoint: ${solanaRpcEndpoint}`);
|
|
102
|
+
const solanaCluster = isDevnet ? "devnet" : "mainnet";
|
|
103
|
+
const solanaChainId = ethers_1.BigNumber.from(BigInt(ethers_1.ethers.utils.keccak256(ethers_1.ethers.utils.toUtf8Bytes(`solana-${solanaCluster}`))) & BigInt("0xFFFFFFFFFFFFFFFF"));
|
|
104
|
+
const irisApiUrl = isDevnet ? constants_2.CIRCLE_IRIS_API_URL_DEVNET : constants_2.CIRCLE_IRIS_API_URL_MAINNET;
|
|
105
|
+
const supportedEvmChainId = isDevnet ? constants_1.CHAIN_IDs.SEPOLIA : constants_1.CHAIN_IDs.MAINNET; // Sepolia is bridged to devnet, Ethereum to mainnet in CCTP.
|
|
106
|
+
const evmChainId = (await ethersProvider.getNetwork()).chainId;
|
|
107
|
+
if (evmChainId !== supportedEvmChainId) {
|
|
108
|
+
throw new Error(`Chain ID ${evmChainId} does not match expected Solana cluster ${solanaCluster}`);
|
|
109
|
+
}
|
|
110
|
+
const l1TokenAddress = constants_1.TOKEN_SYMBOLS_MAP.USDC.addresses[evmChainId];
|
|
111
|
+
const solanaTokenKey = isDevnet ? new web3_js_1.PublicKey(constants_2.SOLANA_USDC_DEVNET) : new web3_js_1.PublicKey(constants_2.SOLANA_USDC_MAINNET);
|
|
112
|
+
console.log("Executing rebalance pool bundle to spoke...");
|
|
113
|
+
console.table([
|
|
114
|
+
{ Property: "originChainId", Value: evmChainId.toString() },
|
|
115
|
+
{ Property: "targetChainId", Value: solanaChainId.toString() },
|
|
116
|
+
{ Property: "hubPoolAddress", Value: hubPool.address },
|
|
117
|
+
{ Property: "l1TokenAddress", Value: l1TokenAddress },
|
|
118
|
+
{ Property: "solanaTokenKey", Value: solanaTokenKey.toString() },
|
|
119
|
+
{ Property: "svmSpokeProgramProgramId", Value: svmSpokeProgram.programId.toString() },
|
|
120
|
+
{ Property: "providerPublicKey", Value: provider.wallet.publicKey.toString() },
|
|
121
|
+
{ Property: "netSendAmount", Value: netSendAmount.toString() },
|
|
122
|
+
]);
|
|
123
|
+
// Send executeRootBundle call from Ethereum, unless resuming a remote transaction.
|
|
124
|
+
let remoteTxHash;
|
|
125
|
+
if (!resumeRemoteTx) {
|
|
126
|
+
remoteTxHash = await executeRebalanceOnHubPool(l1TokenAddress, netSendAmount, solanaChainId);
|
|
127
|
+
}
|
|
128
|
+
else
|
|
129
|
+
remoteTxHash = resumeRemoteTx;
|
|
130
|
+
// Get Solana accounts required to receive tokens over CCTP.
|
|
131
|
+
const [statePda] = web3_js_1.PublicKey.findProgramAddressSync([Buffer.from("state"), seed.toArrayLike(Buffer, "le", 8)], svmSpokeProgram.programId);
|
|
132
|
+
const vault = (0, spl_token_1.getAssociatedTokenAddressSync)(solanaTokenKey, statePda, true);
|
|
133
|
+
const [messageTransmitterState] = web3_js_1.PublicKey.findProgramAddressSync([Buffer.from("message_transmitter")], messageTransmitterProgram.programId);
|
|
134
|
+
const [authorityPda] = web3_js_1.PublicKey.findProgramAddressSync([Buffer.from("message_transmitter_authority"), tokenMessengerMinterProgram.programId.toBuffer()], messageTransmitterProgram.programId);
|
|
135
|
+
const [tokenMessengerAccount] = web3_js_1.PublicKey.findProgramAddressSync([Buffer.from("token_messenger")], tokenMessengerMinterProgram.programId);
|
|
136
|
+
const [remoteTokenMessengerKey] = web3_js_1.PublicKey.findProgramAddressSync([Buffer.from("remote_token_messenger"), Buffer.from(remoteDomain.toString())], tokenMessengerMinterProgram.programId);
|
|
137
|
+
const [tokenMinterAccount] = web3_js_1.PublicKey.findProgramAddressSync([Buffer.from("token_minter")], tokenMessengerMinterProgram.programId);
|
|
138
|
+
const [localToken] = web3_js_1.PublicKey.findProgramAddressSync([Buffer.from("local_token"), solanaTokenKey.toBuffer()], tokenMessengerMinterProgram.programId);
|
|
139
|
+
const [tokenPair] = web3_js_1.PublicKey.findProgramAddressSync([Buffer.from("token_pair"), Buffer.from(remoteDomain.toString()), (0, SvmUtils_1.evmAddressToPublicKey)(l1TokenAddress).toBuffer()], tokenMessengerMinterProgram.programId);
|
|
140
|
+
const [custodyTokenAccount] = web3_js_1.PublicKey.findProgramAddressSync([Buffer.from("custody"), solanaTokenKey.toBuffer()], tokenMessengerMinterProgram.programId);
|
|
141
|
+
const [tokenMessengerEventAuthority] = web3_js_1.PublicKey.findProgramAddressSync([Buffer.from("__event_authority")], tokenMessengerMinterProgram.programId);
|
|
142
|
+
// Fetch attestation from CCTP attestation service.
|
|
143
|
+
const attestationResponse = await (0, cctpHelpers_1.getMessages)(remoteTxHash, remoteDomain, irisApiUrl);
|
|
144
|
+
const { attestation, message } = attestationResponse.messages[0];
|
|
145
|
+
console.log("CCTP attestation response:", attestationResponse.messages[0]);
|
|
146
|
+
// Accounts in CCTP message_transmitter receive_message instruction.
|
|
147
|
+
const nonce = (0, cctpHelpers_1.decodeMessageHeader)(Buffer.from(message.replace("0x", ""), "hex")).nonce;
|
|
148
|
+
const usedNonces = (await messageTransmitterProgram.methods
|
|
149
|
+
.getNoncePda({
|
|
150
|
+
nonce: new anchor_1.BN(nonce.toString()),
|
|
151
|
+
sourceDomain: remoteDomain,
|
|
152
|
+
})
|
|
153
|
+
.accounts({
|
|
154
|
+
messageTransmitter: messageTransmitterState,
|
|
155
|
+
})
|
|
156
|
+
.view());
|
|
157
|
+
const receiveMessageAccounts = {
|
|
158
|
+
payer: provider.wallet.publicKey,
|
|
159
|
+
caller: provider.wallet.publicKey,
|
|
160
|
+
authorityPda,
|
|
161
|
+
messageTransmitter: messageTransmitterState,
|
|
162
|
+
usedNonces,
|
|
163
|
+
receiver: tokenMessengerMinterProgram.programId,
|
|
164
|
+
systemProgram: web3_js_1.SystemProgram.programId,
|
|
165
|
+
};
|
|
166
|
+
// accountMetas list to pass to remaining accounts when receiving token bridge message via CCTP.
|
|
167
|
+
const remainingAccounts = [];
|
|
168
|
+
remainingAccounts.push({
|
|
169
|
+
isSigner: false,
|
|
170
|
+
isWritable: false,
|
|
171
|
+
pubkey: tokenMessengerAccount,
|
|
172
|
+
});
|
|
173
|
+
remainingAccounts.push({
|
|
174
|
+
isSigner: false,
|
|
175
|
+
isWritable: false,
|
|
176
|
+
pubkey: remoteTokenMessengerKey,
|
|
177
|
+
});
|
|
178
|
+
remainingAccounts.push({
|
|
179
|
+
isSigner: false,
|
|
180
|
+
isWritable: true,
|
|
181
|
+
pubkey: tokenMinterAccount,
|
|
182
|
+
});
|
|
183
|
+
remainingAccounts.push({
|
|
184
|
+
isSigner: false,
|
|
185
|
+
isWritable: true,
|
|
186
|
+
pubkey: localToken,
|
|
187
|
+
});
|
|
188
|
+
remainingAccounts.push({
|
|
189
|
+
isSigner: false,
|
|
190
|
+
isWritable: false,
|
|
191
|
+
pubkey: tokenPair,
|
|
192
|
+
});
|
|
193
|
+
remainingAccounts.push({
|
|
194
|
+
isSigner: false,
|
|
195
|
+
isWritable: true,
|
|
196
|
+
pubkey: vault,
|
|
197
|
+
});
|
|
198
|
+
remainingAccounts.push({
|
|
199
|
+
isSigner: false,
|
|
200
|
+
isWritable: true,
|
|
201
|
+
pubkey: custodyTokenAccount,
|
|
202
|
+
});
|
|
203
|
+
remainingAccounts.push({
|
|
204
|
+
isSigner: false,
|
|
205
|
+
isWritable: false,
|
|
206
|
+
pubkey: spl_token_1.TOKEN_PROGRAM_ID,
|
|
207
|
+
});
|
|
208
|
+
remainingAccounts.push({
|
|
209
|
+
isSigner: false,
|
|
210
|
+
isWritable: false,
|
|
211
|
+
pubkey: tokenMessengerEventAuthority,
|
|
212
|
+
});
|
|
213
|
+
remainingAccounts.push({
|
|
214
|
+
isSigner: false,
|
|
215
|
+
isWritable: false,
|
|
216
|
+
pubkey: tokenMessengerMinterProgram.programId,
|
|
217
|
+
});
|
|
218
|
+
// Receive tokens on Solana.
|
|
219
|
+
console.log(`Receiving ${netSendAmount.toString()} tokens on Solana...`);
|
|
220
|
+
const receiveMessageTx = await messageTransmitterProgram.methods
|
|
221
|
+
.receiveMessage({
|
|
222
|
+
message: Buffer.from(message.replace("0x", ""), "hex"),
|
|
223
|
+
attestation: Buffer.from(attestation.replace("0x", ""), "hex"),
|
|
224
|
+
})
|
|
225
|
+
.accounts(receiveMessageAccounts)
|
|
226
|
+
.remainingAccounts(remainingAccounts)
|
|
227
|
+
.rpc();
|
|
228
|
+
console.log("\nReceived remote message");
|
|
229
|
+
console.log("Your transaction signature", receiveMessageTx);
|
|
230
|
+
}
|
|
231
|
+
async function executeRebalanceOnHubPool(l1TokenAddress, netSendAmount, solanaChainId) {
|
|
232
|
+
// Reconstruct the merkle tree for the pool rebalance.
|
|
233
|
+
const { poolRebalanceTree, poolRebalanceLeaf } = (0, poolRebalanceTree_1.constructSimpleRebalanceTree)(l1TokenAddress, netSendAmount, solanaChainId);
|
|
234
|
+
// Make sure the proposal liveness has passed, it has not been executed and rebalance root matches.
|
|
235
|
+
const currentRootBundleProposal = await hubPool.connect(ethersSigner).callStatic.rootBundleProposal();
|
|
236
|
+
if (currentRootBundleProposal.challengePeriodEndTimestamp > (await hubPool.callStatic.getCurrentTime()).toNumber())
|
|
237
|
+
throw new Error("Not passed liveness");
|
|
238
|
+
if (!currentRootBundleProposal.claimedBitMap.isZero())
|
|
239
|
+
throw new Error("Already claimed");
|
|
240
|
+
if (currentRootBundleProposal.poolRebalanceRoot !== poolRebalanceTree.getHexRoot())
|
|
241
|
+
throw new Error("Rebalance root mismatch");
|
|
242
|
+
// Execute the rebalance bundle on the HubPool.
|
|
243
|
+
console.log(`Executing ${netSendAmount.toString()} rebalance to spoke pool:`);
|
|
244
|
+
const tx = await hubPool
|
|
245
|
+
.connect(ethersSigner)
|
|
246
|
+
.executeRootBundle(poolRebalanceLeaf.chainId, poolRebalanceLeaf.groupIndex, poolRebalanceLeaf.bundleLpFees, poolRebalanceLeaf.netSendAmounts, poolRebalanceLeaf.runningBalances, poolRebalanceLeaf.leafId, poolRebalanceLeaf.l1Tokens, poolRebalanceTree.getHexProof(poolRebalanceLeaf));
|
|
247
|
+
console.log(`✔️ submitted tx hash: ${tx.hash}`);
|
|
248
|
+
await tx.wait();
|
|
249
|
+
console.log(`✔️ tx confirmed`);
|
|
250
|
+
return tx.hash;
|
|
251
|
+
}
|
|
252
|
+
// Run the executeRebalanceToSpokePool function
|
|
253
|
+
executeRebalanceToSpokePool();
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Script: Propose Root Bundle for USDC Rebalance to Hub Pool
|
|
3
|
+
*
|
|
4
|
+
* Submits a root bundle proposal on the Hub Pool to rebalance USDC from the Solana Spoke Pool to the Ethereum Hub Pool.
|
|
5
|
+
* After submission and the liveness period, the rebalance can be executed with `executeRebalanceToHubPool.ts`.
|
|
6
|
+
*
|
|
7
|
+
* Required Environment Variables:
|
|
8
|
+
* - TESTNET: (Optional) Set to "true" to use Sepolia; defaults to mainnet.
|
|
9
|
+
* - MNEMONIC: Wallet mnemonic to sign the Ethereum transaction.
|
|
10
|
+
* - HUB_POOL_ADDRESS: Ethereum address of the Hub Pool.
|
|
11
|
+
* - NODE_URL_1: Ethereum RPC URL for mainnet (ignored if TESTNET=true).
|
|
12
|
+
* - NODE_URL_11155111: Ethereum RPC URL for Sepolia (ignored if TESTNET=false).
|
|
13
|
+
*
|
|
14
|
+
* Required Argument:
|
|
15
|
+
* - `--netSendAmount`: The unscaled amount of USDC to rebalance. (e.g., for USDC with 6 decimals, 1 = 0.000001 USDC).
|
|
16
|
+
*
|
|
17
|
+
* Example Usage:
|
|
18
|
+
* TESTNET=true \
|
|
19
|
+
* NODE_URL_11155111=$NODE_URL_11155111 \
|
|
20
|
+
* MNEMONIC=$MNEMONIC \
|
|
21
|
+
* HUB_POOL_ADDRESS=$HUB_POOL_ADDRESS \
|
|
22
|
+
* anchor run proposeRebalanceToHubPool -- --netSendAmount 7
|
|
23
|
+
*
|
|
24
|
+
* Note:
|
|
25
|
+
* Ensure the required environment variables are set before running this script.
|
|
26
|
+
*/
|
|
27
|
+
export {};
|