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
2,912 changes: 2,912 additions & 0 deletions FrameFlatten.sol

Large diffs are not rendered by default.

2,976 changes: 2,976 additions & 0 deletions SepoliaFlatten.sol

Large diffs are not rendered by default.

63 changes: 63 additions & 0 deletions contracts/FrameLayerZeroNFT_Frame.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
//SPDX-License-Identifier: MIT
pragma solidity ^0.8.22;

import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "./interfaces/ILayerZeroEndpoint.sol";
import "./interfaces/ILayerZeroReceiver.sol";
import "./NonblockingLzApp.sol";

error TokenDoesNotExist();

contract FrameLayerZeroNFT_Frame is Ownable, ERC721, NonblockingLzApp {
event ReceivedNFT(uint16 _srcChainId, address _from, uint256 _tokenId);

// user has bridged over successfully
mapping(address => bool) public bridged;

// tokenID exists
mapping(uint256 => bool) public exists;

string private _tokenURI;

constructor(
address _endpoint,
string memory _uri
)
ERC721("Frame LayerZero Testnet NFT", "FLZTNFT")
NonblockingLzApp(_endpoint)
Ownable(msg.sender)
{
_tokenURI = _uri;
}

function _nonblockingLzReceive(
uint16 _srcChainId,
bytes memory _srcAddress,
uint64 /*_nonce*/,
bytes memory _payload
) internal override {
address from;
assembly {
from := mload(add(_srcAddress, 20))
}
(address toAddress, uint256 tokenId) = abi.decode(
_payload,
(address, uint256)
);

bridged[toAddress] = true;
exists[tokenId] = true;

_mint(toAddress, tokenId);

emit ReceivedNFT(_srcChainId, from, tokenId);
}

function tokenURI(
uint256 tokenId
) public view override returns (string memory) {
if (!exists[tokenId]) revert TokenDoesNotExist();
return _tokenURI;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,48 +9,59 @@ import "./NonblockingLzApp.sol";

error NotTokenOwner();
error InsufficientGas();
error SupplyExceeded();
error AlreadyMinted();
error TokenDoesNotExist();
error OneWayBridge();

contract FrameLayerZeroNFT_Sepolia is Ownable, ERC721, NonblockingLzApp {
enum STATUS {
MINTED,
BRIDGED,
NEITHER
}

// token exists
mapping(uint256 => bool) public exists;

// user has minted
mapping(address => bool) public minted;

// user has initiated bridge
mapping(address => bool) public bridged;

contract CrossChainNFT is Ownable, ERC721, NonblockingLzApp {
uint256 public counter;
uint256 public currentTokenId;
uint256 public immutable MAX_ID;

event ReceivedNFT(
uint16 _srcChainId,
address _from,
uint256 _tokenId,
uint256 counter
);
string private _tokenURI;

uint16 public constant FRAME_CHAIN_ID = 10222;

constructor(
address _endpoint,
uint256 _startTokenId
string memory _uri
)
ERC721("Frame CrossChain NFT", "FCCNFT")
ERC721("Frame LayerZero Testnet NFT", "FLZTNFT")
NonblockingLzApp(_endpoint)
Ownable(msg.sender)
{
currentTokenId = _startTokenId;
MAX_ID = currentTokenId + 99999;
_tokenURI = _uri;
}

function mint() external {
if (currentTokenId == MAX_ID) revert SupplyExceeded();
function mint() external returns (uint256) {
if (minted[msg.sender]) revert AlreadyMinted();

exists[currentTokenId] = true;
minted[msg.sender] = true;

_mint(msg.sender, currentTokenId);
unchecked {
++currentTokenId;
++counter;
}

// will return then increment
return currentTokenId++;
}

function crossChain(uint16 dstChainId, uint256 tokenId) public payable {
function bridgeToFrame(uint256 tokenId) public payable {
if (msg.sender != ownerOf(tokenId)) revert NotTokenOwner();

// Remove NFT on current chain
unchecked {
--counter;
}
exists[tokenId] = false;
_burn(tokenId);

bytes memory payload = abi.encode(msg.sender, tokenId);
Expand All @@ -59,16 +70,19 @@ contract CrossChainNFT is Ownable, ERC721, NonblockingLzApp {
bytes memory adapterParams = abi.encodePacked(version, gasForLzReceive);

(uint256 messageFee, ) = lzEndpoint.estimateFees(
dstChainId,
FRAME_CHAIN_ID,
address(this),
payload,
false,
adapterParams
);
if (msg.value <= messageFee) revert InsufficientGas();

// bridged
bridged[msg.sender] = true;

_lzSend(
dstChainId,
FRAME_CHAIN_ID,
payload,
payable(msg.sender),
address(0x0),
Expand All @@ -83,36 +97,39 @@ contract CrossChainNFT is Ownable, ERC721, NonblockingLzApp {
uint64 /*_nonce*/,
bytes memory _payload
) internal override {
address from;
assembly {
from := mload(add(_srcAddress, 20))
}
(address toAddress, uint256 tokenId) = abi.decode(
_payload,
(address, uint256)
);

_mint(toAddress, tokenId);
unchecked {
++counter;
}
emit ReceivedNFT(_srcChainId, from, tokenId, counter);
revert OneWayBridge();
}

// Endpoint.sol estimateFees() returns the fees for the message
function estimateFees(uint16 dstChainId) external view returns (uint256) {
function estimateFees() external view returns (uint256) {
bytes memory payload = abi.encode(msg.sender, 1); // Use arbitrary token id
uint16 version = 1;
uint256 gasForLzReceive = 350000;
bytes memory adapterParams = abi.encodePacked(version, gasForLzReceive);

(uint256 messageFee, ) = lzEndpoint.estimateFees(
dstChainId,
FRAME_CHAIN_ID,
address(this),
payload,
false,
adapterParams
);
return messageFee;
}

function tokenURI(
uint256 tokenId
) public view override returns (string memory) {
if (!exists[tokenId]) revert TokenDoesNotExist();

return _tokenURI;
}

function status(address addr) external view returns (STATUS) {
if (bridged[addr]) return STATUS.BRIDGED;

if (minted[addr]) return STATUS.MINTED;

return STATUS.NEITHER;
}
}
28 changes: 12 additions & 16 deletions scripts/configureFrameTestnet.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,28 +4,24 @@
// You can also run a script with `npx hardhat run <script>`. If you do that, Hardhat
// will compile your contracts, add the Hardhat Runtime Environment's members to the
// global scope, and execute the script.
const { ethers } = require('hardhat');
const deployments = require('./deployments');
const { ethers } = require("hardhat");
const deployments = require("./deployments");

async function main() {
const CrossChainNFTFactory = await ethers.getContractFactory(
'CrossChainNFT'
);
const CrossChainNFT = await CrossChainNFTFactory.attach(
deployments.FRAMETESTNET_ADDRESS
);
const CrossChainNFTFactory = await ethers.getContractFactory("FrameLayerZeroNFT_Frame");
const CrossChainNFT = await CrossChainNFTFactory.attach(deployments.FRAMETESTNET_ADDRESS);

// Set sepolia trusted remote
let trustedRemote = hre.ethers.solidityPacked(
['address', 'address'],
[deployments.SEPOLIA_ADDRESS, deployments.FRAMETESTNET_ADDRESS]
);
await CrossChainNFT.setTrustedRemote(10161, trustedRemote);
// Set sepolia trusted remote
let trustedRemote = hre.ethers.solidityPacked(
["address", "address"],
[deployments.SEPOLIA_ADDRESS, deployments.FRAMETESTNET_ADDRESS]
);
await CrossChainNFT.setTrustedRemote(10161, trustedRemote);
}

// We recommend this pattern to be able to use async/await everywhere
// and properly handle errors.
main().catch((error) => {
console.error(error);
process.exitCode = 1;
console.error(error);
process.exitCode = 1;
});
28 changes: 12 additions & 16 deletions scripts/configureSepolia.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,28 +4,24 @@
// You can also run a script with `npx hardhat run <script>`. If you do that, Hardhat
// will compile your contracts, add the Hardhat Runtime Environment's members to the
// global scope, and execute the script.
const { ethers } = require('hardhat');
const deployments = require('./deployments');
const { ethers } = require("hardhat");
const deployments = require("./deployments");

async function main() {
const CrossChainNFTFactory = await ethers.getContractFactory(
'CrossChainNFT'
);
const CrossChainNFT = await CrossChainNFTFactory.attach(
deployments.SEPOLIA_ADDRESS
);
const CrossChainNFTFactory = await ethers.getContractFactory("FrameLayerZeroNFT_Sepolia");
const CrossChainNFT = await CrossChainNFTFactory.attach(deployments.SEPOLIA_ADDRESS);

// Set sepolia trusted remote
let trustedRemote = hre.ethers.solidityPacked(
['address', 'address'],
[deployments.FRAMETESTNET_ADDRESS, deployments.SEPOLIA_ADDRESS]
);
await CrossChainNFT.setTrustedRemote(10222, trustedRemote);
// Set sepolia trusted remote
let trustedRemote = hre.ethers.solidityPacked(
["address", "address"],
[deployments.FRAMETESTNET_ADDRESS, deployments.SEPOLIA_ADDRESS]
);
await CrossChainNFT.setTrustedRemote(10222, trustedRemote);
}

// We recommend this pattern to be able to use async/await everywhere
// and properly handle errors.
main().catch((error) => {
console.error(error);
process.exitCode = 1;
console.error(error);
process.exitCode = 1;
});
18 changes: 5 additions & 13 deletions scripts/deployFrameTestnet.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,16 @@
// You can also run a script with `npx hardhat run <script>`. If you do that, Hardhat
// will compile your contracts, add the Hardhat Runtime Environment's members to the
// global scope, and execute the script.
const { ethers } = require('hardhat');
const { ethers } = require("hardhat");

async function main() {
const LZFrameTestnetEndpoint = '0x83c73Da98cf733B03315aFa8758834b36a195b87';
const LZFrameTestnetEndpoint = "0x83c73Da98cf733B03315aFa8758834b36a195b87";
const frameStartId = 100000;
const CrossChainNFTFactory = await ethers.getContractFactory(
'CrossChainNFT'
);
const CrossChainNFT = await CrossChainNFTFactory.deploy(
LZFrameTestnetEndpoint,
frameStartId
);
const CrossChainNFTFactory = await ethers.getContractFactory("FrameLayerZeroNFT_Frame");
const CrossChainNFT = await CrossChainNFTFactory.deploy(LZFrameTestnetEndpoint, "URL");
await CrossChainNFT.waitForDeployment();
const deploymentAddress = await CrossChainNFT.getAddress();
console.log(
'Frame testnet ----- CrossChainNFT deployed to:',
deploymentAddress
);
console.log("Frame testnet ----- CrossChainNFT deployed to:", deploymentAddress);
}

// We recommend this pattern to be able to use async/await everywhere
Expand Down
18 changes: 5 additions & 13 deletions scripts/deploySepolia.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,16 @@
// You can also run a script with `npx hardhat run <script>`. If you do that, Hardhat
// will compile your contracts, add the Hardhat Runtime Environment's members to the
// global scope, and execute the script.
const { ethers } = require('hardhat');
const { ethers } = require("hardhat");

async function main() {
const LZSepoliaEndpoint = '0xae92d5aD7583AD66E49A0c67BAd18F6ba52dDDc1';
const LZSepoliaEndpoint = "0xae92d5aD7583AD66E49A0c67BAd18F6ba52dDDc1";
const sepoliaStartId = 0;
const CrossChainNFTFactory = await ethers.getContractFactory(
'CrossChainNFT'
);
const CrossChainNFT = await CrossChainNFTFactory.deploy(
LZSepoliaEndpoint,
sepoliaStartId
);
const CrossChainNFTFactory = await ethers.getContractFactory("FrameLayerZeroNFT_Sepolia");
const CrossChainNFT = await CrossChainNFTFactory.deploy(LZSepoliaEndpoint, "URL");
await CrossChainNFT.waitForDeployment();
const deploymentAddress = await CrossChainNFT.getAddress();
console.log(
'Sepolia testnet ----- CrossChainNFT deployed to:',
deploymentAddress
);
console.log("Sepolia testnet ----- CrossChainNFT deployed to:", deploymentAddress);
}

// We recommend this pattern to be able to use async/await everywhere
Expand Down
4 changes: 2 additions & 2 deletions scripts/deployments.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
module.exports = {
SEPOLIA_ADDRESS: '0x78a7d656fc81064f16b52fd8940e319e3fe56a4b',
FRAMETESTNET_ADDRESS: '0xa558CF3E0Ca1B9aAA696A48677d359277FeE4c34',
SEPOLIA_ADDRESS: "0x194C45CEeDf11eCad2C2Ab20abEcE490379faE53",
FRAMETESTNET_ADDRESS: "0xf48257323E3Bc4ABfb0234358Dc57BF4E6C03919",
};