Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,40 @@
This repo contains token contracts to be deployed on Etherlink Mainnet by the Etherlink team. This project is a hardhat project built on the [template](https://docs.layerzero.network/v2/developers/evm/create-lz-oapp/start) provided by LayerZero.

The token deployments, including addresses, can be found under `deployments/` and are summarized as follows:

- `WXTZ`: Wrapped XTZ, exchange XTZ for WXTZ 1:1 on Etherlink. This should be deployed as a [classic OFT](#classic-ofts).
- `tzBTC`: Wrapped BTC, the contract owner can influence the supply of the token on Etherlink. This should be deployed as a [custom OApp](#custom-oapps).

## Deployments

### Mainnets

| Network | Chain ID | Contract | Address |
| --- | --- | --- | --- |
| Etherlink | 42793 | `WXTZ` | [`0xc9B53AB2679f573e480d01e0f49e2B5CFB7a3EAb`](https://explorer.etherlink.com/address/0xc9B53AB2679f573e480d01e0f49e2B5CFB7a3EAb) |
| Ethereum | 1 | `WXTZ` | [`0xc9B53AB2679f573e480d01e0f49e2B5CFB7a3EAb`](https://etherscan.io/address/0xc9B53AB2679f573e480d01e0f49e2B5CFB7a3EAb) |
| Arbitrum One | 42161 | `WXTZ` | [`0x7424f00845777A06E21F0bd8873f814A8A814B2D`](https://arbiscan.io/address/0x7424f00845777A06E21F0bd8873f814A8A814B2D) |
| Base | 8453 | `WXTZ` | [`0x91F9cc2649ac70a071602cadE9b0C1A5868af51D`](https://basescan.org/address/0x91F9cc2649ac70a071602cadE9b0C1A5868af51D) |
| BNB Smart Chain | 56 | `WXTZ` | [`0x91F9cc2649ac70a071602cadE9b0C1A5868af51D`](https://bscscan.com/address/0x91F9cc2649ac70a071602cadE9b0C1A5868af51D) |

### Testnets

| Network | Chain ID | Contract | Address |
| --- | --- | --- | --- |
| Etherlink Testnet (Ghostnet) | 128123 | `WXTZ` | [`0x40fAfA2E6b2F0734caDDC0a1c572D3BCeABa5313`](https://testnet-explorer.etherlink.com/address/0x40fAfA2E6b2F0734caDDC0a1c572D3BCeABa5313) |
| Etherlink Testnet (Ghostnet) | 128123 | `tzBTCTokenProxy` | [`0xEa5e33525D44dbc4cD4598202AD99f077c58be15`](https://testnet-explorer.etherlink.com/address/0xEa5e33525D44dbc4cD4598202AD99f077c58be15) |
| Etherlink Testnet (Ghostnet) | 128123 | `tzBTCTokenImplem` | [`0x492e4bD1395080B6F6A517e29392C82FAcecc887`](https://testnet-explorer.etherlink.com/address/0x492e4bD1395080B6F6A517e29392C82FAcecc887) |
| Etherlink Testnet (Ghostnet) | 128123 | `tzBTCAdapter` | [`0x632E0216ed9a71b257DD00B79b056231040aBD6a`](https://testnet-explorer.etherlink.com/address/0x632E0216ed9a71b257DD00B79b056231040aBD6a) |
| Etherlink Shadownet | 127823 | `WXTZ` | [`0xc9B53AB2679f573e480d01e0f49e2B5CFB7a3EAb`](https://shadownet.explorer.etherlink.com/address/0xc9B53AB2679f573e480d01e0f49e2B5CFB7a3EAb) |
| Sepolia | 11155111 | `WXTZ` | [`0x5E51503409Acf2457C5cEf6c1DFb0e3F6330fD65`](https://sepolia.etherscan.io/address/0x5E51503409Acf2457C5cEf6c1DFb0e3F6330fD65) |
| BSC Testnet | 97 | `WXTZ` | [`0x66b48b4b33d075f6c87699ad4e669355d2794f39`](https://testnet.bscscan.com/address/0x66b48b4b33d075f6c87699ad4e669355d2794f39) |
| TezosX Previewnet [*](#tezosx-previewnet-stub) | 128064 | `WXTZ` | [`0xf4a2e3BA5C14f11c23c8c4351093C058D20542f2`](https://blockscout.previewnet.tezosx.nomadic-labs.com/address/0xf4a2e3BA5C14f11c23c8c4351093C058D20542f2) |
| TezosX Previewnet [*](#tezosx-previewnet-stub) | 128064 | `EndpointV2Stub` | [`0xF01b3160F2926420C7416ddA62544969063269ED`](https://blockscout.previewnet.tezosx.nomadic-labs.com/address/0xF01b3160F2926420C7416ddA62544969063269ED) |

#### TezosX Previewnet stub

TezosX Previewnet is an ephemeral chain (≈1–2 month lifespan) and has no LayerZero infrastructure. To deploy the unmodified `WXTZ` contract there, a minimal `EndpointV2Stub` (see [`contracts/mocks/tezosXPreviewnet/`](contracts/mocks/tezosXPreviewnet/)) is deployed first and registered under the `EndpointV2` deployment name so `OAppCore`'s constructor call to `setDelegate` succeeds. Cross-chain `send`/`lzReceive` do not work on previewnet; local `deposit`/`withdraw` and the full ERC20/permit surface do. The stub and its deploy script ([`deploy/tezosXPreviewnet/`](deploy/tezosXPreviewnet/)) are scoped to that network via a `skip` guard, and should be removed when previewnet is decommissioned.

## Module Guide

- `docs/`: Documentation of the tokens
Expand Down Expand Up @@ -51,6 +82,7 @@ npx hardhat lz:deploy
### Custom OApps

If the contract is not a classic OFT, it is recommended to deploy it manually with hardhat-deploy instead as follows:

```
npx hardhat deploy --tags <TAG_OF_DEPLOYMENT> --network <DESIRED_NETWORK>
```
Expand Down
2 changes: 1 addition & 1 deletion chain-config.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
export const testnetChains = [127823, 11155111, 11155420, 97, 80002, 84532, 421614, 128123]
export const testnetChains = [128064, 127823, 11155111, 11155420, 97, 80002, 84532, 421614, 128123]

export const mainnetChains = [1, 10, 56, 137, 8453, 42161, 42793]
22 changes: 22 additions & 0 deletions contracts/mocks/tezosXPreviewnet/EndpointV2Stub.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

/// @title EndpointV2Stub (tezosXPreviewnet ONLY — DO NOT DEPLOY ELSEWHERE)
/// @notice Stand-in for `ILayerZeroEndpointV2` so OFT-based contracts can be
/// deployed on the ephemeral tezosXPreviewnet chain, which has no real
/// LayerZero infrastructure.
/// @dev Implements only `setDelegate(address)` — the single endpoint method
/// invoked from `OAppCore`'s constructor. Cross-chain send/receive,
/// quoting, and config flows are intentionally absent and will not
/// work. Delete this contract and its deployment when previewnet is
/// decommissioned.
contract EndpointV2Stub {
event DelegateSet(address indexed oapp, address indexed delegate);

mapping(address oapp => address delegate) public delegates;

function setDelegate(address _delegate) external {
delegates[msg.sender] = _delegate;
emit DelegateSet(msg.sender, _delegate);
}
}
9 changes: 6 additions & 3 deletions deploy/WXTZ.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import assert from 'assert'
import { error } from 'console'

import { type DeployFunction } from 'hardhat-deploy/types'

import { mainnetChains, testnetChains } from '../chain-config'
import { updateDeploymentFile } from '../scripts/utils'
import { testnetChains, mainnetChains } from '../chain-config'
import { network } from 'hardhat'
import { error } from 'console'

const contractName = 'WXTZ'

Expand Down Expand Up @@ -43,6 +44,8 @@ const deploy: DeployFunction = async (hre) => {
let etherlinkChainId
if (currentNetwork == 127823) {
etherlinkChainId = 127823
} else if (currentNetwork == 128064) {
etherlinkChainId = 128064
} else if (testnetChains.includes(currentNetwork)) {
etherlinkChainId = 128123
} else if (mainnetChains.includes(currentNetwork)) {
Expand Down
34 changes: 34 additions & 0 deletions deploy/tezosXPreviewnet/EndpointV2Stub.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import assert from 'assert'

import { type DeployFunction } from 'hardhat-deploy/types'

const TEZOS_X_PREVIEWNET = 'tezosXPreviewnet'

const deploy: DeployFunction = async (hre) => {
const { getNamedAccounts, deployments } = hre
const { deploy } = deployments
const { deployer } = await getNamedAccounts()

assert(deployer, 'Missing named deployer account')

console.log(`Network: ${hre.network.name}`)
console.log(`Deployer: ${deployer}`)

// Deploys the previewnet-only LayerZero endpoint stub and registers it under
// the name "EndpointV2" so that hre.deployments.get('EndpointV2') resolves
// to the stub on this network. The OApp constructor's only endpoint call
// (setDelegate) is satisfied; cross-chain send/receive will not work.
const result = await deploy('EndpointV2', {
contract: 'EndpointV2Stub',
from: deployer,
args: [],
log: true,
})

console.log(`EndpointV2Stub deployed to ${result.address} (saved as 'EndpointV2')`)
}

deploy.tags = ['EndpointV2Stub']
deploy.skip = async (hre) => hre.network.name !== TEZOS_X_PREVIEWNET

export default deploy
92 changes: 49 additions & 43 deletions deploy/tzBTCToken.ts
Original file line number Diff line number Diff line change
@@ -1,63 +1,69 @@
import assert from 'assert'

import { upgrades } from 'hardhat'
import { type DeployFunction } from 'hardhat-deploy/types'

import { updateDeploymentFile, verifyContract } from '../scripts/utils'

const contractName = 'tzBTCToken'

const deploy: DeployFunction = async (hre) => {
const { getNamedAccounts, ethers, deployments } = hre
const { getNamedAccounts, ethers, deployments } = hre

const { deploy } = deployments
const { deployer } = await getNamedAccounts()
const { deploy } = deployments
const { deployer } = await getNamedAccounts()

assert(deployer, 'Missing named deployer account')
assert(deployer, 'Missing named deployer account')

console.log(`Network: ${hre.network.name}`)
console.log(`Deployer: ${deployer}`)
console.log(`Network: ${hre.network.name}`)
console.log(`Deployer: ${deployer}`)

// This is an external deployment pulled in from @layerzerolabs/lz-evm-sdk-v2
//
// @layerzerolabs/toolbox-hardhat takes care of plugging in the external deployments
// from @layerzerolabs packages based on the configuration in your hardhat config
//
// For this to work correctly, your network config must define an eid property
// set to `EndpointId` as defined in @layerzerolabs/lz-definitions
//
// For example:
//
// networks: {
// fuji: {
// ...
// eid: EndpointId.AVALANCHE_V2_TESTNET
// }
// }
const endpointV2Deployment = await hre.deployments.get('EndpointV2')
// This is an external deployment pulled in from @layerzerolabs/lz-evm-sdk-v2
//
// @layerzerolabs/toolbox-hardhat takes care of plugging in the external deployments
// from @layerzerolabs packages based on the configuration in your hardhat config
//
// For this to work correctly, your network config must define an eid property
// set to `EndpointId` as defined in @layerzerolabs/lz-definitions
//
// For example:
//
// networks: {
// fuji: {
// ...
// eid: EndpointId.AVALANCHE_V2_TESTNET
// }
// }
const endpointV2Deployment = await hre.deployments.get('EndpointV2')

// Deploy the ERC20 Upgradable contract tzBTC
const Token = await hre.ethers.getContractFactory("tzBTCToken");
const tzBTCToken = await upgrades.deployProxy(Token, [deployer], { initializer: 'initialize', kind: 'uups' });
await tzBTCToken.deployed();
const tzBTCTokenLogic = await upgrades.erc1967.getImplementationAddress(tzBTCToken.address);
// Deploy the ERC20 Upgradable contract tzBTC
const Token = await hre.ethers.getContractFactory('tzBTCToken')
const tzBTCToken = await upgrades.deployProxy(Token, [deployer], { initializer: 'initialize', kind: 'uups' })
await tzBTCToken.deployed()
const tzBTCTokenLogic = await upgrades.erc1967.getImplementationAddress(tzBTCToken.address)

// Deploy OFT Adapter
const Adapter = await hre.ethers.getContractFactory("tzBTCAdapter");
const adapter = await Adapter.deploy(tzBTCToken.address, endpointV2Deployment.address, deployer);
await tzBTCToken.deployed();
// Deploy OFT Adapter
const Adapter = await hre.ethers.getContractFactory('tzBTCAdapter')
const adapter = await Adapter.deploy(tzBTCToken.address, endpointV2Deployment.address, deployer)
await tzBTCToken.deployed()

console.log(`Deployed contract: ${contractName} proxy, network: ${hre.network.name}, address: ${tzBTCToken.address}`);
console.log(`Deployed contract: ${contractName} implementation, network: ${hre.network.name}, address: ${tzBTCTokenLogic}`);
console.log(`Deployed contract: ${contractName} adapter, network: ${hre.network.name}, address: ${adapter.address}`);
console.log(
`Deployed contract: ${contractName} proxy, network: ${hre.network.name}, address: ${tzBTCToken.address}`
)
console.log(
`Deployed contract: ${contractName} implementation, network: ${hre.network.name}, address: ${tzBTCTokenLogic}`
)
console.log(`Deployed contract: ${contractName} adapter, network: ${hre.network.name}, address: ${adapter.address}`)

updateDeploymentFile(hre.network.name, {
tzBTCTokenProxy: tzBTCToken.address,
tzBTCTokenImplem: tzBTCTokenLogic,
tzBTCAdapter: adapter.address
});
updateDeploymentFile(hre.network.name, {
tzBTCTokenProxy: tzBTCToken.address,
tzBTCTokenImplem: tzBTCTokenLogic,
tzBTCAdapter: adapter.address,
})

await verifyContract(tzBTCToken.address, []);
await verifyContract(tzBTCTokenLogic, []);
await verifyContract(adapter.address, [tzBTCToken.address, endpointV2Deployment.address, deployer]);
await verifyContract(tzBTCToken.address, [])
await verifyContract(tzBTCTokenLogic, [])
await verifyContract(adapter.address, [tzBTCToken.address, endpointV2Deployment.address, deployer])
}

deploy.tags = [contractName]
Expand Down
3 changes: 3 additions & 0 deletions deployments/tezosXPreviewnet.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"WXTZ": "0xf4a2e3BA5C14f11c23c8c4351093C058D20542f2"
}
1 change: 1 addition & 0 deletions deployments/tezosXPreviewnet/.chainId
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
128064
Loading