-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
28070a5
commit 76acfe0
Showing
4 changed files
with
321 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
import { BigNumber } from 'ethers' | ||
import { hexZeroPad } from 'ethers/lib/utils' | ||
import { task, types } from 'hardhat/config' | ||
import { ActionType, HardhatRuntimeEnvironment } from 'hardhat/types' | ||
|
||
// struct SendParam { | ||
// uint32 dstEid; // Destination endpoint ID. | ||
// bytes32 to; // Recipient address. | ||
// uint256 amountLD; // Amount to send in local decimals. | ||
// uint256 minAmountLD; // Minimum amount to send in local decimals. | ||
// bytes extraOptions; // Additional options supplied by the caller to be used in the LayerZero message. | ||
// bytes composeMsg; // The composed message for the send() operation. | ||
// bytes oftCmd; // The OFT command to be executed, unused in default OFT implementations. | ||
// } | ||
|
||
interface TaskArguments { | ||
dstEid: number | ||
amount: string | ||
to: string | ||
contractName: string | ||
} | ||
|
||
const action: ActionType<TaskArguments> = async ( | ||
{ dstEid, amount, to, contractName }, | ||
hre: HardhatRuntimeEnvironment | ||
) => { | ||
const signer = (await hre.ethers.getSigners())[0] | ||
const Oft = await hre.deployments.get(contractName) | ||
const oft = await hre.ethers.getContractAt(Oft.abi, Oft.address, signer) | ||
|
||
// approve the amount to be sent adapters only | ||
if (hre.network.name.startsWith('ethereum')) { | ||
const erc20Token = await hre.ethers.getContractAt('IERC20', (await oft.functions.token())[0]) | ||
const approvalTxResponse = await erc20Token.approve(oft.address, amount) | ||
const approvalTxReceipt = await approvalTxResponse.wait() | ||
console.log(`approved: ${amount}: ${approvalTxReceipt.transactionHash}`) | ||
console.log('balance: ', (await erc20Token.balanceOf(signer.address)).toString()) | ||
} else { | ||
console.log('balance: ', (await oft.balanceOf(signer.address)).toString()) | ||
} | ||
|
||
console.log('amount: ', amount) | ||
|
||
const amountLD = BigNumber.from(amount) | ||
const sendParam = { | ||
dstEid, | ||
to: hexZeroPad(to, 32), | ||
amountLD: amountLD.toString(), | ||
minAmountLD: amountLD.mul(9_000).div(10_000).toString(), | ||
// calls with 60k gas + the existing enforced options | ||
// extraOptions: '0x0003010011010000000000000000000000000000ea60', | ||
extraOptions: '0x', | ||
composeMsg: '0x', | ||
oftCmd: '0x', | ||
} | ||
const [msgFee] = await oft.functions.quoteSend(sendParam, false) | ||
console.log('msgFee: ', msgFee.nativeFee.toString()) | ||
const txResponse = await oft.functions.send(sendParam, msgFee, to, { | ||
value: msgFee.nativeFee, | ||
// gasLimit: 1000000, | ||
}) | ||
console.log(txResponse) | ||
const txReceipt = await txResponse.wait() | ||
console.log(`send: ${amount} to ${to}: ${txReceipt.transactionHash}`) | ||
} | ||
|
||
task('send', 'Sends a transaction') | ||
.setAction(action) | ||
.addParam('dstEid', 'Destination endpoint ID', undefined, types.int, false) | ||
.addParam('amount', 'Amount to send in wei', undefined, types.string, false) | ||
.addParam('to', 'Recipient address', undefined, types.string, false) | ||
.addParam('contractName', 'Contract name', undefined, types.string, false) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,199 @@ | ||
import assert from 'assert' | ||
|
||
import { fetchDigitalAsset } from '@metaplex-foundation/mpl-token-metadata' | ||
import { | ||
fetchAddressLookupTable, | ||
findAssociatedTokenPda, | ||
mplToolbox, | ||
setComputeUnitLimit, | ||
setComputeUnitPrice, | ||
} from '@metaplex-foundation/mpl-toolbox' | ||
import { | ||
AddressLookupTableInput, | ||
TransactionBuilder, | ||
createSignerFromKeypair, | ||
signerIdentity, | ||
} from '@metaplex-foundation/umi' | ||
import { createUmi } from '@metaplex-foundation/umi-bundle-defaults' | ||
import { | ||
fromWeb3JsInstruction, | ||
fromWeb3JsKeypair, | ||
fromWeb3JsPublicKey, | ||
toWeb3JsPublicKey, | ||
} from '@metaplex-foundation/umi-web3js-adapters' | ||
import { TOKEN_PROGRAM_ID } from '@solana/spl-token' | ||
import { Keypair, PublicKey } from '@solana/web3.js' | ||
import { getExplorerLink, getSimulationComputeUnits } from '@solana-developers/helpers' | ||
import bs58 from 'bs58' | ||
import { hexlify } from 'ethers/lib/utils' | ||
import { task } from 'hardhat/config' | ||
|
||
import { formatEid } from '@layerzerolabs/devtools' | ||
import { types } from '@layerzerolabs/devtools-evm-hardhat' | ||
import { EndpointId } from '@layerzerolabs/lz-definitions' | ||
import { OFT_SEED, OftPDADeriver, OftProgram, OftTools, SendHelper } from '@layerzerolabs/lz-solana-sdk-v2' | ||
import { Options, addressToBytes32 } from '@layerzerolabs/lz-v2-utilities' | ||
|
||
import { createSolanaConnectionFactory } from '../common/utils' | ||
import getFee from '../utils/getFee' | ||
|
||
interface Args { | ||
amount: number | ||
to: string | ||
fromEid: EndpointId | ||
toEid: EndpointId | ||
programId: string | ||
mint: string | ||
} | ||
|
||
const LOOKUP_TABLE_ADDRESS: Partial<Record<EndpointId, PublicKey>> = { | ||
[EndpointId.SOLANA_V2_MAINNET]: new PublicKey('AokBxha6VMLLgf97B5VYHEtqztamWmYERBmmFvjuTzJB'), | ||
[EndpointId.SOLANA_V2_TESTNET]: new PublicKey('9thqPdbR27A1yLWw2spwJLySemiGMXxPnEvfmXVk4KuK'), | ||
} | ||
|
||
// Define a Hardhat task for sending OFT from Solana | ||
task('lz:oft:solana:send', 'Send tokens from Solana to a target EVM chain') | ||
.addParam('amount', 'The amount of tokens to send', undefined, types.int) | ||
.addParam('fromEid', 'The source endpoint ID', undefined, types.eid) | ||
.addParam('to', 'The recipient address on the destination chain') | ||
.addParam('toEid', 'The destination endpoint ID', undefined, types.eid) | ||
.addParam('mint', 'The OFT token mint public key', undefined, types.string) | ||
.addParam('programId', 'The OFT program ID', undefined, types.string) | ||
.setAction(async (taskArgs: Args) => { | ||
const privateKey = process.env.SOLANA_PRIVATE_KEY | ||
assert(!!privateKey, 'SOLANA_PRIVATE_KEY is not defined in the environment variables.') | ||
|
||
const keypair = Keypair.fromSecretKey(bs58.decode(privateKey)) | ||
const umiKeypair = fromWeb3JsKeypair(keypair) | ||
|
||
const lookupTableAddress = LOOKUP_TABLE_ADDRESS[taskArgs.fromEid] | ||
assert(lookupTableAddress != null, `No lookup table found for ${formatEid(taskArgs.fromEid)}`) | ||
|
||
const connectionFactory = createSolanaConnectionFactory() | ||
const connection = await connectionFactory(taskArgs.fromEid) | ||
|
||
// Initialize Solana connection and UMI framework | ||
const umi = createUmi(connection.rpcEndpoint).use(mplToolbox()) | ||
const umiWalletSigner = createSignerFromKeypair(umi, umiKeypair) | ||
umi.use(signerIdentity(umiWalletSigner)) | ||
|
||
// Define OFT program and token mint public keys | ||
const oftProgramId = new PublicKey(taskArgs.programId) | ||
const mintPublicKey = new PublicKey(taskArgs.mint) | ||
const umiMintPublicKey = fromWeb3JsPublicKey(mintPublicKey) | ||
|
||
// Find the associated token account | ||
const tokenAccount = findAssociatedTokenPda(umi, { | ||
mint: umiMintPublicKey, | ||
owner: umiWalletSigner.publicKey, | ||
}) | ||
|
||
// Derive the OFT configuration PDA | ||
const [oftConfigPda] = PublicKey.findProgramAddressSync( | ||
[Buffer.from(OFT_SEED), mintPublicKey.toBuffer()], | ||
oftProgramId | ||
) | ||
|
||
// Fetch token metadata | ||
const mintInfo = (await fetchDigitalAsset(umi, umiMintPublicKey)).mint | ||
const destinationEid: EndpointId = taskArgs.toEid | ||
const amount = taskArgs.amount * 10 ** mintInfo.decimals | ||
|
||
// Derive peer address and fetch peer information | ||
const deriver = new OftPDADeriver(oftProgramId) | ||
const [peerAddress] = deriver.peer(oftConfigPda, destinationEid) | ||
const peerInfo = await OftProgram.accounts.Peer.fromAccountAddress(connection, peerAddress) | ||
|
||
// Set up send helper and convert recipient address to bytes32 | ||
const sendHelper = new SendHelper() | ||
const recipientAddressBytes32 = addressToBytes32(taskArgs.to) | ||
|
||
// Quote the fee for the cross-chain transfer | ||
const feeQuote = await OftTools.quoteWithUln( | ||
connection, | ||
keypair.publicKey, | ||
mintPublicKey, | ||
destinationEid, | ||
BigInt(amount), | ||
(BigInt(amount) * BigInt(9)) / BigInt(10), | ||
Options.newOptions().addExecutorLzReceiveOption(0, 0).toBytes(), | ||
Array.from(recipientAddressBytes32), | ||
false, // payInZRO | ||
undefined, // tokenEscrow | ||
undefined, // composeMsg | ||
peerInfo.address, | ||
await sendHelper.getQuoteAccounts( | ||
connection, | ||
keypair.publicKey, | ||
oftConfigPda, | ||
destinationEid, | ||
hexlify(peerInfo.address) | ||
), | ||
TOKEN_PROGRAM_ID, // SPL Token Program | ||
oftProgramId // OFT Program | ||
) | ||
|
||
console.log(feeQuote) | ||
|
||
// Create the instruction for sending tokens | ||
const sendInstruction = await OftTools.sendWithUln( | ||
connection, | ||
keypair.publicKey, // payer | ||
mintPublicKey, // tokenMint | ||
toWeb3JsPublicKey(tokenAccount[0]), // tokenSource | ||
destinationEid, | ||
BigInt(amount), | ||
(BigInt(amount) * BigInt(9)) / BigInt(10), | ||
Options.newOptions().addExecutorLzReceiveOption(0, 0).toBytes(), | ||
Array.from(recipientAddressBytes32), | ||
feeQuote.nativeFee, | ||
undefined, // payInZRO | ||
undefined, | ||
undefined, | ||
peerInfo.address, | ||
undefined, | ||
TOKEN_PROGRAM_ID, // SPL Token Program | ||
oftProgramId // OFT Program | ||
) | ||
|
||
// Convert the instruction and create the transaction builder | ||
const convertedInstruction = fromWeb3JsInstruction(sendInstruction) | ||
const transactionBuilder = new TransactionBuilder([ | ||
{ | ||
instruction: convertedInstruction, | ||
signers: [umiWalletSigner], | ||
bytesCreatedOnChain: 0, | ||
}, | ||
]) | ||
|
||
// Fetch simulation compute units and set compute unit price | ||
const { averageFeeExcludingZeros } = await getFee() | ||
const priorityFee = Math.round(averageFeeExcludingZeros) | ||
const computeUnitPrice = BigInt(priorityFee) | ||
console.log(`Compute unit price: ${computeUnitPrice}`) | ||
|
||
const addressLookupTableInput: AddressLookupTableInput = await fetchAddressLookupTable( | ||
umi, | ||
fromWeb3JsPublicKey(lookupTableAddress) | ||
) | ||
const { value: lookupTableAccount } = await connection.getAddressLookupTable(new PublicKey(lookupTableAddress)) | ||
const computeUnits = await getSimulationComputeUnits(connection, [sendInstruction], keypair.publicKey, [ | ||
lookupTableAccount!, | ||
]) | ||
|
||
// Build and send the transaction | ||
const transactionSignature = await transactionBuilder | ||
.add(setComputeUnitPrice(umi, { microLamports: computeUnitPrice * BigInt(4) })) | ||
.add(setComputeUnitLimit(umi, { units: computeUnits! * 1.1 })) | ||
.setAddressLookupTables([addressLookupTableInput]) | ||
.sendAndConfirm(umi) | ||
|
||
// Encode the transaction signature and generate explorer links | ||
const transactionSignatureBase58 = bs58.encode(transactionSignature.signature) | ||
const solanaTxLink = getExplorerLink('tx', transactionSignatureBase58.toString(), 'mainnet-beta') | ||
const layerZeroTxLink = `https://layerzeroscan.com/tx/${transactionSignatureBase58}` | ||
|
||
console.log(`✅ Sent ${taskArgs.amount} token(s) to destination EID: ${destinationEid}!`) | ||
console.log(`View Solana transaction here: ${solanaTxLink}`) | ||
console.log(`Track cross-chain transfer here: ${layerZeroTxLink}`) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
import { task, types } from 'hardhat/config' | ||
import { ActionType, HardhatRuntimeEnvironment, TaskArguments } from 'hardhat/types' | ||
|
||
import { MULTISIGS } from '../constants/multisigs' | ||
|
||
const action: ActionType<TaskArguments> = async ({ contractName }, hre: HardhatRuntimeEnvironment) => { | ||
// Needs to be the delegate wallet index | ||
const signer = (await hre.ethers.getSigners())[0] | ||
const Oft = await hre.deployments.get(contractName) | ||
const oft = await hre.ethers.getContractAt(Oft.abi, Oft.address, signer) | ||
const owner = MULTISIGS[hre.network.name] | ||
|
||
if (!owner) { | ||
throw new Error('No owner found for this network') | ||
} | ||
|
||
const txResponse = await oft.functions.setDelegate(owner) | ||
console.log(txResponse) | ||
const txReceipt = await txResponse.wait() | ||
console.log(`tx hash: ${txReceipt.transactionHash}`) | ||
} | ||
|
||
task('transferDelegate', 'Transfers delegate of a contract to another address') | ||
.setAction(action) | ||
.addParam('contractName', 'Contract name', undefined, types.string, false) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
import { task, types } from 'hardhat/config' | ||
import { ActionType, HardhatRuntimeEnvironment, TaskArguments } from 'hardhat/types' | ||
|
||
import { MULTISIGS } from '../constants/multisigs' | ||
|
||
const action: ActionType<TaskArguments> = async ({ contractName }, hre: HardhatRuntimeEnvironment) => { | ||
// Needs to be the delegate wallet index | ||
const signer = (await hre.ethers.getSigners())[0] | ||
const Oft = await hre.deployments.get(contractName) | ||
const oft = await hre.ethers.getContractAt(Oft.abi, Oft.address, signer) | ||
const owner = MULTISIGS[hre.network.name] | ||
|
||
if (!owner) { | ||
throw new Error('No owner found for this network') | ||
} | ||
|
||
const txResponse = await oft.functions.transferOwnership(owner) | ||
console.log(txResponse) | ||
const txReceipt = await txResponse.wait() | ||
console.log(`tx hash: ${txReceipt.transactionHash}`) | ||
} | ||
|
||
task('transferOwnership', 'Transfers ownership of a contract to another address') | ||
.setAction(action) | ||
.addParam('contractName', 'Contract name', undefined, types.string, false) |