Hardhat Installation & Deploy Liquidity Provision Contract that integrates with X-Talk Swap
Liquidity Provision Contract integrates with X-Talk Swap Contract to provide liquidity for swaps. Liquidity provision contracts are one of the examples of the use case of X-Talk Swaps.
Step 1: Initialize a New Project
Create a New Directory (if you're starting fresh):
mkdir stake-contract cd stake-contract
Initialize a new NPM project:
npm init -y
Step 2: Install Hardhat and Set Up the Project
Install Hardhat:
npm install --save-dev hardhat
Set up the Hardhat project: Run the setup command and choose to create a TypeScript project:
npx hardhat init
When prompted, select to create a TypeScript project. Follow the prompts to add a
.gitignore
and install the project's dependencies.
Step 3: Install Necessary Plugins and Dependencies
Install TypeScript-related dependencies:
npm install --save-dev ts-node typescript @types/node @types/mocha
Install OpenZeppelin Contracts:
npm install @openzeppelin/contracts
Install Ethers and Hardhat Ethers (ensure compatibility):
npm install --save-dev @nomiclabs/hardhat-ethers ethers npm install hardhat-gas-reporter npm install @nomicfoundation/hardhat-toolbox npm install hardhat/config
At this stage, you project structure would look like this.
stake-contract/
├── contracts/
├── scripts/
├── test/
├── hardhat.config.js
└── package.json
Step 4: Write your smart contract
Create the Contract: Create StakeContract.sol inside the contracts directory and paste your contract code there.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
interface ISwapContract {
function transferToStake(string memory _internalId) external;
}
contract StakeV2Contract is Ownable, ReentrancyGuard {
using SafeERC20 for IERC20;
string public DESTINATION_NETWORK = "L1X";
string public NATIVE_ASSET_SYMBOL = "ETH";
string public NATIVE_SYMBOL = "ETH";
address public constant NATIVE_ASSET_ADDRESS = address(0);
uint256 public NATIVE_ASSET_DECIMAL = 18;
address public SWAP_CONTRACT_ADDRESS = address(0);
uint256 public depositFeePercent;
// Withdrawal Penalties
uint256 public withdrawalPenaltyInDays;
uint256 public withdrawalPenaltyInPercentage;
// Mapping to store asset config information
mapping(address => mapping(uint256 => AssetConfiguration)) public assetConfig;
// Mapping to store asset capping
mapping(address => CappingConfiguration) public assetDepositCapConfig;
// Mapping to store user staking information
mapping(string => Stake) userStakesByTxId;
// Store Flagged Withdrawl
mapping(string => FlagWithdrawal) stakeFlagByTxId;
// Authorized caller
mapping(address => bool) public authorizedCaller;
// Treasury contract address
address public treasuryContract;
// Pause Feature
bool public isDepositPaused;
bool public isWithdrawalPaused;
// Structure to store staking details
// Note: Native Asset is represented by address(0)
struct Stake {
uint256 startDate;
uint256 durationInDays;
uint256 amount;
address assetAddress;
address senderAddress;
address rewardAddress;
string assetSymbol;
uint256 assetDecimal;
uint256 apr;
bool isERC20;
bool isRedemed;
uint256 redemedTimestamp;
uint256 penaltyAmount;
uint256 penaltyPercentage;
uint256 depositFeePercent;
}
struct FlagWithdrawal {
bool holdWithdrawl;
string message;
address caller;
}
struct AssetConfiguration {
uint16 apr;
uint16 treasuryPercentage;
}
struct CappingConfiguration {
uint256 maxCap;
uint256 totalDeposited;
}
event StakeDeposited(
string internalId,
address indexed walletAddress,
address indexed assetAddress, // 0x0 in case of Native
address rewardAddress,
string assetSymbol,
uint256 assetDecimal,
uint256 apr,
uint256 amount,
uint256 durationInDays,
uint256 depositFeePercent,
uint256 stakeType, // 0 = Native , 1 - ERC-20
uint256 timestamp
);
event StakeWithdraw(
string internalId,
address indexed walletAddress,
address indexed assetAddress, // 0x0 in case of Native
address rewardAddress,
string assetSymbol,
uint256 assetDecimal,
uint256 apr,
uint256 amount,
uint256 amountTransferred,
uint256 penaltyAmount,
uint256 durationInDays,
uint256 withdrawalPenaltyInPercentage,
uint256 stakeType, // 0 = Native , 1 - ERC-20
uint256 timestamp
);
event AuthorizationUpdated(
address indexed sender,
bool status
);
// XTalk Events
event XTalkMessageBroadcasted(
bytes message,
string destinationNetwork,
string destinationSmartContractAddress
);
event SwapContractAddress ( address swapContract );
modifier onlyAuthorizedCaller() {
require(
msg.sender == owner() || authorizedCaller[msg.sender] == true,
"Not authorized"
);
_;
}
constructor(
string memory _nativeSymbol,
string memory _nativeAssetSymbol,
uint256 _nativeAssetDecimal,
address _treasuryContract,
uint256 _depositFeePercent,
uint256 _withdrawalPenaltyInDays,
uint256 _withdrawalPenaltyInPercentage
) Ownable(msg.sender) {
NATIVE_SYMBOL = _nativeSymbol;
NATIVE_ASSET_SYMBOL = _nativeAssetSymbol;
NATIVE_ASSET_DECIMAL = _nativeAssetDecimal;
treasuryContract = _treasuryContract;
depositFeePercent = _depositFeePercent;
// Withdrawal Penalty configuration
withdrawalPenaltyInDays = _withdrawalPenaltyInDays;
withdrawalPenaltyInPercentage = _withdrawalPenaltyInPercentage;
}
function getStakeDetails(
string memory internalId
) public view returns (Stake memory) {
return userStakesByTxId[internalId];
}
function getStakeWithdrawFlaggedDetails(
string memory internalId
) public view returns (FlagWithdrawal memory) {
return stakeFlagByTxId[internalId];
}
// Owner can configure deposit status
function pauseDeposit(
bool depositStatus
) external onlyAuthorizedCaller {
isDepositPaused = depositStatus;
}
// Owner can configure withdrawal status
function pauseWithdrawal(
bool withdrawalStatus
) external onlyAuthorizedCaller {
isWithdrawalPaused = withdrawalStatus;
}
// Owner can configure withdrawal penalty in days
function setMinimumWithdrawalDays(
uint256 penaltyInDays
) external onlyAuthorizedCaller {
withdrawalPenaltyInDays = penaltyInDays;
}
// Owner can configure withdrawal penalty in percentage
function setWithdrawalPenaltyPercent(
uint256 penaltyInPercentage
) external onlyAuthorizedCaller {
require(
penaltyInPercentage >= 0 && penaltyInPercentage <= 100,
"Percent should be between 0 and 100"
);
withdrawalPenaltyInPercentage = penaltyInPercentage;
}
// Owner can configure staking treasury contract
function setTreasuryAddress(
address treasuryContractAddress
) external onlyAuthorizedCaller {
treasuryContract = treasuryContractAddress;
}
// Owner can configure deposit fee percent
function setDepositFeePercent(
uint256 _depositFeePercent
) external onlyAuthorizedCaller {
require(
_depositFeePercent <= 100,
"Deposit Fee should be less than 100"
);
depositFeePercent = _depositFeePercent;
}
// Update the Gas Price Contract address (only callable by the owner)
function updateSwapContractAddress(
address newAddress
) external onlyAuthorizedCaller {
require(
newAddress != address(0),
"Address cannot be the zero address."
);
SWAP_CONTRACT_ADDRESS = newAddress;
emit SwapContractAddress(newAddress);
}
// Function to transfer asset to treasury
function transferToTreasury(
address tokenAddress,
uint256 amount
) external onlyAuthorizedCaller {
require(treasuryContract != address(0), "Treasury address is not set");
if (tokenAddress == NATIVE_ASSET_ADDRESS) {
// Send Native to Treasury with reentrancy Protection for Native
(bool sent, bytes memory data) = treasuryContract.call{
value: amount
}("");
} else {
// Send Native to Treasury with reentrancy Protection for ERC20
IERC20(tokenAddress).safeTransfer(treasuryContract, amount);
}
}
// Pause and Start withdraw for perticular stake ID
function flagWithdrawRequest(
string memory internalId,
bool holdWithdrawl,
string memory message
) public onlyAuthorizedCaller returns (bool) {
stakeFlagByTxId[internalId].holdWithdrawl = holdWithdrawl;
stakeFlagByTxId[internalId].message = message;
stakeFlagByTxId[internalId].caller = msg.sender;
return true;
}
// Set locking configuration
function setAssetConfiguration(
address assetAddress,
uint256 durationInDays,
uint16 apr,
uint16 treasuryPercentage
) external onlyAuthorizedCaller {
assetConfig[assetAddress][durationInDays] = AssetConfiguration(apr, treasuryPercentage);
}
function bulkAssetConfigurationUpdate(
address[] memory assetAddress,
uint256[] memory maxCap,
uint256[] memory durationInDays,
uint16[][] memory apr,
uint16[][] memory treasuryPercentage
) public onlyAuthorizedCaller returns (bool) {
require(
assetAddress.length == maxCap.length &&
apr.length == treasuryPercentage.length,
"Array lengths must match"
);
for (uint256 _addressIndex = 0; _addressIndex < assetAddress.length; _addressIndex++) {
address _assetAddress = assetAddress[_addressIndex];
assetDepositCapConfig[_assetAddress].maxCap = maxCap[_addressIndex];
for (uint256 _daysIndex = 0; _daysIndex < durationInDays.length; _daysIndex++) {
uint256 _durationInDays = durationInDays[_daysIndex];
uint16 _apr = apr[_addressIndex][_daysIndex];
uint16 _treasuryPercentage = treasuryPercentage[_addressIndex][_daysIndex];
assetConfig[_assetAddress][_durationInDays] = AssetConfiguration(_apr, _treasuryPercentage);
}
}
}
// Set asset cap
function setAssetDepositCapConfig(
address assetAddress,
uint256 maxCap
) external onlyAuthorizedCaller {
assetDepositCapConfig[assetAddress].maxCap = maxCap;
}
// Set asset cap
function resetAssetDepositedAmount(
address assetAddress,
uint256 totalDeposited
) external onlyAuthorizedCaller {
assetDepositCapConfig[assetAddress].totalDeposited = totalDeposited;
}
// Set an authorized caller
function setAuthorizedCaller(
address sender,
bool status
) external onlyOwner {
authorizedCaller[sender] = status;
emit AuthorizationUpdated(sender, status);
}
// User can deposit stake (ERC20)
function depositForERC20(
uint256 durationInDays,
address rewardAddress,
string memory assetSymbol,
address assetAddress,
uint256 amount,
uint256 decimals,
string memory internalId
) external nonReentrant {
require(isDepositPaused == false, "Deposit is paused");
require(userStakesByTxId[internalId].amount == 0, "Stake with given internal ID already exist");
require(amount > 0, "Amount must be greater than zero.");
uint256 totalDeposit = amount + assetDepositCapConfig[assetAddress].totalDeposited;
require(totalDeposit <= assetDepositCapConfig[assetAddress].maxCap, "Max capping reached. Please try with lesser amount");
require(decimals > 0, "Decimals must be greater than 0");
require(rewardAddress != address(0), "Invalid Reward Address");
require(assetAddress != address(0), "Invalid Asset Address");
require(SWAP_CONTRACT_ADDRESS != address(0), "Invalid Swap Address");
require(isEmptyString(assetSymbol) == false, "Invalid Asset Symbol");
// Call the internal transfer function
require(
transferAsset(
assetAddress,
msg.sender,
address(this),
amount,
0
),
"Transfer failed"
);
// Handle Treeasury Share
uint256 swapDistribution = calulatePercentAmount(assetConfig[assetAddress][durationInDays].treasuryPercentage, amount);
if (swapDistribution > 0) {
transferAssetForWithdrawal(
assetAddress,
address(this),
SWAP_CONTRACT_ADDRESS,
swapDistribution
);
}
userStakesByTxId[internalId] = Stake(
getCurrentTimestamp(),
durationInDays,
amount,
assetAddress,
msg.sender,
rewardAddress,
assetSymbol,
decimals,
assetConfig[assetAddress][durationInDays].apr,
true,
false,
0,
0,
0,
depositFeePercent
);
assetDepositCapConfig[assetAddress].totalDeposited = totalDeposit;
bytes memory messageBytes = abi.encode(
internalId,
msg.sender,
assetAddress,
rewardAddress,
NATIVE_SYMBOL,
assetSymbol,
decimals,
assetConfig[assetAddress][durationInDays].apr,
amount,
durationInDays,
depositFeePercent,
1,
getCurrentTimestamp()
);
// Broadcast cross-network message
emit XTalkMessageBroadcasted(messageBytes, DESTINATION_NETWORK, '');
emit StakeDeposited(
internalId,
msg.sender,
assetAddress,
rewardAddress,
assetSymbol,
decimals,
assetConfig[assetAddress][durationInDays].apr,
amount,
durationInDays,
depositFeePercent,
1,
getCurrentTimestamp()
);
}
// User can deposit stake (Native)
function depositForNative(
uint256 durationInDays,
address rewardAddress,
string memory internalId
) external payable nonReentrant {
require(isDepositPaused == false, "Deposit is paused");
require(userStakesByTxId[internalId].amount == 0, "Stake with given internal ID already exist");
require(rewardAddress != address(0), "Invalid Reward Address");
require(SWAP_CONTRACT_ADDRESS != address(0), "Invalid Swap Address");
uint256 amount = msg.value;
require(amount > 0, "Amount must be greater than zero.");
uint256 totalDeposit = amount + assetDepositCapConfig[NATIVE_ASSET_ADDRESS].totalDeposited;
require(totalDeposit <= assetDepositCapConfig[NATIVE_ASSET_ADDRESS].maxCap, "Max capping reached. Please try with lesser amount");
// Handle Treeasury Share
uint256 swapDistribution = calulatePercentAmount(assetConfig[NATIVE_ASSET_ADDRESS][durationInDays].treasuryPercentage, amount);
if (swapDistribution > 0) {
uint256 nativeAmount = msg.value;
// Call the internal transfer function
require(
transferAsset(
NATIVE_ASSET_ADDRESS,
msg.sender,
SWAP_CONTRACT_ADDRESS,
swapDistribution,
nativeAmount
),
"Transfer failed"
);
}
// Store Stake
userStakesByTxId[internalId] = Stake(
getCurrentTimestamp(),
durationInDays,
amount,
NATIVE_ASSET_ADDRESS,
msg.sender,
rewardAddress,
NATIVE_ASSET_SYMBOL,
NATIVE_ASSET_DECIMAL,
assetConfig[NATIVE_ASSET_ADDRESS][durationInDays].apr,
false,
false,
0,
0,
0,
depositFeePercent
);
assetDepositCapConfig[NATIVE_ASSET_ADDRESS].totalDeposited = totalDeposit;
bytes memory messageBytes = abi.encode(
internalId,
msg.sender,
NATIVE_ASSET_ADDRESS,
rewardAddress,
NATIVE_SYMBOL,
NATIVE_ASSET_SYMBOL,
NATIVE_ASSET_DECIMAL,
assetConfig[NATIVE_ASSET_ADDRESS][durationInDays].apr,
amount,
durationInDays,
depositFeePercent,
0,
getCurrentTimestamp()
);
// Broadcast cross-network message
emit XTalkMessageBroadcasted(messageBytes, DESTINATION_NETWORK, '');
emit StakeDeposited(
internalId,
msg.sender,
NATIVE_ASSET_ADDRESS,
rewardAddress,
NATIVE_ASSET_SYMBOL,
NATIVE_ASSET_DECIMAL,
assetConfig[NATIVE_ASSET_ADDRESS][durationInDays].apr,
amount,
durationInDays,
depositFeePercent,
0,
getCurrentTimestamp()
);
}
function checkWithdrawStakeById(string memory internalId) public view returns(bool) {
require(
isStakeUnlocked(internalId) == true,
"Given Stake is not available for withdrawal yet"
);
// Retrieve Stake
Stake memory stake = userStakesByTxId[internalId];
return stake.isRedemed;
}
function withdrawStakeById(string memory internalId) external nonReentrant {
require(isWithdrawalPaused == false, "Withdrawal is paused");
require(
stakeFlagByTxId[internalId].holdWithdrawl == false,
"This internalId is flagged please contact admin"
);
require(
isStakeUnlocked(internalId) == true,
"Given Stake is not available for withdrawal yet"
);
// Retrieve Stake
Stake memory stake = userStakesByTxId[internalId];
// Check if already redeemed
require(stake.isRedemed == false, "Stake is already withdrawn");
(
address _tokenAddress,
uint256 _amount,
uint256 amountAfterPenalty,
uint256 penaltyAmount,
uint256 depositFee,
uint256 amountToTransfer
) = validateRequiredAmount(internalId);
if (_amount > 0) {
require(SWAP_CONTRACT_ADDRESS!= address(0), "Invalid swap address");
ISwapContract(SWAP_CONTRACT_ADDRESS).transferToStake(internalId);
}
// Update Stake Status - set isRedemed to true
userStakesByTxId[internalId].isRedemed = true;
userStakesByTxId[internalId].penaltyAmount = penaltyAmount;
userStakesByTxId[internalId].redemedTimestamp = getCurrentTimestamp();
if(penaltyAmount > 0){
userStakesByTxId[internalId].penaltyPercentage = withdrawalPenaltyInPercentage;
}
// Transfer unstaked funds to contract
if(amountToTransfer > 0){
transferAssetForWithdrawal(stake.assetAddress,address(this),stake.senderAddress,amountToTransfer);
}
bytes memory messageBytes = abi.encode(
internalId,
stake.senderAddress,
stake.assetAddress, // 0x0 in case of Native
stake.rewardAddress,
NATIVE_SYMBOL,
stake.assetSymbol,
stake.assetDecimal,
stake.apr,
stake.amount,
amountToTransfer,
penaltyAmount,
stake.durationInDays,
withdrawalPenaltyInPercentage,
stake.assetAddress == NATIVE_ASSET_ADDRESS ? 0 : 1, // 0 = Native , 1 - ERC-20 ,
getCurrentTimestamp()
);
emit XTalkMessageBroadcasted(messageBytes, DESTINATION_NETWORK, '');
emit StakeWithdraw(
internalId,
stake.senderAddress,
stake.assetAddress, // 0x0 in case of Native
stake.rewardAddress,
stake.assetSymbol,
stake.assetDecimal,
stake.apr,
stake.amount,
amountToTransfer,
penaltyAmount,
stake.durationInDays,
withdrawalPenaltyInPercentage,
stake.assetAddress == NATIVE_ASSET_ADDRESS ? 0 : 1, // 0 = Native , 1 - ERC-20 ,
getCurrentTimestamp()
);
}
function validateRequiredAmount(string memory internalId) public view returns (
address,
uint256,
uint256,
uint256,
uint256,
uint256
) {
address _tokenAddress = userStakesByTxId[internalId].assetAddress;
uint256 _stakeAmount = userStakesByTxId[internalId].amount;
uint256 _depositFeePercent = userStakesByTxId[internalId].depositFeePercent;
bool _isRedeemed = userStakesByTxId[internalId].isRedemed;
require(_isRedeemed == false, "Stake is already withdrawn");
(
uint256 amountAfterPenalty,
uint256 penaltyAmount
) = calculateAmountLeftAfterPenalty(internalId);
uint256 _depositFee = calulatePercentAmount(_depositFeePercent, _stakeAmount);
uint256 _requiredAmount = amountAfterPenalty - _depositFee;
require(_requiredAmount > 0, "Invalid internal id");
uint256 _balance;
if (_tokenAddress == NATIVE_ASSET_ADDRESS) {
// Check native balance (ETH)
_balance = address(this).balance;
} else {
// Check ERC20 token balance
IERC20 token = IERC20(_tokenAddress);
_balance = token.balanceOf(address(this));
}
uint256 _requiredSwapBalance;
if (_balance >= _requiredAmount) {
_requiredSwapBalance = 0;
} else {
uint256 swapContractBalance;
if (_tokenAddress == NATIVE_ASSET_ADDRESS) {
// Check native balance (ETH) of SWAP_CONTRACT_ADDRESS
swapContractBalance = SWAP_CONTRACT_ADDRESS.balance;
} else {
// Check ERC20 token balance of SWAP_CONTRACT_ADDRESS
IERC20 token = IERC20(_tokenAddress);
swapContractBalance = token.balanceOf(SWAP_CONTRACT_ADDRESS);
}
_requiredSwapBalance = _requiredAmount - _balance;
require(swapContractBalance >= _requiredSwapBalance, "Insufficient liquidity.");
}
return (
_tokenAddress,
_requiredSwapBalance,
amountAfterPenalty,
penaltyAmount,
_depositFee,
_requiredAmount
);
}
// User can calculate amount after subtracting penalty
function calculateAmountLeftAfterPenalty(
string memory internalId
) public view returns (uint256, uint256) {
Stake memory stake = userStakesByTxId[internalId];
require(stake.startDate > 0, "Invalid Start Date");
require(stake.isRedemed == false, "Already Unstaked the amount");
uint256 penaltyAmount = 0;
uint256 elapsedTime = getCurrentTimestamp() - stake.startDate;
if (stake.durationInDays == 0 && elapsedTime < withdrawalPenaltyInDays * (1 days)) {
penaltyAmount = calulatePercentAmount(
withdrawalPenaltyInPercentage,
stake.amount
);
}
uint256 amountLeft = (stake.amount) - penaltyAmount;
return (amountLeft, penaltyAmount);
}
// User can check if stake is unlocked
function isStakeUnlocked(
string memory internalId
) public view returns (bool) {
Stake memory stake = userStakesByTxId[internalId];
if (stake.startDate == 0) {
return false;
}
uint256 elapsedTime = getCurrentTimestamp() - stake.startDate;
if (elapsedTime >= stake.durationInDays * (1 days)) {
return true;
}
return false;
}
// Internal Function to transfer asset ERC20 or Native
function transferAssetForWithdrawal(
address assetAddress,
address from,
address to,
uint256 amount
) internal returns (bool) {
if (assetAddress == NATIVE_ASSET_ADDRESS) {
require(from.balance >= amount, "Insufficient Native balance");
// Send Native to Treasury with reentrancy Protection for Native
(bool sent, ) = to.call{value: amount}("");
return sent;
} else {
require(IERC20(assetAddress).balanceOf(from) >= amount, "Insufficient ERC20 balance");
// Send Native to Treasury with reentrancy Protection for ERC20
IERC20(assetAddress).safeTransfer(to, amount);
return true;
}
}
// Internal function to transfer asset ERC20 or Native
function transferAsset(
address assetAddress,
address from,
address to,
uint256 amount,
uint256 nativeAmount
) internal returns (bool) {
if (assetAddress == NATIVE_ASSET_ADDRESS) {
// Ensure the correct amount of native asset is sent
require(nativeAmount >= amount, "Incorrect amount of native asset sent");
// Send native asset to the recipient
(bool sent, ) = to.call{value: nativeAmount}("");
require(sent, "Failed to send native asset");
return true;
} else {
// ERC20 asset transfer
IERC20 token = IERC20(assetAddress);
require(token.balanceOf(from) >= amount, "Insufficient ERC20 balance");
require(token.allowance(from, address(this)) >= amount, "Allowance is not enough");
// Use SafeERC20 to handle transfer
token.safeTransferFrom(from, to, amount);
return true;
}
}
// Internal function to any percent amount
function calulatePercentAmount(
uint256 percent,
uint256 amount
) internal pure returns (uint256) {
if (percent > 0) {
return (amount* (percent))/ 100;
}
return 0;
}
// Internal Function for String Compare
function isEmptyString(
string memory refString
) internal pure returns (bool) {
return bytes(refString).length == 0;
}
function getCurrentTimestamp() internal view returns (uint256) {
return block.timestamp;
}
fallback() external{}
receive() external payable {}
}
Step 5: General and Network configuration
Create a config folder inside your project and create two configurations files viz. general.json and networkWise.json as shown below.
//config/general.json
{
"L1XCrossChainSwap":{
"TREASURY_ADDRESS":"YOUR_TREASURY_ADDRESS",
"TREASURY_SHARE_PERCENT": 0,
"SOURCE_NATIVE_FEE": 0
}
}
List of predefined evm-compatible blockchain networks are listed in networkWise.json. You can get the list of authentic XTALK_GATEWAY_CONTRACT_ADDRESS from the table.
// config/networkWise.json
{
"LOCALHOST": {
"TREASURY_CONTRACT_ADDRESS": "YOUR_TREASURY_CONTRACT_ADDRESS",
"L1X_STANDARD_CROSS_CHAIN_SWAP_ADDRESS": "YOUR_CONTRACT_ADDRESS",
"XTALK_GATEWAY_CONTRACT_ADDRESS" : null,
"TOKEN_CONTRACT_ADDRESS": {
"USDT": "YOUR_USDT_CONTRACT_ADDRESS",
"USDC": "YOUR_USDC_CONTRACT_ADDRESS"
}
},
"HARDHAT": {
"TREASURY_CONTRACT_ADDRESS": null,
"L1X_STANDARD_CROSS_CHAIN_SWAP_ADDRESS": null,
"XTALK_GATEWAY_CONTRACT_ADDRESS" : null,
"TOKEN_CONTRACT_ADDRESS": {
"USDT": null,
"USDC": null
}
},
"ETH": {
"TREASURY_CONTRACT_ADDRESS": null,
"L1X_STANDARD_CROSS_CHAIN_SWAP_ADDRESS": null,
"XTALK_GATEWAY_CONTRACT_ADDRESS" : null,
"TOKEN_CONTRACT_ADDRESS": {
"USDT": null,
"USDC": null
}
},
"AVAX": {
"TREASURY_CONTRACT_ADDRESS": null,
"L1X_STANDARD_CROSS_CHAIN_SWAP_ADDRESS": null,
"XTALK_GATEWAY_CONTRACT_ADDRESS" : null,
"TOKEN_CONTRACT_ADDRESS": {
"USDT": null,
"USDC": null
}
},
"BSC": {
"TREASURY_CONTRACT_ADDRESS": null,
"L1X_STANDARD_CROSS_CHAIN_SWAP_ADDRESS": "YOUR_CONTRACT_ADDRESS",
"XTALK_GATEWAY_CONTRACT_ADDRESS" : "XTALK_BSC_GATEWAY_CONTRACT_ADDRESS",
"TOKEN_CONTRACT_ADDRESS": {
"USDT": null,
"USDC": null
}
},
"OPTIMISM": {
"TREASURY_CONTRACT_ADDRESS": null,
"L1X_STANDARD_CROSS_CHAIN_SWAP_ADDRESS": null,
"XTALK_GATEWAY_CONTRACT_ADDRESS" : null,
"TOKEN_CONTRACT_ADDRESS": {
"USDT": null,
"USDC": null
}
},
"ARBITRUM": {
"TREASURY_CONTRACT_ADDRESS": null,
"L1X_STANDARD_CROSS_CHAIN_SWAP_ADDRESS": null,
"XTALK_GATEWAY_CONTRACT_ADDRESS" : null,
"TOKEN_CONTRACT_ADDRESS": {
"USDT": null,
"USDC": null
}
},
"MATIC": {
"TREASURY_CONTRACT_ADDRESS": null,
"L1X_STANDARD_CROSS_CHAIN_SWAP_ADDRESS": null,
"XTALK_GATEWAY_CONTRACT_ADDRESS" : null,
"TOKEN_CONTRACT_ADDRESS": {
"USDT": null,
"USDC": null
}
}
}
Step 6: Compile and Deploy Your Contracts
Compile your project:
npx hardhat compile
Write deployment scripts or tests as needed, using the setup you've created. Example Provided below.
// scripts/deploy.js
const fs = require("fs/promises");
const hre = require("hardhat");
const path = require('path');
const currentDirectory = process.cwd();
const relativePath = "../config/networkWise.json";
const absolutePath = path.resolve(currentDirectory, relativePath);
const NetworkConfig = require("../config/networkWise.json");
const GeneralConfig = require("../config/general.json");
async function main() {
const [owner] = await ethers.getSigners();
// Check Allowed Network
const currentNetwork = network.name.toUpperCase();
const arrNetwork = Object.keys(NetworkConfig);
if(arrNetwork.includes(currentNetwork) == false){
console.log("Please use network out of : ",arrNetwork.join(", "));
return false;
}
console.log("Deployer Account: ",owner.address);
if(NetworkConfig[currentNetwork]['XTALK_GATEWAY_CONTRACT_ADDRESS'] == "" || NetworkConfig[currentNetwork]['XTALK_GATEWAY_CONTRACT_ADDRESS'] == null){
console.log("XTALK_GATEWAY_CONTRACT_ADDRESS not deployed.")
return false;
}
const L1XStandardCrossChainSwapContract = await ethers.deployContract("L1XStandardCrossChainSwap", [
NetworkConfig[currentNetwork]['XTALK_GATEWAY_CONTRACT_ADDRESS'],
currentNetwork,
GeneralConfig.L1XCrossChainSwap.TREASURY_ADDRESS,
GeneralConfig.L1XCrossChainSwap.TREASURY_SHARE_PERCENT,
GeneralConfig.L1XCrossChainSwap.SOURCE_NATIVE_FEE
]);
NetworkConfig[currentNetwork]['L1X_STANDARD_CROSS_CHAIN_SWAP_ADDRESS'] = await L1XStandardCrossChainSwapContract.getAddress();
console.log("Deployed L1XStandardCrossChainSwap: ",NetworkConfig[currentNetwork]['L1X_STANDARD_CROSS_CHAIN_SWAP_ADDRESS']);
}
// We recommend this pattern to be able to use async/await everywhere
// and properly handle errors.
main().catch((error) => {
console.error(error);
process.exitCode = 1;
});
At this stage, your project structure looks like this:
stake-contract/
├── config/
│ └── general.json
│ └── networkWise.json
├── contracts/
│ └── StakeContract.sol
├── scripts/
│ └── deploy.js
├── test/
├── hardhat.config.js
└── package.json
Sample Hardhat Config JS File: Configure your Hardhat project by editing hardhat.config.js. Add your network in this file with relevant details. Ensure it looks like this:
//hardhat.config.js
require("@nomicfoundation/hardhat-toolbox");
const INFURA_API_KEY = "YOUR_PRIVATE_KEY";
const PRIVATE_KEY = "YOUR_PRIVATE_KEY";
/** @type import('hardhat/config').HardhatUserConfig */
module.exports = {
solidity: {
version: "0.8.20",
settings: {
optimizer: {
enabled: true,
runs: 5000,
},
viaIR: true,
},
},
networks: {
goerli: {
url: `https://goerli.infura.io/v3/${INFURA_API_KEY}`,
accounts: [PRIVATE_KEY],
},
sepolia: {
url: `https://sepolia.infura.io/v3/${INFURA_API_KEY}`,
accounts: [PRIVATE_KEY],
},
bscTestnet: {
url: "https://data-seed-prebsc-1-s1.binance.org:8545/",
accounts: [PRIVATE_KEY],
},
optimisticTestnet: {
url: `https://optimism-goerli.infura.io/v3/${INFURA_API_KEY}`,
accounts: [PRIVATE_KEY],
},
devnet: {
url: "http://localhost:8545",
accounts: [PRIVATE_KEY],
},
avax: {
// url: "https://responsive-cosmological-arrow.avalanche-mainnet.quiknode.pro/b5445a7f5dd8ca1e8d9fd20a9a53dba8ecbb9e14/ext/bc/C/rpc",
url: "https://api.avax.network/ext/bc/C/rpc",
accounts: [process.env.DEPLOYER_PRIVATE_KEY || ""]
},
eth: {
url: "https://floral-crimson-valley.quiknode.pro/3e31dca22be305511d899d3cf7fc9b370f0e0fa1",
// url: "https://eth.llamarpc.com",
accounts: [process.env.DEPLOYER_PRIVATE_KEY || ""]
},
bsc: {
// url: "https://silent-skilled-log.bsc.quiknode.pro/228836424b4f96da10efda362e8e93e240a9ab76",
url: "https://bsc-dataseed1.ninicoin.io",
// url: "https://bsc-mainnet.public.blastapi.io",
// url: "https://binance.llamarpc.com",
accounts: [process.env.DEPLOYER_PRIVATE_KEY || ""]
},
optimism: {
url: "https://optimism.publicnode.com",
accounts: [process.env.DEPLOYER_PRIVATE_KEY || ""]
},
arbitrum: {
url: "https://winter-responsive-film.arbitrum-mainnet.quiknode.pro/9b0931ce258b9f1cdffa279f4a6e7336af7a3f9e",
accounts: [process.env.DEPLOYER_PRIVATE_KEY || ""]
},
matic: {
url: "https://fabled-dry-sanctuary.matic.quiknode.pro/b76be513e5e558c0778327696462b99ae565069a",
// url: "https://polygon-rpc.com",
accounts: [process.env.DEPLOYER_PRIVATE_KEY || ""]
}
},
etherscan: {
apiKey: {
bscTestnet: "X6K857FHFMS7CWHPG2X2AD3I2KBCHQP3ME",
goerli: "CNQMU2ZM1T1CBI1IY79A1EKJFIBMU8JB8M",
optimisticGoerli: "C4YIC217NXH3EYNV1U4TNBBKWYIE1Q1E8D",
},
// url: "https://api-testnet.bscscan.com/",
url: "https://api-goerli.etherscan.io/",
// url: "https://api-goerli-optimistic.etherscan.io/"
}
};
-- Check Endpoint for L1X TestNet Faucet Before Deployment
Deployment Bash
npx hardhat run --network SOURCE_NETWORK ./scripts/deploy.js
Last updated