The following is a guide for deploying OFT's and wiring them to Initia.
To download the example run:
LZ_ENABLE_EXPERIMENTAL_INITIA_EXAMPLES=1 npx create-lz-oapp@latest
Set pnpm to the required version:
npm install -g pnpm@8.14.0Deploying to Initia requires the Initia CLI tool (Initiad). We ONLY support version 0.7.3 of Initiad. To install Initiad, follow the official documentation at: https://docs.initia.xyz/build-on-initia/initiad
After installation, generate a new key and add it to the keyring:
initiad keys add <your-key-name> --keyring-backend testFor more information on key management please reference the Initiad docs: https://docs.initia.xyz/build-on-initia/initiad#managing-keys
To list of your imported or generated keys run:
initiad keys list --keyring-backend testCreate a .env file with the following variables:
Important: The INITIA_ACCOUNT_ADDRESS must be in the bech32 format starting with "init" (example: init19ck72hj3vt2ccsw78zwv7mtu4r0rjs9xzf3gc3)
# You only need to set one of these values based on your preference
EVM_MNEMONIC=
EVM_PRIVATE_KEY=
# All Initia values below are required - testnet values can be found in the .env.example file
INITIA_ACCOUNT_ADDRESS=<your-initia-bech32-account-address>
INITIA_PRIVATE_KEY=<your-initia-private-key-hex>
INITIA_KEY_NAME=<your-initia-key-name>
INITIA_REST_URL=<your-initia-rest-url>
INITIA_RPC_URL=<your-initia-rpc-url>
INITIA_CHAIN_ID=<your-desired-initia-chain-id>After creating the .env file, load the environment variables:
source .envBefore running the deploy and wire commands, inside of move.layerzero.config.ts, configure the delegate and owner address to your deployer account address. These can be changed in the future with commands shown later in this README, but for now they should be set to the address you will be running the commands from (deployer account address).
Note: in move.layerzero.config.ts all Initia addresses must be in hex format e.g.: 0x1a2b3c...
To convert your init prefixed bech32 address to hex, run the command:
initiad keys parse <your-bech32-addresss> --output json
move.layerzero.config.ts:
contracts: [
{
contract: your_initia_contract_name,
config: {
delegate: 'your_initia_hex_account_address',
owner: 'your_initia_hex_account_address',
},
},
],Note: By default, minting functionality is not available on the OFT. If you need to mint tokens for testing, please follow the Mint to Account on Move VM OFT instructions at the bottom of this README.
To build the contracts without deploying them, run the following command:
pnpm run lz:sdk:move:build --oapp-config move.layerzero.config.ts --oapp-type oftTo build and deploy the contracts, run the following command:
pnpm run lz:sdk:move:deploy --oapp-config move.layerzero.config.ts --address-name oft --move-deploy-script deploy-move/OFTAdapterInitParams.ts --oapp-type oftnpx hardhat lz:deploySelect only the EVM networks you wish to deploy to (do not select Aptos, Movement, Solana, or Initia).
First modify deploy-move/OFTAdapterInitParams.ts and replace the oftMetadata with your desired values:
const oftMetadata = {
move_vm_fa_address: "<your fungible asset address>",
localDecimals: 6,
};Then run the following command to set the delegate:
pnpm run lz:sdk:move:set-delegate --oapp-config move.layerzero.config.tsThen run the following command to initialize the oft:
pnpm run lz:sdk:move:init-fa-adapter --oapp-config move.layerzero.config.ts --move-deploy-script deploy-move/OFTAdapterInitParams.tsImportant: Follow the LayerZero Project Configuration Guide to properly set up your
move.layerzero.config.tsfile with correct endpoint IDs and network configurations before running wiring commands.
EVM Wiring: Ensure that in move.layerzero.config.ts, all of your evm contracts have the owner and delegate specified.
contracts: [
{
contract: your_contract_name,
config: {
owner: 'YOUR_ACCOUNT_ADDRESS',
delegate: 'YOUR_ACCOUNT_ADDRESS',
},
},
...
]If you are wiring solana to move-vm, create a file in deployments/solana-mainnet/MyOFT.json (solana-testnet if you are using testnet) and add the following field:
{
"address": <oft-store-address-from-solana-deployment-folder>
}pnpm run lz:sdk:evm:wire --oapp-config move.layerzero.config.tsNote: --simulate <true> and --mnemonic-index <value> are optional.
--mnemonic-index <value> is the index of the mnemonic to use for the EVM account. If not specified, EVM_PRIVATE_KEY from .env is used. Otherwise, the mnemonic is used along with the index.
If --only-calldata <true> is specified, only the calldata is generated and not the transaction (this is primarily for multisig wallets).
To wire from Move-VM to EVM:
⚠️ Important Security Consideration: When configuring yourmove.layerzero.config.tsfile, pay careful attention to theconfirmationsparameter. This value determines the number of block confirmations to wait on Aptos before emitting the message from the source chain. The default value of5is for illustration purposes only. For production deployments, it is critical to select an appropriate confirmation value based on your security requirements and risk assessment. Default recommended values can be found at: https://layerzeroscan.com/tools/defaults
pnpm run lz:sdk:move:wire --oapp-config move.layerzero.config.tspnpm run lz:sdk:move:adapter-set-fee --oapp-config move.layerzero.config.ts --fee-bps 1000 --to-eid numberpnpm run lz:sdk:move:adapter-set-rate-limit --oapp-config move.layerzero.config.ts --rate-limit 10000 --window-seconds number --to-eid numberRate limit limits how much is sent netted by the amount that is received. It is set on a per pathway basis. For example if the rate limit from Aptos to EVM is 100 tokens you can send 100 tokens from Aptos to EVM, however if you receive 50 tokens from EVM to Aptos you are then able to send 150 tokens from Aptos to EVM. Window is the number of seconds over which the capacity is restored. If the rate limit is 1000 and window is 10 seconds, then each second you get 100 (1000/10) capacity back. The units of the rate limit are the tokens in local decimals.
pnpm run lz:sdk:move:adapter-unset-rate-limit --oapp-config move.layerzero.config.ts --to-eid number
⚠️ Warning: This will permanently disable the blocklist for the OFT. It is for OFTs that want to demonstrate to their holders that they will never use blocklisting abilities.
pnpm run lz:sdk:move:adapter-permanently-disable-blocklist --oapp-config move.layerzero.config.tsThere are three steps to transferring ownership of your Move OFT:
- Transfer the delegate
- Transfer the OApp owner
- Transfer the object owner
Note: These ownership transfer commands only affect the Move VM (Aptos/Movement) implementation of your OFT. To transfer ownership of EVM implementations, you'll need to use the corresponding EVM ownership transfer commands.
To transfer the delegate, run the following command: First ensure that the delegate you wish to transfer to is specified in the move.layerzero.config.ts file.
contracts: [
{
contract: your_contract_name,
config: {
delegate: 'YOUR_DESIRED_DELEGATE_ACCOUNT_ADDRESS',
},
},
...
]Then run the following command:
pnpm run lz:sdk:move:set-delegate --oapp-config move.layerzero.config.tsTo transfer the OApp owner, run the following command:
pnpm run lz:sdk:move:transfer-oapp-owner --oapp-config move.layerzero.config.ts --new-owner <new-owner-address>To transfer the Move-VM object owner, run the following command:
pnpm run lz:sdk:move:transfer-object-owner --oapp-config move.layerzero.config.ts --new-owner <new-owner-address>Note: The object owner has the upgrade authority for the Object.
⚠️ Warning: This mint command is only for testing and experimentation purposes. Do not use in production. First add this function to ./sources/internal_oft/oft_fa.move in order to expose minting functionality to our move sdk script:
public entry fun mint(
admin: &signer,
recipient: address,
amount: u64,
) acquires OftImpl {
assert_admin(address_of(admin));
primary_fungible_store::mint(&store().mint_ref, recipient, amount);
}Then run the deploy, init, and wire commands again on this new oft. To utilize the mint function, run the following command:
pnpm run lz:sdk:move:mint-to-move-oft --oapp-config move.layerzero.config.ts --amount-ld 1000000000000000000 --to-address <your-move-account-address>Important: The source and destination addresses must be in hex format (0x...), not the bech32 format
pnpm run lz:sdk:move:send-from-move-oft \
--oapp-config move.layerzero.config.ts \
--amount-ld <your-amount-ld> \
--min-amount-ld <your-min-amount-ld> \
--src-address <your-source-account-address> \
--to-address <your-destination-account-address> \
--gas-limit <your-gas-limit> \
--dst-eid <your-dst-eid>Important: The destination address must be in hex format (0x...), not the bech32 format
pnpm run lz:sdk:evm:send-evm \
--oapp-config move.layerzero.config.ts \
--src-eid <your-src-eid> \
--dst-eid <your-dst-eid> \
--to <your-source-account-address> \
--amount <your-amount> \
--min-amount <your-min-amount>Sending Native Token via NativeAdaptor
pnpm run lz:sdk:evm:send-evm \
--oapp-config move.layerzero.config.ts \
--src-eid <your-src-eid> \
--dst-eid <your-dst-eid> \
--to <your-source-account-address> \
--amount <your-amount> \
--min-amount <your-min-amount> \
--native-token trueMulti-sig wallet creation and transaction execution on Initia can be done using the Initia multi-sig builder: https://multisig.testnet.initia.xyz/
For executing transactions on Initia multi-sig you to first generate the transaction parameters using the CLI. This is because some of the OApp methods take parameters that are encoded on the client side by our CLI. In order to obtain the encoded data you can run the desired command in the CLI and choose option 'e' to export the parameters to a json file. When 'e' is selected, the command will not be executed, and instead the transaction data will be written to a json file. From there you can copy the encoded values into the Initia multi-sig front end and execute them with your multi-sig wallet.
Example of console prinout after running a command in the CLI:
Choose an action:
(y)es - execute transactions
(e)xport - save as JSON for multisig execution
(n)o - cancel
pnpm run lz:sdk:helpGo to your OFT object address on the block explorer. Under resources, 0x1, click on 'object'. Verify owner field displays the desired object owner address.