diff --git a/packages/hardhat/contracts/LinkedNft.sol b/packages/hardhat/contracts/LinkedNft.sol index 197742a..d444cbc 100644 --- a/packages/hardhat/contracts/LinkedNft.sol +++ b/packages/hardhat/contracts/LinkedNft.sol @@ -151,10 +151,32 @@ contract LinkedNft is ERC721URIStorage, CCIPReceiver { emit X_Bridge(chainSelector, nftId, msg.sender, messageId); } + // Calculate the required fee + function calculateBridgeFee(uint256 nftId, uint64 chainSelector) external view returns(uint256) { + // pre-compute the linked address + address linkedAddr = linkedNfts[chainSelector]; - // mint and burn are done by the registrar + bytes memory data; + if (nftSupportedChains[0] == chainSelector) { + data = abi.encodeWithSignature("bridge(uint256,address)", nftId, msg.sender); + } else { + data = abi.encodeWithSignature("bridge(uint256,address,string)", nftId, msg.sender, tokenURI(nftId)); + } - // linkNfts is called by factory or by the original selector + Client.EVM2AnyMessage memory message = Client.EVM2AnyMessage({ + receiver: abi.encode(linkedAddr), + data: data, + tokenAmounts: new Client.EVMTokenAmount[](0), + extraArgs: "", + feeToken: address(0) + }); + + uint256 fee = IRouterClient(router).getFee( + chainSelector, + message + ); + return fee; + } function _ccipReceive( Client.Any2EVMMessage memory message diff --git a/packages/hardhat/contracts/Registrar.sol b/packages/hardhat/contracts/Registrar.sol index 256fa61..366fad1 100644 --- a/packages/hardhat/contracts/Registrar.sol +++ b/packages/hardhat/contracts/Registrar.sol @@ -140,6 +140,41 @@ contract Registrar is Ownable { } } + // Calculate the linked nft fee + // Along with `calculateLinting` defines the total sum required for `setup` + function calculateCreateLinkedNftFee(address nftAddr, uint64 destSelector) public view returns (uint256) { + WrappedNft wrappedNft = WrappedNft(linkedAddrs[nftAddr]); + // The created nft will be linked to all previous nfts that we have. + (uint64[] memory selectors, address[] memory linkedNftAddrs) = wrappedNft.allNfts(); + + // A function of LinkedFactory that we invoke + bytes memory data = abi.encodeWithSignature("xSetup(address,string,string,uint64[],address[])", + nftAddr, wrappedNft.name(), wrappedNft.symbol(), selectors, linkedNftAddrs); + + Client.EVMExtraArgsV1 memory extra; + extra.gasLimit = 4000000; + extra.strict = false; + + Client.EVM2AnyMessage memory message = Client.EVM2AnyMessage({ + receiver: abi.encode(destNetworks[destSelector].factory), + data: data, + tokenAmounts: new Client.EVMTokenAmount[](0), + extraArgs: Client._argsToBytes(extra), + feeToken: address(0) + }); + + return IRouterClient(router).getFee( + destSelector, + message + ); + } + + // Calculate the linting + // Along with `calculateCreateLinkedNftFee` defines the total sum required for `setup` + function calculateLinting(address nftAddr) public view returns (uint256) { + return WrappedNft(linkedAddrs[nftAddr]).calculateLinting(); + } + /// @dev Invokes the factory in the destination chain to create a linked NFT for `nftAddr`. /// @param nftAddr the original NFT address /// @return the native token that user can retrieve back diff --git a/packages/hardhat/contracts/WrappedNft.sol b/packages/hardhat/contracts/WrappedNft.sol index a0c0fcc..fe7d390 100644 --- a/packages/hardhat/contracts/WrappedNft.sol +++ b/packages/hardhat/contracts/WrappedNft.sol @@ -132,6 +132,41 @@ contract WrappedNft is ERC721URIStorage, IERC721Receiver, CCIPReceiver { return budget; } + function calculateLinting() external view returns(uint256) { + if (nftSupportedChains.length <= 1) { + return 0; + } + // the first selector is this contract. + // the last one added and linted automatically to previous ones. + uint64 lastSelector = nftSupportedChains[nftSupportedChains.length-1]; + address lastNftAddr = linkedNfts[lastSelector]; + + uint256 totalFee = 0; + + for (uint256 i = 1; i < nftSupportedChains.length; i++) { + uint64 destSelector = nftSupportedChains[i]; + + // Linked NFT will accept this method to add nft on a new blockchain. + bytes memory data = abi.encodeWithSignature("setupOne(uint64,address)", lastSelector, lastNftAddr); + + Client.EVM2AnyMessage memory message = Client.EVM2AnyMessage({ + receiver: abi.encode(linkedNfts[destSelector]), + data: data, + tokenAmounts: new Client.EVMTokenAmount[](0), + extraArgs: "", + feeToken: address(0) + }); + + totalFee += IRouterClient(router).getFee( + destSelector, + message + ); + } + + return totalFee; + } + + function allNfts() external view returns(uint64[] memory, address[] memory) { address[] memory linkedNftAddrs = new address[](nftSupportedChains.length); for (uint256 i = 0; i < nftSupportedChains.length; i++) { @@ -187,6 +222,28 @@ contract WrappedNft is ERC721URIStorage, IERC721Receiver, CCIPReceiver { emit X_Bridge(chainSelector, nftId, msg.sender, messageId); } + function calculateBridgeFee(uint256 nftId, uint64 chainSelector) external view returns(uint256) { + string memory uri = originalNft.tokenURI(nftId); + + // pre-compute the linked address + address linkedAddr = linkedNfts[chainSelector]; + + Client.EVM2AnyMessage memory message = Client.EVM2AnyMessage({ + receiver: abi.encode(linkedAddr), + data: abi.encodeWithSignature("bridge(uint256,address,string)", nftId, msg.sender, uri), + tokenAmounts: new Client.EVMTokenAmount[](0), + extraArgs: "", + feeToken: address(0) + }); + + uint256 fee = IRouterClient(router).getFee( + chainSelector, + message + ); + return fee; + } + + function _ccipReceive( Client.Any2EVMMessage memory message ) internal override { diff --git a/packages/hardhat/scripts/bridgeNft.ts b/packages/hardhat/scripts/bridgeNft.ts index 8e7b5e8..24d8ee7 100644 --- a/packages/hardhat/scripts/bridgeNft.ts +++ b/packages/hardhat/scripts/bridgeNft.ts @@ -2,8 +2,9 @@ import { ethers } from "hardhat"; import { supportedNetworkParams } from "./params"; const calculateAddress = async () => { + // 0x5660fc9457358639DAdfD05FEC21070B973d277d const WrappedNft = await ethers.getContractFactory("WrappedNft"); - const wrappedNft = WrappedNft.attach("0xf22ca298eef6f8562a2284d5d2ff2ed8f86f214a"); + const wrappedNft = WrappedNft.attach("0x60335a6510b8a705531616a7f262cbff404112e7"); const nftId: number = 1; const destChainId = "80001"; diff --git a/packages/nextjs/pages/approve.tsx b/packages/nextjs/pages/approve.tsx index 3c53335..8993bff 100644 --- a/packages/nextjs/pages/approve.tsx +++ b/packages/nextjs/pages/approve.tsx @@ -5,7 +5,7 @@ import { useAccount, useNetwork } from "wagmi"; import { readContract, waitForTransaction, writeContract } from "wagmi/actions"; import { MetaHeader } from "~~/components/MetaHeader"; import { Spinner } from "~~/components/assets/Spinner"; -import { TxnNotification, useAutoConnect, useDeployedContractInfo } from "~~/hooks/scaffold-eth"; +import { TxnNotification, useDeployedContractInfo } from "~~/hooks/scaffold-eth"; import WrapperNft from "~~/utils/WrappedNft"; import { getTargetById, notification } from "~~/utils/scaffold-eth"; import { ContractName } from "~~/utils/scaffold-eth/contract"; @@ -13,7 +13,6 @@ import { ContractName } from "~~/utils/scaffold-eth/contract"; const managerNames = ["Registrar", "LinkedFactory"] as Array; const Approve: NextPage = () => { - useAutoConnect(); const { chain } = useNetwork(); const { address, isConnecting, isDisconnected } = useAccount(); @@ -51,20 +50,21 @@ const Approve: NextPage = () => { ); } - async function onClick(e: React.MouseEvent) { - e.preventDefault(); + async function onClick() { let notificationId = notification.loading(); // first checking in the Registrar. If wrapper exists, we say you can set up. const wrapperAddress = await readContract({ address: registrarData?.address as string, abi: registrarData?.abi as Abi, - functionName: "wrappers", // todo change to linkedAddrs + functionName: "linkedAddrs", args: [originalNft], }); notification.remove(notificationId); if (wrapperAddress === "0x0000000000000000000000000000000000000000") { - notification.error(); + notification.error( + , + ); return; } @@ -156,7 +156,7 @@ const Approve: NextPage = () => {