@across-protocol/contracts 3.0.17 → 3.0.19
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/README.md +2 -0
- package/artifacts/build-info/9cb910e5bb5dd730cd01af84a0fb0466.json +1 -0
- package/artifacts/contracts/Ink_SpokePool.sol/Ink_SpokePool.dbg.json +4 -0
- package/artifacts/contracts/Ink_SpokePool.sol/Ink_SpokePool.json +2316 -0
- package/contracts/Ink_SpokePool.sol +72 -0
- package/dist/deploy/consts.js +8 -2
- package/dist/deployments/deployments.json +7 -1
- package/dist/hardhat.config.js +17 -0
- package/dist/scripts/svm/addressToPublicKey.d.ts +1 -0
- package/dist/scripts/svm/addressToPublicKey.js +20 -0
- package/dist/scripts/svm/bridgeLiabilityToHubPool.d.ts +25 -0
- package/dist/scripts/svm/bridgeLiabilityToHubPool.js +221 -0
- package/dist/scripts/svm/closeRelayerPdas.js +8 -9
- package/dist/scripts/svm/enableRoute.js +7 -1
- package/dist/scripts/svm/executeRebalanceToHubPool.d.ts +29 -0
- package/dist/scripts/svm/executeRebalanceToHubPool.js +345 -0
- package/dist/scripts/svm/executeRebalanceToSpokePool.d.ts +1 -0
- package/dist/scripts/svm/executeRebalanceToSpokePool.js +240 -0
- package/dist/scripts/svm/fakeFillWithRandomDistribution.js +6 -7
- package/dist/scripts/svm/initialize.js +2 -2
- 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 +97 -0
- package/dist/scripts/svm/publicKeyToAddress.d.ts +1 -0
- package/dist/scripts/svm/publicKeyToAddress.js +20 -0
- package/dist/scripts/svm/queryDeposits.js +3 -2
- package/dist/scripts/svm/queryFills.js +12 -14
- 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 +191 -0
- package/dist/scripts/svm/remoteHubPoolSetDepositRoute.js +16 -29
- package/dist/scripts/svm/remotePauseDeposits.js +21 -27
- package/dist/scripts/svm/simpleDeposit.js +7 -1
- package/dist/scripts/svm/simpleFakeRelayerRepayment.js +3 -3
- package/dist/scripts/svm/simpleFill.js +6 -6
- 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 +33 -0
- package/dist/scripts/svm/utils/helpers.js +60 -1
- package/dist/scripts/svm/utils/poolRebalanceTree.d.ts +22 -0
- package/dist/scripts/svm/utils/poolRebalanceTree.js +20 -0
- package/dist/src/svm/coders.d.ts +37 -0
- package/dist/src/svm/coders.js +250 -0
- package/dist/src/svm/conversionUtils.d.ts +22 -0
- package/dist/src/svm/conversionUtils.js +73 -0
- package/dist/src/svm/index.d.ts +6 -0
- package/dist/src/svm/index.js +22 -0
- package/dist/src/svm/instructionParamsUtils.d.ts +31 -0
- package/dist/src/svm/instructionParamsUtils.js +128 -0
- package/dist/src/svm/relayHashUtils.d.ts +30 -0
- package/dist/src/svm/relayHashUtils.js +209 -0
- package/dist/src/svm/solanaProgramUtils.d.ts +38 -0
- package/dist/src/svm/solanaProgramUtils.js +147 -0
- package/dist/src/svm/transactionUtils.d.ts +8 -0
- package/dist/src/svm/transactionUtils.js +55 -0
- package/dist/src/types/svm.d.ts +118 -0
- package/dist/src/types/svm.js +2 -0
- 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/MulticallHandler.js +2 -2
- package/dist/test/svm/SvmSpoke.Bundle.js +59 -60
- package/dist/test/svm/SvmSpoke.Deposit.js +23 -31
- package/dist/test/svm/SvmSpoke.Fill.AcrossPlus.js +16 -18
- package/dist/test/svm/SvmSpoke.Fill.js +38 -40
- package/dist/test/svm/SvmSpoke.HandleReceiveMessage.js +2 -2
- package/dist/test/svm/SvmSpoke.Ownership.js +11 -17
- package/dist/test/svm/SvmSpoke.RefundClaims.js +12 -11
- package/dist/test/svm/SvmSpoke.Routes.js +11 -7
- package/dist/test/svm/SvmSpoke.SlowFill.AcrossPlus.js +72 -16
- package/dist/test/svm/SvmSpoke.SlowFill.js +33 -28
- package/dist/test/svm/SvmSpoke.TokenBridge.js +15 -17
- package/dist/test/svm/SvmSpoke.common.d.ts +1 -49
- package/dist/test/svm/SvmSpoke.common.js +6 -5
- package/dist/test/svm/cctpHelpers.js +2 -2
- package/dist/test/svm/utils.d.ts +5 -67
- package/dist/test/svm/utils.js +10 -220
- package/dist/typechain/contracts/Ink_SpokePool.d.ts +1251 -0
- package/dist/typechain/contracts/Ink_SpokePool.js +2 -0
- package/dist/typechain/contracts/index.d.ts +1 -0
- package/dist/typechain/factories/contracts/Ink_SpokePool__factory.d.ts +1833 -0
- package/dist/typechain/factories/contracts/Ink_SpokePool__factory.js +2347 -0
- package/dist/typechain/factories/contracts/index.d.ts +1 -0
- package/dist/typechain/factories/contracts/index.js +3 -1
- package/dist/typechain/hardhat.d.ts +9 -0
- package/dist/typechain/index.d.ts +2 -0
- package/dist/typechain/index.js +5 -3
- package/package.json +2 -2
- package/dist/src/SvmUtils.d.ts +0 -50
- package/dist/src/SvmUtils.js +0 -415
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
// SPDX-License-Identifier: BUSL-1.1
|
|
2
|
+
pragma solidity ^0.8.0;
|
|
3
|
+
import "@eth-optimism/contracts/libraries/constants/Lib_PredeployAddresses.sol";
|
|
4
|
+
|
|
5
|
+
import "./Ovm_SpokePool.sol";
|
|
6
|
+
import "./external/interfaces/CCTPInterfaces.sol";
|
|
7
|
+
import { IOpUSDCBridgeAdapter } from "./external/interfaces/IOpUSDCBridgeAdapter.sol";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @notice Ink Spoke pool.
|
|
11
|
+
* @custom:security-contact bugs@across.to
|
|
12
|
+
*/
|
|
13
|
+
contract Ink_SpokePool is Ovm_SpokePool {
|
|
14
|
+
using SafeERC20 for IERC20;
|
|
15
|
+
|
|
16
|
+
// Address of the custom L2 USDC bridge.
|
|
17
|
+
address private constant USDC_BRIDGE = address(0);
|
|
18
|
+
|
|
19
|
+
/// @custom:oz-upgrades-unsafe-allow constructor
|
|
20
|
+
constructor(
|
|
21
|
+
address _wrappedNativeTokenAddress,
|
|
22
|
+
uint32 _depositQuoteTimeBuffer,
|
|
23
|
+
uint32 _fillDeadlineBuffer,
|
|
24
|
+
IERC20 _l2Usdc,
|
|
25
|
+
ITokenMessenger _cctpTokenMessenger
|
|
26
|
+
)
|
|
27
|
+
Ovm_SpokePool(
|
|
28
|
+
_wrappedNativeTokenAddress,
|
|
29
|
+
_depositQuoteTimeBuffer,
|
|
30
|
+
_fillDeadlineBuffer,
|
|
31
|
+
_l2Usdc,
|
|
32
|
+
_cctpTokenMessenger
|
|
33
|
+
)
|
|
34
|
+
{} // solhint-disable-line no-empty-blocks
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* @notice Construct the OVM World Chain SpokePool.
|
|
38
|
+
* @param _initialDepositId Starting deposit ID. Set to 0 unless this is a re-deployment in order to mitigate
|
|
39
|
+
* relay hash collisions.
|
|
40
|
+
* @param _crossDomainAdmin Cross domain admin to set. Can be changed by admin.
|
|
41
|
+
* @param _withdrawalRecipient Address which receives token withdrawals. Can be changed by admin. For Spoke Pools on L2, this will
|
|
42
|
+
*/
|
|
43
|
+
function initialize(
|
|
44
|
+
uint32 _initialDepositId,
|
|
45
|
+
address _crossDomainAdmin,
|
|
46
|
+
address _withdrawalRecipient
|
|
47
|
+
) public initializer {
|
|
48
|
+
__OvmSpokePool_init(_initialDepositId, _crossDomainAdmin, _withdrawalRecipient, Lib_PredeployAddresses.OVM_ETH);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* @notice Ink-specific logic to bridge tokens back to the hub pool contract on L1.
|
|
53
|
+
* @param amountToReturn Amount of the token to bridge back.
|
|
54
|
+
* @param l2TokenAddress Address of the l2 Token to bridge back. This token will either be bridged back to the token defined in the mapping `remoteL1Tokens`,
|
|
55
|
+
* or via the canonical mapping defined in the bridge contract retrieved from `tokenBridges`.
|
|
56
|
+
* @dev This implementation deviates slightly from `_bridgeTokensToHubPool` in the `Ovm_SpokePool` contract since World Chain has a USDC bridge which uses
|
|
57
|
+
* a custom interface. This is because the USDC token on World Chain is meant to be upgraded to a native, CCTP supported version in the future.
|
|
58
|
+
*/
|
|
59
|
+
function _bridgeTokensToHubPool(uint256 amountToReturn, address l2TokenAddress) internal virtual override {
|
|
60
|
+
// Handle custom USDC bridge which doesn't conform to the standard bridge interface. In the future, CCTP may be used to bridge USDC to mainnet, in which
|
|
61
|
+
// case bridging logic is handled by the Ovm_SpokePool code. In the meantime, if CCTP is not enabled, then use the USDC bridge. Once CCTP is activated on
|
|
62
|
+
// WorldChain, this block of code will be unused.
|
|
63
|
+
if (l2TokenAddress == address(usdcToken) && !_isCCTPEnabled()) {
|
|
64
|
+
usdcToken.safeIncreaseAllowance(USDC_BRIDGE, amountToReturn);
|
|
65
|
+
IOpUSDCBridgeAdapter(USDC_BRIDGE).sendMessage(
|
|
66
|
+
withdrawalRecipient, // _to. Withdraw, over the bridge, to the l1 hub pool contract.
|
|
67
|
+
amountToReturn, // _amount.
|
|
68
|
+
l1Gas // _minGasLimit. Same value used in other OpStack bridges.
|
|
69
|
+
);
|
|
70
|
+
} else super._bridgeTokensToHubPool(amountToReturn, l2TokenAddress);
|
|
71
|
+
}
|
|
72
|
+
}
|
package/dist/deploy/consts.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.CIRCLE_DOMAIN_IDs = exports.POLYGON_CHAIN_IDS = exports.L2_ADDRESS_MAP = exports.OP_STACK_ADDRESS_MAP = exports.L1_ADDRESS_MAP = exports.AZERO_GAS_PRICE = exports.ARBITRUM_MAX_SUBMISSION_COST = exports.FILL_DEADLINE_BUFFER = exports.QUOTE_TIME_BUFFER = exports.AZERO = exports.WAZERO = exports.WMATIC = exports.WETH = exports.USDCe = exports.USDC = exports.ZERO_ADDRESS = void 0;
|
|
4
|
-
|
|
5
|
-
Object.defineProperty(exports, "ZERO_ADDRESS", { enumerable: true, get: function () { return common_1.ZERO_ADDRESS; } });
|
|
4
|
+
const common_1 = require("@uma/common");
|
|
6
5
|
const utils_1 = require("../utils");
|
|
6
|
+
var common_2 = require("@uma/common");
|
|
7
|
+
Object.defineProperty(exports, "ZERO_ADDRESS", { enumerable: true, get: function () { return common_2.ZERO_ADDRESS; } });
|
|
7
8
|
exports.USDC = utils_1.TOKEN_SYMBOLS_MAP.USDC.addresses;
|
|
8
9
|
exports.USDCe = utils_1.TOKEN_SYMBOLS_MAP["USDC.e"].addresses;
|
|
9
10
|
exports.WETH = utils_1.TOKEN_SYMBOLS_MAP.WETH.addresses;
|
|
@@ -74,6 +75,11 @@ exports.OP_STACK_ADDRESS_MAP = {
|
|
|
74
75
|
L1CrossDomainMessenger: "0x5D4472f31Bd9385709ec61305AFc749F0fA8e9d0",
|
|
75
76
|
L1StandardBridge: "0x697402166Fbf2F22E970df8a6486Ef171dbfc524",
|
|
76
77
|
},
|
|
78
|
+
[utils_1.CHAIN_IDs.INK]: {
|
|
79
|
+
L1CrossDomainMessenger: "0x69d3cf86b2bf1a9e99875b7e2d9b6a84426c171f",
|
|
80
|
+
L1StandardBridge: "0x88ff1e5b602916615391f55854588efcbb7663f0",
|
|
81
|
+
L1OpUSDCBridgeAdapter: common_1.ZERO_ADDRESS,
|
|
82
|
+
},
|
|
77
83
|
[utils_1.CHAIN_IDs.LISK]: {
|
|
78
84
|
L1CrossDomainMessenger: "0x31B72D76FB666844C41EdF08dF0254875Dbb7edB",
|
|
79
85
|
L1StandardBridge: "0x2658723Bf70c7667De6B25F99fcce13A16D25d08",
|
|
@@ -28,7 +28,8 @@
|
|
|
28
28
|
"Redstone_Adapter": { "address": "0x188F8C95B7cfB7993B53a4F643efa687916f73fA", "blockNumber": 20432774 },
|
|
29
29
|
"Zora_Adapter": { "address": "0x024f2fc31cbdd8de17194b1892c834f98ef5169b", "blockNumber": 20512287 },
|
|
30
30
|
"WorldChain_Adapter": { "address": "0xA8399e221a583A57F54Abb5bA22f31b5D6C09f32", "blockNumber": 20963234 },
|
|
31
|
-
"AlephZero_Adapter": { "address": "0x6F4083304C2cA99B077ACE06a5DcF670615915Af", "blockNumber": 21131132 }
|
|
31
|
+
"AlephZero_Adapter": { "address": "0x6F4083304C2cA99B077ACE06a5DcF670615915Af", "blockNumber": 21131132 },
|
|
32
|
+
"Ink_Adapter": { "address": "0x7e90a40c7519b041a7df6498fbf5662e8cfc61d2", "blockNumber": 21438590 }
|
|
32
33
|
},
|
|
33
34
|
"10": {
|
|
34
35
|
"SpokePool": { "address": "0x6f26Bf09B1C792e3228e5467807a900A503c0281", "blockNumber": 93903076 },
|
|
@@ -157,5 +158,10 @@
|
|
|
157
158
|
"41455": {
|
|
158
159
|
"SpokePool": { "address": "0x13fDac9F9b4777705db45291bbFF3c972c6d1d97", "blockNumber": 4240318 },
|
|
159
160
|
"MulticallHandler": { "address": "0x924a9f036260DdD5808007E1AA95f08eD08aA569", "blockNumber": 4112529 }
|
|
161
|
+
},
|
|
162
|
+
"57073": {
|
|
163
|
+
"SpokePool": { "address": "0xeF684C38F94F48775959ECf2012D7E864ffb9dd4", "blockNumber": 1139240 },
|
|
164
|
+
"SpokePoolVerifier": { "address": "0xB4A8d45647445EA9FC3E1058096142390683dBC2", "blockNumber": 1152853 },
|
|
165
|
+
"MulticallHandler": { "address": "0x924a9f036260DdD5808007E1AA95f08eD08aA569", "blockNumber": 1145284 }
|
|
160
166
|
}
|
|
161
167
|
}
|
package/dist/hardhat.config.js
CHANGED
|
@@ -101,6 +101,7 @@ const config = {
|
|
|
101
101
|
"contracts/Base_SpokePool.sol": LARGE_CONTRACT_COMPILER_SETTINGS,
|
|
102
102
|
"contracts/Optimism_SpokePool.sol": LARGE_CONTRACT_COMPILER_SETTINGS,
|
|
103
103
|
"contracts/WorldChain_SpokePool.sol": LARGE_CONTRACT_COMPILER_SETTINGS,
|
|
104
|
+
"contracts/Ink_SpokePool.sol": LARGE_CONTRACT_COMPILER_SETTINGS,
|
|
104
105
|
},
|
|
105
106
|
},
|
|
106
107
|
zksolc: {
|
|
@@ -203,6 +204,13 @@ const config = {
|
|
|
203
204
|
accounts: { mnemonic },
|
|
204
205
|
companionNetworks: { l1: "sepolia" },
|
|
205
206
|
},
|
|
207
|
+
ink: {
|
|
208
|
+
chainId: constants_1.CHAIN_IDs.INK,
|
|
209
|
+
url: "https://rpc-gel.inkonchain.com",
|
|
210
|
+
saveDeployments: true,
|
|
211
|
+
accounts: { mnemonic },
|
|
212
|
+
companionNetworks: { l1: "mainnet" },
|
|
213
|
+
},
|
|
206
214
|
linea: {
|
|
207
215
|
chainId: constants_1.CHAIN_IDs.LINEA,
|
|
208
216
|
url: `https://linea-mainnet.infura.io/v3/${process.env.INFURA_API_KEY}`,
|
|
@@ -337,6 +345,7 @@ const config = {
|
|
|
337
345
|
zora: "routescan",
|
|
338
346
|
worldchain: "blockscout",
|
|
339
347
|
alephzero: "blockscout",
|
|
348
|
+
ink: "blockscout",
|
|
340
349
|
},
|
|
341
350
|
customChains: [
|
|
342
351
|
{
|
|
@@ -363,6 +372,14 @@ const config = {
|
|
|
363
372
|
browserURL: "https://sepolia.basescan.org",
|
|
364
373
|
},
|
|
365
374
|
},
|
|
375
|
+
{
|
|
376
|
+
network: "ink",
|
|
377
|
+
chainId: constants_1.CHAIN_IDs.INK,
|
|
378
|
+
urls: {
|
|
379
|
+
apiURL: "https://explorer.inkonchain.com/api",
|
|
380
|
+
browserURL: "https://explorer.inkonchain.com",
|
|
381
|
+
},
|
|
382
|
+
},
|
|
366
383
|
{
|
|
367
384
|
network: "linea",
|
|
368
385
|
chainId: constants_1.CHAIN_IDs.LINEA,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const svm_1 = require("../../src/svm");
|
|
7
|
+
const yargs_1 = __importDefault(require("yargs"));
|
|
8
|
+
const helpers_1 = require("yargs/helpers");
|
|
9
|
+
const argv = (0, yargs_1.default)((0, helpers_1.hideBin)(process.argv)).option("address", {
|
|
10
|
+
type: "string",
|
|
11
|
+
demandOption: true,
|
|
12
|
+
describe: "Ethereum address to convert",
|
|
13
|
+
}).argv;
|
|
14
|
+
async function logPublicKey() {
|
|
15
|
+
const address = (await argv).address;
|
|
16
|
+
const publicKey = (0, svm_1.evmAddressToPublicKey)(address);
|
|
17
|
+
console.log("Ethereum Address:", address);
|
|
18
|
+
console.log("Associated Public Key:", publicKey.toString());
|
|
19
|
+
}
|
|
20
|
+
logPublicKey();
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Script: Bridge USDC Liability to Hub Pool
|
|
3
|
+
*
|
|
4
|
+
* This script bridges pending USDC liabilities from the Solana Spoke Pool to the Ethereum Hub Pool using the CCTP
|
|
5
|
+
* (Circle Cross-Chain Transfer Protocol). It manages CCTP message attestations, verifies transfer completion, and
|
|
6
|
+
* updates the Hub Pool’s USDC balance.
|
|
7
|
+
*
|
|
8
|
+
* Required Environment Variables:
|
|
9
|
+
* - MNEMONIC: Wallet mnemonic to sign the Ethereum transaction.
|
|
10
|
+
* - HUB_POOL_ADDRESS: Ethereum address of the Hub Pool.
|
|
11
|
+
* - NODE_URL_${CHAIN_ID}: Ethereum RPC URL (must point to the Mainnet or Sepolia depending on Solana cluster).
|
|
12
|
+
*
|
|
13
|
+
* Example Usage:
|
|
14
|
+
* NODE_URL_11155111=$NODE_URL_11155111 \
|
|
15
|
+
* MNEMONIC=$MNEMONIC \
|
|
16
|
+
* HUB_POOL_ADDRESS=$HUB_POOL_ADDRESS \
|
|
17
|
+
* anchor run bridgeLiabilityToHubPool \
|
|
18
|
+
* --provider.cluster "devnet" \
|
|
19
|
+
* --provider.wallet $SOLANA_PKEY_PATH
|
|
20
|
+
*
|
|
21
|
+
* Note:
|
|
22
|
+
* - Ensure all required environment variables are properly configured.
|
|
23
|
+
* - Pending USDC liabilities must exist in the Solana Spoke Pool for the script to execute.
|
|
24
|
+
*/
|
|
25
|
+
export {};
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Script: Bridge USDC Liability to Hub Pool
|
|
4
|
+
*
|
|
5
|
+
* This script bridges pending USDC liabilities from the Solana Spoke Pool to the Ethereum Hub Pool using the CCTP
|
|
6
|
+
* (Circle Cross-Chain Transfer Protocol). It manages CCTP message attestations, verifies transfer completion, and
|
|
7
|
+
* updates the Hub Pool’s USDC balance.
|
|
8
|
+
*
|
|
9
|
+
* Required Environment Variables:
|
|
10
|
+
* - MNEMONIC: Wallet mnemonic to sign the Ethereum transaction.
|
|
11
|
+
* - HUB_POOL_ADDRESS: Ethereum address of the Hub Pool.
|
|
12
|
+
* - NODE_URL_${CHAIN_ID}: Ethereum RPC URL (must point to the Mainnet or Sepolia depending on Solana cluster).
|
|
13
|
+
*
|
|
14
|
+
* Example Usage:
|
|
15
|
+
* NODE_URL_11155111=$NODE_URL_11155111 \
|
|
16
|
+
* MNEMONIC=$MNEMONIC \
|
|
17
|
+
* HUB_POOL_ADDRESS=$HUB_POOL_ADDRESS \
|
|
18
|
+
* anchor run bridgeLiabilityToHubPool \
|
|
19
|
+
* --provider.cluster "devnet" \
|
|
20
|
+
* --provider.wallet $SOLANA_PKEY_PATH
|
|
21
|
+
*
|
|
22
|
+
* Note:
|
|
23
|
+
* - Ensure all required environment variables are properly configured.
|
|
24
|
+
* - Pending USDC liabilities must exist in the Solana Spoke Pool for the script to execute.
|
|
25
|
+
*/
|
|
26
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
27
|
+
if (k2 === undefined) k2 = k;
|
|
28
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
29
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
30
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
31
|
+
}
|
|
32
|
+
Object.defineProperty(o, k2, desc);
|
|
33
|
+
}) : (function(o, m, k, k2) {
|
|
34
|
+
if (k2 === undefined) k2 = k;
|
|
35
|
+
o[k2] = m[k];
|
|
36
|
+
}));
|
|
37
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
38
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
39
|
+
}) : function(o, v) {
|
|
40
|
+
o["default"] = v;
|
|
41
|
+
});
|
|
42
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
43
|
+
if (mod && mod.__esModule) return mod;
|
|
44
|
+
var result = {};
|
|
45
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
46
|
+
__setModuleDefault(result, mod);
|
|
47
|
+
return result;
|
|
48
|
+
};
|
|
49
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
50
|
+
const anchor = __importStar(require("@coral-xyz/anchor"));
|
|
51
|
+
const anchor_1 = require("@coral-xyz/anchor");
|
|
52
|
+
const spl_token_1 = require("@solana/spl-token");
|
|
53
|
+
const web3_js_1 = require("@solana/web3.js");
|
|
54
|
+
// eslint-disable-next-line camelcase
|
|
55
|
+
const constants_1 = require("./utils/constants");
|
|
56
|
+
const constants_2 = require("@across-protocol/constants");
|
|
57
|
+
const common_1 = require("@uma/common");
|
|
58
|
+
const ethers_1 = require("ethers");
|
|
59
|
+
const cctpHelpers_1 = require("../../test/svm/cctpHelpers");
|
|
60
|
+
const typechain_1 = require("../../typechain");
|
|
61
|
+
const helpers_1 = require("./utils/helpers");
|
|
62
|
+
// Set up Solana provider.
|
|
63
|
+
const provider = anchor_1.AnchorProvider.env();
|
|
64
|
+
anchor.setProvider(provider);
|
|
65
|
+
// Get Solana programs and IDLs.
|
|
66
|
+
const svmSpokeIdl = require("../../target/idl/svm_spoke.json");
|
|
67
|
+
const svmSpokeProgram = new anchor_1.Program(svmSpokeIdl, provider);
|
|
68
|
+
const messageTransmitterIdl = require("../../target/idl/message_transmitter.json");
|
|
69
|
+
const tokenMessengerMinterIdl = require("../../target/idl/token_messenger_minter.json");
|
|
70
|
+
// CCTP domains.
|
|
71
|
+
const ethereumDomain = 0; // Ethereum
|
|
72
|
+
const solanaDomain = 5; // Solana
|
|
73
|
+
// Set up Ethereum provider and signer.
|
|
74
|
+
const isDevnet = (0, helpers_1.isSolanaDevnet)(provider);
|
|
75
|
+
const nodeURL = isDevnet ? (0, common_1.getNodeUrl)("sepolia", true) : (0, common_1.getNodeUrl)("mainnet", true);
|
|
76
|
+
const ethersProvider = new ethers_1.ethers.providers.JsonRpcProvider(nodeURL);
|
|
77
|
+
const ethersSigner = ethers_1.ethers.Wallet.fromMnemonic((0, helpers_1.requireEnv)("MNEMONIC")).connect(ethersProvider);
|
|
78
|
+
// Get the HubPool contract instance.
|
|
79
|
+
const hubPoolAddress = ethers_1.ethers.utils.getAddress((0, helpers_1.requireEnv)("HUB_POOL_ADDRESS")); // Used to check USDC balance before and after.
|
|
80
|
+
const messageTransmitterAbi = [
|
|
81
|
+
{
|
|
82
|
+
inputs: [
|
|
83
|
+
{
|
|
84
|
+
internalType: "bytes",
|
|
85
|
+
name: "message",
|
|
86
|
+
type: "bytes",
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
internalType: "bytes",
|
|
90
|
+
name: "attestation",
|
|
91
|
+
type: "bytes",
|
|
92
|
+
},
|
|
93
|
+
],
|
|
94
|
+
name: "receiveMessage",
|
|
95
|
+
outputs: [
|
|
96
|
+
{
|
|
97
|
+
internalType: "bool",
|
|
98
|
+
name: "success",
|
|
99
|
+
type: "bool",
|
|
100
|
+
},
|
|
101
|
+
],
|
|
102
|
+
stateMutability: "nonpayable",
|
|
103
|
+
type: "function",
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
inputs: [
|
|
107
|
+
{
|
|
108
|
+
internalType: "bytes32",
|
|
109
|
+
name: "",
|
|
110
|
+
type: "bytes32",
|
|
111
|
+
},
|
|
112
|
+
],
|
|
113
|
+
name: "usedNonces",
|
|
114
|
+
outputs: [
|
|
115
|
+
{
|
|
116
|
+
internalType: "uint256",
|
|
117
|
+
name: "",
|
|
118
|
+
type: "uint256",
|
|
119
|
+
},
|
|
120
|
+
],
|
|
121
|
+
stateMutability: "view",
|
|
122
|
+
type: "function",
|
|
123
|
+
},
|
|
124
|
+
];
|
|
125
|
+
async function bridgeLiabilityToHubPool() {
|
|
126
|
+
const seed = constants_1.SOLANA_SPOKE_STATE_SEED; // Seed is always 0 for the state account PDA in public networks.
|
|
127
|
+
// Resolve Solana USDC addresses.
|
|
128
|
+
const svmUsdc = isDevnet ? constants_1.SOLANA_USDC_DEVNET : constants_1.SOLANA_USDC_MAINNET;
|
|
129
|
+
const [statePda, _] = web3_js_1.PublicKey.findProgramAddressSync([Buffer.from("state"), seed.toArrayLike(Buffer, "le", 8)], svmSpokeProgram.programId);
|
|
130
|
+
const irisApiUrl = isDevnet ? constants_1.CIRCLE_IRIS_API_URL_DEVNET : constants_1.CIRCLE_IRIS_API_URL_MAINNET;
|
|
131
|
+
const cctpMessageTransmitter = isDevnet
|
|
132
|
+
? constants_1.SEPOLIA_CCTP_MESSAGE_TRANSMITTER_ADDRESS
|
|
133
|
+
: constants_1.MAINNET_CCTP_MESSAGE_TRANSMITTER_ADDRESS;
|
|
134
|
+
const messageTransmitter = new ethers_1.ethers.Contract(cctpMessageTransmitter, messageTransmitterAbi, ethersSigner);
|
|
135
|
+
const evmChainId = (await ethersProvider.getNetwork()).chainId;
|
|
136
|
+
const usdcAddress = constants_2.TOKEN_SYMBOLS_MAP.USDC.addresses[evmChainId];
|
|
137
|
+
const usdc = typechain_1.BondToken__factory.connect(usdcAddress, ethersProvider);
|
|
138
|
+
const usdcBalanceBefore = await usdc.balanceOf(hubPoolAddress);
|
|
139
|
+
console.log("Receiving liability from Solana Spoke Pool to Ethereum Hub Pool...");
|
|
140
|
+
console.table([
|
|
141
|
+
{ Property: "isTestnet", Value: isDevnet },
|
|
142
|
+
{ Property: "hubPoolAddress", Value: hubPoolAddress },
|
|
143
|
+
{ Property: "svmSpokeProgramProgramId", Value: svmSpokeProgram.programId.toString() },
|
|
144
|
+
{ Property: "providerPublicKey", Value: provider.wallet.publicKey.toString() },
|
|
145
|
+
{ Property: "usdcBalanceBefore", Value: usdcBalanceBefore.toString() },
|
|
146
|
+
]);
|
|
147
|
+
const [transferLiability] = web3_js_1.PublicKey.findProgramAddressSync([Buffer.from("transfer_liability"), new web3_js_1.PublicKey(svmUsdc).toBuffer()], svmSpokeProgram.programId);
|
|
148
|
+
const liability = await svmSpokeProgram.account.transferLiability.fetch(transferLiability);
|
|
149
|
+
console.log(`Pending transfer liability: ${(0, helpers_1.formatUsdc)(ethers_1.BigNumber.from(liability.pendingToHubPool.toString()))} USDC.`);
|
|
150
|
+
if (liability.pendingToHubPool.eq(new anchor_1.BN(0))) {
|
|
151
|
+
console.log("No pending transfer liability to bridge. Exiting...");
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
console.log("Bridging liability to hub pool...");
|
|
155
|
+
const txHash = await bridgeTokensToHubPool(liability.pendingToHubPool, provider.wallet, statePda, new web3_js_1.PublicKey(svmUsdc));
|
|
156
|
+
const attestationResponse = await (0, cctpHelpers_1.getMessages)(txHash, solanaDomain, irisApiUrl);
|
|
157
|
+
const { attestation, message, eventNonce } = attestationResponse.messages[0];
|
|
158
|
+
console.log("CCTP attestation response:", attestationResponse.messages[0]);
|
|
159
|
+
const nonceHash = ethers_1.ethers.utils.solidityKeccak256(["uint32", "uint64"], [solanaDomain, eventNonce]);
|
|
160
|
+
const usedNonces = await messageTransmitter.usedNonces(nonceHash);
|
|
161
|
+
if (usedNonces.eq(1)) {
|
|
162
|
+
console.log(`Skipping already received message. Exiting...`);
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
console.log("Receiving message from CCTP...");
|
|
166
|
+
const receiveTx = await messageTransmitter.receiveMessage(message, attestation);
|
|
167
|
+
console.log(`Tx hash: ${receiveTx.hash}`);
|
|
168
|
+
await receiveTx.wait();
|
|
169
|
+
console.log(`Received message`);
|
|
170
|
+
const usdcBalanceAfter = await usdc.balanceOf(hubPoolAddress);
|
|
171
|
+
console.log(`Hub Pool USDC balance after: ${(0, helpers_1.formatUsdc)(usdcBalanceAfter)}. Received ${(0, helpers_1.formatUsdc)(usdcBalanceAfter.sub(usdcBalanceBefore))} USDC.`);
|
|
172
|
+
console.log("✅ Bridge liability to hub pool completed successfully.");
|
|
173
|
+
}
|
|
174
|
+
async function bridgeTokensToHubPool(amount, signer, statePda, inputToken) {
|
|
175
|
+
const messageTransmitterProgram = new anchor_1.Program(messageTransmitterIdl, provider);
|
|
176
|
+
const vault = (0, spl_token_1.getAssociatedTokenAddressSync)(inputToken, statePda, true, spl_token_1.TOKEN_PROGRAM_ID, spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID);
|
|
177
|
+
// Derive the transferLiability PDA
|
|
178
|
+
const [transferLiability] = web3_js_1.PublicKey.findProgramAddressSync([Buffer.from("transfer_liability"), inputToken.toBuffer()], svmSpokeProgram.programId);
|
|
179
|
+
const tokenMessengerMinterProgram = new anchor_1.Program(tokenMessengerMinterIdl, provider);
|
|
180
|
+
const [tokenMessengerMinterSenderAuthority] = web3_js_1.PublicKey.findProgramAddressSync([Buffer.from("sender_authority")], tokenMessengerMinterProgram.programId);
|
|
181
|
+
const [messageTransmitter] = web3_js_1.PublicKey.findProgramAddressSync([Buffer.from("message_transmitter")], messageTransmitterProgram.programId);
|
|
182
|
+
const [tokenMessenger] = web3_js_1.PublicKey.findProgramAddressSync([Buffer.from("token_messenger")], tokenMessengerMinterProgram.programId);
|
|
183
|
+
const [remoteTokenMessenger] = web3_js_1.PublicKey.findProgramAddressSync([Buffer.from("remote_token_messenger"), Buffer.from(anchor.utils.bytes.utf8.encode(ethereumDomain.toString()))], tokenMessengerMinterProgram.programId);
|
|
184
|
+
const [tokenMinter] = web3_js_1.PublicKey.findProgramAddressSync([Buffer.from("token_minter")], tokenMessengerMinterProgram.programId);
|
|
185
|
+
const [localToken] = web3_js_1.PublicKey.findProgramAddressSync([Buffer.from("local_token"), inputToken.toBuffer()], tokenMessengerMinterProgram.programId);
|
|
186
|
+
const [cctpEventAuthority] = web3_js_1.PublicKey.findProgramAddressSync([Buffer.from("__event_authority")], tokenMessengerMinterProgram.programId);
|
|
187
|
+
const messageSentEventData = anchor.web3.Keypair.generate(); // This will hold the message sent event data.
|
|
188
|
+
const bridgeTokensToHubPoolAccounts = {
|
|
189
|
+
payer: signer.publicKey,
|
|
190
|
+
mint: inputToken,
|
|
191
|
+
state: statePda,
|
|
192
|
+
transferLiability,
|
|
193
|
+
vault,
|
|
194
|
+
tokenMessengerMinterSenderAuthority,
|
|
195
|
+
messageTransmitter,
|
|
196
|
+
tokenMessenger,
|
|
197
|
+
remoteTokenMessenger,
|
|
198
|
+
tokenMinter,
|
|
199
|
+
localToken,
|
|
200
|
+
messageSentEventData: messageSentEventData.publicKey,
|
|
201
|
+
messageTransmitterProgram: messageTransmitterProgram.programId,
|
|
202
|
+
tokenMessengerMinterProgram: tokenMessengerMinterProgram.programId,
|
|
203
|
+
tokenProgram: spl_token_1.TOKEN_PROGRAM_ID,
|
|
204
|
+
systemProgram: web3_js_1.SystemProgram.programId,
|
|
205
|
+
cctpEventAuthority: cctpEventAuthority,
|
|
206
|
+
program: svmSpokeProgram.programId,
|
|
207
|
+
};
|
|
208
|
+
const initialVaultBalance = (await provider.connection.getTokenAccountBalance(vault)).value.amount;
|
|
209
|
+
const tx = await svmSpokeProgram.methods
|
|
210
|
+
.bridgeTokensToHubPool(new anchor_1.BN(amount))
|
|
211
|
+
.accounts(bridgeTokensToHubPoolAccounts)
|
|
212
|
+
.signers([messageSentEventData])
|
|
213
|
+
.rpc();
|
|
214
|
+
const finalVaultBalance = (await provider.connection.getTokenAccountBalance(vault)).value.amount;
|
|
215
|
+
console.log(`SVM Spoke Pool Initial Vault balance: ${(0, helpers_1.formatUsdc)(ethers_1.BigNumber.from(initialVaultBalance))} USDC.`);
|
|
216
|
+
console.log(`SVM Spoke Pool Final Vault balance: ${(0, helpers_1.formatUsdc)(ethers_1.BigNumber.from(finalVaultBalance))} USDC.`);
|
|
217
|
+
console.log(`Sent ${(0, helpers_1.formatUsdc)(ethers_1.BigNumber.from(initialVaultBalance).sub(ethers_1.BigNumber.from(finalVaultBalance)))} USDC through CCTP.`);
|
|
218
|
+
return tx;
|
|
219
|
+
}
|
|
220
|
+
// Run the bridgeLiabilityToHubPool function
|
|
221
|
+
bridgeLiabilityToHubPool();
|
|
@@ -33,8 +33,7 @@ const anchor_1 = require("@coral-xyz/anchor");
|
|
|
33
33
|
const web3_js_1 = require("@solana/web3.js");
|
|
34
34
|
const yargs_1 = __importDefault(require("yargs"));
|
|
35
35
|
const helpers_1 = require("yargs/helpers");
|
|
36
|
-
const
|
|
37
|
-
const SvmUtils_2 = require("../../src/SvmUtils");
|
|
36
|
+
const svm_1 = require("../../src/svm");
|
|
38
37
|
// Set up the provider
|
|
39
38
|
const provider = anchor_1.AnchorProvider.env();
|
|
40
39
|
anchor.setProvider(provider);
|
|
@@ -55,7 +54,7 @@ async function closeExpiredRelays() {
|
|
|
55
54
|
{ Property: "programId", Value: programId.toString() },
|
|
56
55
|
]);
|
|
57
56
|
try {
|
|
58
|
-
const events = await (0,
|
|
57
|
+
const events = await (0, svm_1.readProgramEvents)(provider.connection, program);
|
|
59
58
|
const fillEvents = events.filter((event) => event.name === "filledV3Relay" && new web3_js_1.PublicKey(event.data.relayer).equals(relayer));
|
|
60
59
|
console.log(`Number of fill events found: ${fillEvents.length}`);
|
|
61
60
|
if (fillEvents.length === 0) {
|
|
@@ -78,7 +77,7 @@ async function closeExpiredRelays() {
|
|
|
78
77
|
}
|
|
79
78
|
async function closeFillPda(eventData, seed) {
|
|
80
79
|
// Accept seed as a parameter
|
|
81
|
-
const
|
|
80
|
+
const relayEventData = {
|
|
82
81
|
depositor: new web3_js_1.PublicKey(eventData.depositor),
|
|
83
82
|
recipient: new web3_js_1.PublicKey(eventData.recipient),
|
|
84
83
|
exclusiveRelayer: new web3_js_1.PublicKey(eventData.exclusiveRelayer),
|
|
@@ -90,13 +89,13 @@ async function closeFillPda(eventData, seed) {
|
|
|
90
89
|
depositId: eventData.depositId,
|
|
91
90
|
fillDeadline: eventData.fillDeadline,
|
|
92
91
|
exclusivityDeadline: eventData.exclusivityDeadline,
|
|
93
|
-
|
|
92
|
+
messageHash: eventData.messageHash,
|
|
94
93
|
};
|
|
95
94
|
const [statePda] = web3_js_1.PublicKey.findProgramAddressSync([Buffer.from("state"), seed.toArrayLike(Buffer, "le", 8)], programId);
|
|
96
95
|
// Fetch the state to get the chainId
|
|
97
96
|
const state = await program.account.state.fetch(statePda);
|
|
98
97
|
const chainId = new anchor_1.BN(state.chainId);
|
|
99
|
-
const relayHashUint8Array = (0,
|
|
98
|
+
const relayHashUint8Array = (0, svm_1.calculateRelayEventHashUint8Array)(relayEventData, chainId);
|
|
100
99
|
const [fillStatusPda] = web3_js_1.PublicKey.findProgramAddressSync([Buffer.from("fills"), relayHashUint8Array], programId);
|
|
101
100
|
try {
|
|
102
101
|
// Check if the fillStatusPda account exists
|
|
@@ -106,8 +105,8 @@ async function closeFillPda(eventData, seed) {
|
|
|
106
105
|
return;
|
|
107
106
|
}
|
|
108
107
|
// Display additional information in a table
|
|
109
|
-
console.log("Found a relay to close. Relay
|
|
110
|
-
console.table(Object.entries(
|
|
108
|
+
console.log("Found a relay to close. Relay event data:");
|
|
109
|
+
console.table(Object.entries(relayEventData).map(([key, value]) => ({
|
|
111
110
|
key,
|
|
112
111
|
value: value.toString(),
|
|
113
112
|
})));
|
|
@@ -116,7 +115,7 @@ async function closeFillPda(eventData, seed) {
|
|
|
116
115
|
{ Property: "Fill Status PDA", Value: fillStatusPda.toString() },
|
|
117
116
|
{ Property: "Relay Hash", Value: Buffer.from(relayHashUint8Array).toString("hex") },
|
|
118
117
|
]);
|
|
119
|
-
const tx = await program.methods.closeFillPda(
|
|
118
|
+
const tx = await program.methods.closeFillPda()
|
|
120
119
|
.accounts({
|
|
121
120
|
state: statePda,
|
|
122
121
|
signer: provider.wallet.publicKey,
|
|
@@ -39,6 +39,7 @@ anchor.setProvider(provider);
|
|
|
39
39
|
const idl = require("../../target/idl/svm_spoke.json");
|
|
40
40
|
const program = new anchor_1.Program(idl, provider);
|
|
41
41
|
const programId = program.programId;
|
|
42
|
+
console.log("SVM-Spoke Program ID:", programId.toString());
|
|
42
43
|
// Parse arguments
|
|
43
44
|
const argv = (0, yargs_1.default)((0, helpers_1.hideBin)(process.argv))
|
|
44
45
|
.option("seed", { type: "string", demandOption: true, describe: "Seed for the state account PDA" })
|
|
@@ -54,7 +55,12 @@ async function enableRoute() {
|
|
|
54
55
|
// Define the state account PDA
|
|
55
56
|
const [statePda, _] = web3_js_1.PublicKey.findProgramAddressSync([Buffer.from("state"), seed.toArrayLike(Buffer, "le", 8)], programId);
|
|
56
57
|
// Define the route account PDA
|
|
57
|
-
const [routePda] = web3_js_1.PublicKey.findProgramAddressSync([
|
|
58
|
+
const [routePda] = web3_js_1.PublicKey.findProgramAddressSync([
|
|
59
|
+
Buffer.from("route"),
|
|
60
|
+
originToken.toBytes(),
|
|
61
|
+
seed.toArrayLike(Buffer, "le", 8),
|
|
62
|
+
chainId.toArrayLike(Buffer, "le", 8),
|
|
63
|
+
], programId);
|
|
58
64
|
// Define the signer (replace with your actual signer)
|
|
59
65
|
const signer = provider.wallet.publicKey;
|
|
60
66
|
console.log("Enabling route...");
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Script: Execute USDC Rebalance to Hub Pool
|
|
3
|
+
*
|
|
4
|
+
* This script executes a previously proposed root bundle on the Hub Pool to rebalance USDC from the Solana Spoke Pool
|
|
5
|
+
* to the Ethereum Hub Pool. It handles CCTP attestations, relayer refund leaf execution, and prepares pending
|
|
6
|
+
* liabilities for bridging back to the Hub Pool.
|
|
7
|
+
*
|
|
8
|
+
* Required Environment Variables:
|
|
9
|
+
* - MNEMONIC: Wallet mnemonic to sign the Ethereum transaction.
|
|
10
|
+
* - HUB_POOL_ADDRESS: Ethereum address of the Hub Pool.
|
|
11
|
+
* - NODE_URL_${CHAIN_ID}: Ethereum RPC URL (must point to the Mainnet or Sepolia depending on Solana cluster).
|
|
12
|
+
*
|
|
13
|
+
* Required Arguments:
|
|
14
|
+
* - `--netSendAmount`: The unscaled amount of USDC to rebalance (e.g., for USDC with 6 decimals, 1 = 0.000001 USDC).
|
|
15
|
+
* - `--resumeRemoteTx`: (Optional) Hash of a previously submitted remote transaction to resume.
|
|
16
|
+
*
|
|
17
|
+
* Example Usage:
|
|
18
|
+
* NODE_URL_11155111=$NODE_URL_11155111 \
|
|
19
|
+
* MNEMONIC=$MNEMONIC \
|
|
20
|
+
* HUB_POOL_ADDRESS=$HUB_POOL_ADDRESS \
|
|
21
|
+
* anchor run executeRebalanceToHubPool \
|
|
22
|
+
* --provider.cluster "devnet" \
|
|
23
|
+
* --provider.wallet $SOLANA_PKEY_PATH \
|
|
24
|
+
* -- --netSendAmount 7
|
|
25
|
+
*
|
|
26
|
+
* Note:
|
|
27
|
+
* - Ensure all required environment variables are properly configured.
|
|
28
|
+
*/
|
|
29
|
+
export {};
|