Tenet Mainnet OFT Quickstart
Welcome! In this guide you'll mint and transfer a lightweight Omnichain Fungible Token (OFT) between Tenet Mainnet and any other supported chain.
Project scaffold
LayerZero's CLI lets you spin up an OFT workspace in seconds:
npx create-lz-oapp@latest # choose → "OFT example"
The wizard creates a repo with Hardhat + Foundry, sample contracts, tests and LayerZero helper scripts.
Add private keys
Rename .env.example
file to .env
and update it with needed configurations:
PRIVATE_KEY = your_private_key; // Required
At a minimum, you need to have the PRIVATE_KEY
. RPC URLs are optional, but strongly recommended. If you don't provide them, public RPCs will be used, but public RPCs can be unreliable or slow, leading to long waiting times for transactions to be confirmed or, at worst, cause your transactions to fail.
Hardhat network config
Update your hardhat.config.ts
file to include the networks you want to deploy your contracts to:
networks: {
// the network you are deploying to or are already on
// Tenet Mainnet (EID=30173)
'tenet-mainnet': {
eid: EndpointId.TENET_V2_MAINNET,
url: process.env.RPC_URL_TENET || 'https://rpc.tenet.org',
accounts,
},
// another network you want to connect to
'optimism-mainnet': {
eid: EndpointId.OPTIMISM_V2_MAINNET,
url: process.env.RPC_URL_OPTIMISM || 'https://mainnet.optimism.io',
accounts,
},
}
LayerZero wiring config
Modify your layerzero.config.ts
file to include the chains and channel security settings you want for each connection:
import {EndpointId} from '@layerzerolabs/lz-definitions';
import type {OmniPointHardhat} from '@layerzerolabs/toolbox-hardhat';
import {OAppEnforcedOption} from '@layerzerolabs/toolbox-hardhat';
import {ExecutorOptionType} from '@layerzerolabs/lz-v2-utilities';
import {TwoWayConfig, generateConnectionsConfig} from '@layerzerolabs/metadata-tools';
const tenetContract: OmniPointHardhat = {
eid: EndpointId.TENET_V2_MAINNET,
contractName: 'MyOFT',
};
const optimismContract: OmniPointHardhat = {
eid: EndpointId.OPTIMISM_V2_MAINNET,
contractName: 'MyOFT',
};
// To connect all the above chains to each other, we need the following pathways:
// Optimism <-> tenet
// tenet <-> Optimism
// For this example's simplicity, we will use the same enforced options values for sending to all chains
// To learn more, read https://docs.layerzero.network/v2/concepts/applications/oapp-standard#execution-options-and-enforced-settings
const EVM_ENFORCED_OPTIONS: OAppEnforcedOption[] = [
{
msgType: 1,
optionType: ExecutorOptionType.LZ_RECEIVE,
gas: 80000,
value: 0,
},
];
const pathways: TwoWayConfig[] = [
[
// 1) Chain B's contract (e.g. Optimism)
optimismContract,
// 2) Chain A's contract (e.g. tenet)
tenetContract,
// 3) Channel security settings:
// • first array = "required" DVN names
// • second array = "optional" DVN names array + threshold
// • third value = threshold (i.e., number of optionalDVNs that must sign)
// [ requiredDVN[], [ optionalDVN[], threshold ] ]
[['LayerZero Labs' /* ← add more DVN names here */], []],
// 4) Block confirmations:
// [confirmations for Optimism → tenet, confirmations for tenet → Optimism]
[1, 1],
// 5) Enforced execution options:
// [options for Optimism → tenet, options for tenet → Optimism]
[EVM_ENFORCED_OPTIONS, EVM_ENFORCED_OPTIONS],
],
];
export default async function () {
// Generate the connections config based on the pathways
const connections = await generateConnectionsConfig(pathways);
return {
contracts: [{contract: optimismContract}, {contract: tenetContract}],
connections,
};
}
It is strongly recommended to review LayerZero's Channel Security Model and understand the impact of each of these configuration settings.
See Next Steps to review the available providers and security settings.