LayerZero V2 Solana OFT Program
The Solana OFT, Endpoint, and ULN Programs are currently in Mainnet Beta!
The Omnichain Fungible Token (OFT) Standard allows fungible tokens to be transferred across multiple blockchains without asset wrapping or middlechains. Read more on OFTs in our glossary page: OFT.
While the typical path for Solana program development involves interacting with or deploying executable code that defines your specific implementation, and then minting accounts that want to use that interface (e.g., the SPL Token Program), the OFT Program is different in this respect.
Because every Solana Program has an Upgrade Authority, and this authority can change or modify the implementation of all child accounts, developers wishing to create cross-chain tokens on Solana should deploy their own instance of the OFT Program to create new OFT Store accounts, so that they own their OFT's Upgrade Authority.
End-to-end instruction on how to deploy a Solana OFT can be found in the README at https://github.com/LayerZero-Labs/devtools/tree/main/examples/oft-solana, which will be the README of your project when you setup using the LayerZero CLI.
Requirements
To ensure compatibility and smooth operation, please use the following versions:
- Rust:
v1.75.0
- Solana CLI:
1.17.31
- Anchor:
0.29.0
- Docker
You can find the sample codebase in the LayerZero Developer Tools Repo.
Setup project
Setup your project using the LayerZero CLI.
LZ_ENABLE_SOLANA_OFT_EXAMPLE=1 npx create-lz-oapp@latest
Generate Program Keypairs
Create program keypair files if they do not exist:
solana-keygen new -o target/deploy/endpoint-keypair.json
solana-keygen new -o target/deploy/oft-keypair.json
anchor keys sync
Install Dependencies
pnpm install
Test
Run tests to ensure everything is set up correctly:
pnpm test
Build
anchor build -p oft --verifiable
Building in verifiable mode requires Docker. We recommend you to build in verifiable mode, so that you can carry out program verification. If using Docker is not possible, you can build in regular mode.
Deploy
Navigate to the contract directory to prepare for deployment:
solana program deploy --program-id target/deploy/oft-keypair.json target/verifiable/oft.so -u devnet
To learn more about deploying Solana programs, refer to Deploy a Solana Program with the CLI.
If you encounter issues during compilation and testing, it might be due to the versions of Solana and Anchor. You can switch to Solana version 1.17.31 and Anchor version 0.29.0, as these are the versions we have tested and verified to be working.
(Optional) Verify the OFT Program
To continue, you must first install solana-verify. You can learn about how program verification works in the official Solana program verification guide.
The commands given below assume that you did not make any modifications to the Solana OFT program source code. If you did, you can refer to the instructions in solana-verify directly.
Verification is done via the OtterSec API, which builds the program contained in the repo provided.
If you did not modify the OFT program, you can reference LayerZero's devtools repo, which removes the need for you to host your own public repo for verification purposes. By referencing LayerZero's devtools repo, you also benefit from the LayerZero OFT program's audited status.
Normally, each Anchor program requires its own repository for verification because the program ID provided to declare_id!
is embedded in the bytecode, altering its hash. We solve this by having you supply the program ID as an environment variable during build time. This variable is then read by the program_id_from_env
function in the OFT program's lib.rs
snippet.
Below is the relevant code snippet:
declare_id!(Pubkey::new_from_array(program_id_from_env!(
"OFT_ID",
"9UovNrJD8pQyBLheeHNayuG1wJSEAoxkmM14vw5gcsTT"
)));
The above is used via providing OFT_ID
as an environment variable when running solana-verify
, which is demonstrated in the following sections.
Compare locally
If you wish to, you can view the program hash of the locally built OFT program:
solana-verify get-executable-hash ./target/verifiable/oft.so
Compare with the on-chain program hash:
solana-verify get-program-hash -u devnet <PROGRAM_ID>
Verify against a repository and submit verification data onchain
Run the following command to verify against the repo that contains the program source code:
solana-verify verify-from-repo -ud --program-id <PROGRAM_ID> --mount-path examples/oft-solana https://github.com/LayerZero-Labs/devtools --library-name oft -- --config env.OFT_ID=\'<PROGRAM_ID>\'
The above instruction runs against the Solana Devnet as it uses the -ud
flag. To run it against Solana Mainnet, replace -ud
with -um
.
Upon successful verification, you will be prompted with the following:
Program hash matches ✅
Do you want to upload the program verification to the Solana Blockchain? (y/n)
Respond with y
to proceed with uploading of the program verification data onchain.
(mainnet only) Submit to the OtterSec API
This will provide your program with the Verified
status on explorers. Note that currently the Verified
status only exists on mainnet explorers.
Verify against the code in the git repo and submit for verification status:
solana-verify verify-from-repo --remote -um --program-id <PROGRAM_ID> --mount-path examples/oft-solana https://github.com/LayerZero-Labs/devtools --library-name oft -- --config env.OFT_ID=\'<PROGRAM_ID>\'
Learn more about the solana-verify CLI from the official repo.
Creating OFT Store accounts
After successful program deployment, you can now use the program to create either a vanilla Solana OFT or Solana OFT Adapter.
Troubleshooting
DeclaredProgramIdMismatch
Full error: AnchorError occurred. Error Code: DeclaredProgramIdMismatch. Error Number: 4100. Error Message: The declared program id does not match the actual program id.
Fixing this error requires upgrading the deployed program.
Upgrading your program will require that your keypair has sufficient SOL for the whole program's rent (approximately 3.9 SOL). This is due to how program upgrades in Solana works. Read further for the details. If you have access to additional SOL, we recommend you to continue with these steps. Alternatively, you can close the existing program account (which will return the current program's SOL rent) and deploy from scratch. Note that after closing a program account, you cannot reuse the same program ID, which means you must use a new program keypair.
This error occurs when the program is built with a declare_id!
value that does not match its onchain program ID. The program ID onchain is determined by the original program keypair used when deploying (created by solana-keygen new -o target/deploy/endpoint-keypair.json --force
).
To debug, check the following:
the following section in Anchor.toml
:
[programs.localnet]
oft = "9obQfBnWMhxwYLtmarPWdjJgTc2mAYGRCoWbdvs9Wdm5"
the output of running anchor keys list
:
endpoint: Cfego9Noyr78LWyYjz2rYUiaUR4L2XymJ6su8EpRUviU
oft: 9obQfBnWMhxwYLtmarPWdjJgTc2mAYGRCoWbdvs9Wdm5
Ensure that in both, the oft
values match your OFT program's onchain ID.
If they already do, and you are still encountering DeclaredProgramIdMismatch
, this means that you ran the build command with the wrong program ID, causing the declared program ID onchain to mismatch.
To fix this, you can re-run the build command, ensuring you pass in the OFT_ID
env var:
anchor build -v -e OFT_ID=<OFT_PROGRAM_ID>
Then, re-deploy (upgrade) your program. For this step, your keypair is required to have sufficient SOL at least equivalent to current program's rent.
While the nett difference in SOL will be zero if your program's size did not change, you will still need the same amount of SOL as required by the program's rent due to how Solana program upgrades work, which is as follows:
- the existing program starts off as being unaffected
- the updated program's bytecode is uploaded to a buffer account (new account, hence SOL for rent is required) which acts as a temporary staging area
- the contents of the buffer account are then copied to the program data account
- the buffer account is closed, and its rent SOL is returned
Run the deploy commmand to upgrade the program.
solana program deploy --program-id target/deploy/oft-keypair.json target/deploy/oft.so -u devnet --with-compute-unit-price 300000
To deploy to Solana Mainnet, replace -u devnet
with -u mainnet-beta
.
Retrying Failed Transactions
If a transaction fails, it may be due to network congestion or other temporary issues. You can retry the transaction by resubmitting it. Ensure that you have enough SOL in your account to cover the transaction fees.
Recovering Failed Rent
solana program close --buffer --keypair deployer-keypair.json -u mainnet-beta
For more troubleshooting help, refer to the Solana OFT README.
Known Limitations
Token Extensions
While it is possible to create a Solana OFT using the Token2022 (Token Extensions) there is limited compatibility with token extensions. It is advised for you to conduct an end-to-end test if you require token extensions for your Solana OFT.
Cross Program Invocation into the OFT Program (CPI Depth limitation)
Solana has the max CPI Depth of 4. A Solana OFT send instruction has the following CPI trace:
OFT -> Endpoint -> ULN -> Worker -> Pricefeed
Which is already 4 CPI calls deep, relative to the OFT program.
The above means it's not currently possible to CPI into the OFT program, as it would violate the current Solana CPI Depth limit of 4.
If you require a certain action to be taken in tandem with an OFT.send
call, it would not be possible to have it be done in the same instruction. However, since Solana allows for multiple instructions per transaction, you can instead have it be grouped into the same transaction as the OFT.send
instruction.
For example, if you have a project that involves staking OFTs cross-chain, and when unstaking (let's refer to this instruction as StakingProgram.unstake
), you want to allow for the OFT to be sent (via OFT.send
) to another chain in the same transaction, then you can do the following:
- prepare the
StakingProgram.unstake
instruction - prepare the
OFT.send
instruction - submit both instructions in one transaction
It would not be possible for you to have call OFT.send
inside the StakingProgram
's unstake
instruction directly since this would result in the following CPI trace: StakingProgram -> OFT -> Endpoint -> ULN -> Worker -> Pricefeed
, which has a CPI depth of 5, exceeding the limit of 4.
Building without Docker
Our default instructions ask you to build in verifiable mode:
anchor build -v -e OFT_ID=<OFT_PROGRAM_ID>
Where the -v
flag instructs anchor to build in verifiable mode. We highly recommend you to build in verifiable mode, so that you can carry out program verification.
Verifiable mode requires Docker. If you cannot build using Docker, then the alternative is to build in regular mode, which results in slight differences in commands for two steps: build and deploy.
For building:
OFT_ID=<PROGRAM_ID> anchor build
In verifiable mode, the output defaults to target/verifiable.oft.so
. In regular mode, the output defaults to target/deploy.oft.so
.
For deploying:
solana program deploy --program-id target/deploy/oft-keypair.json target/deploy/oft.so -u devnet --with-compute-unit-price <COMPUTE_UNIT_PRICE_IN_MICRO_LAMPORTS>
All other commands remain the same.