Skip to content

Commit

Permalink
Calculate bridge free and setup fee before submitting
Browse files Browse the repository at this point in the history
  • Loading branch information
ahmetson committed Dec 5, 2023
1 parent b6f7872 commit b553150
Show file tree
Hide file tree
Showing 12 changed files with 1,104 additions and 75 deletions.
26 changes: 24 additions & 2 deletions packages/hardhat/contracts/LinkedNft.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
35 changes: 35 additions & 0 deletions packages/hardhat/contracts/Registrar.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
57 changes: 57 additions & 0 deletions packages/hardhat/contracts/WrappedNft.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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++) {
Expand Down Expand Up @@ -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 {
Expand Down
3 changes: 2 additions & 1 deletion packages/hardhat/scripts/bridgeNft.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down
14 changes: 7 additions & 7 deletions packages/nextjs/pages/approve.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,14 @@ 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";

const managerNames = ["Registrar", "LinkedFactory"] as Array<ContractName>;

const Approve: NextPage = () => {
useAutoConnect();
const { chain } = useNetwork();
const { address, isConnecting, isDisconnected } = useAccount();

Expand Down Expand Up @@ -51,20 +50,21 @@ const Approve: NextPage = () => {
);
}

async function onClick(e: React.MouseEvent<HTMLButtonElement, MouseEvent>) {
e.preventDefault();
async function onClick() {
let notificationId = notification.loading(<TxnNotification message="Checking the Wrapper" />);

// 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(<TxnNotification message={`NFT is not bridged, contact to the NFT creator`} />);
notification.error(
<TxnNotification message={`NFT is not bridged or it's a wrong network, contact to the NFT creator`} />,
);
return;
}

Expand Down Expand Up @@ -156,7 +156,7 @@ const Approve: NextPage = () => {
</label>
<label className="form-control w-full max-w-xs">
<div className="label divider">COMPLETE</div>
<button className="btn btn-primary" onClick={e => onClick(e)}>
<button className="btn btn-primary" onClick={() => onClick()}>
Approve
</button>
<div className="stats">
Expand Down
Loading

0 comments on commit b553150

Please sign in to comment.