-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #5 from crossnft-labs/feature/pay-for-nft-minting
Feature/pay for nft minting on source chain
- Loading branch information
Showing
1 changed file
with
97 additions
and
35 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,62 +1,124 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity 0.8.19; | ||
|
||
import {LinkTokenInterface} from "@chainlink/contracts/src/v0.8/shared/interfaces/LinkTokenInterface.sol"; | ||
import {OwnerIsCreator} from "@chainlink/contracts-ccip/src/v0.8/shared/access/OwnerIsCreator.sol"; | ||
import {IRouterClient} from "@chainlink/contracts-ccip/src/v0.8/ccip/interfaces/IRouterClient.sol"; | ||
import {Client} from "@chainlink/contracts-ccip/src/v0.8/ccip/libraries/Client.sol"; | ||
import {Withdraw} from "./utils/Withdraw.sol"; | ||
import {IERC20} from "@chainlink/contracts-ccip/src/v0.8/vendor/openzeppelin-solidity/v4.8.0/token/ERC20/IERC20.sol"; | ||
import {LinkTokenInterface} from "@chainlink/contracts/src/v0.8/interfaces/LinkTokenInterface.sol"; | ||
|
||
contract CrossNftSourceMinter is Withdraw { | ||
contract CrossNftSourceMinter is OwnerIsCreator{ | ||
enum PayFeesIn { | ||
Native, | ||
LINK | ||
} | ||
ddress immutable i_router; | ||
address immutable i_router; | ||
address immutable i_link; | ||
|
||
event MessageSent(bytes32 messageId); | ||
mapping(uint64 => bool) public whitelistedChains; | ||
|
||
error NotEnoughBalance(uint256 currentBalance, uint256 calculatedFees); | ||
error DestinationChainNotWhitelisted(uint64 destinationChainSelector); | ||
error NothingToWithdraw(); | ||
|
||
//event MessageSent(bytes32 messageId); | ||
|
||
event TokensTransferred( | ||
bytes32 indexed messageId, // The unique ID of the message. | ||
uint64 indexed destinationChainSelector, // The chain selector of the destination chain. | ||
address receiver, // The address of the receiver on the destination chain. | ||
address token, // The token address that was transferred. | ||
uint256 tokenAmount, // The token amount that was transferred. | ||
uint256 fees // The fees paid for sending the message. | ||
); | ||
|
||
modifier onlyWhitelistedChain(uint64 _destinationChainSelector) { | ||
if (!whitelistedChains[_destinationChainSelector]) | ||
revert DestinationChainNotWhitelisted(_destinationChainSelector); | ||
_; | ||
} | ||
|
||
constructor(address router, address link) { | ||
i_router = router; | ||
i_link = link; | ||
LinkTokenInterface(i_link).approve(i_router, type(uint256).max); | ||
} | ||
|
||
receive() external payable {} | ||
function whitelistChain( | ||
uint64 _destinationChainSelector | ||
) external onlyOwner { | ||
whitelistedChains[_destinationChainSelector] = true; | ||
} | ||
|
||
function denylistChain( | ||
uint64 _destinationChainSelector | ||
) external onlyOwner { | ||
whitelistedChains[_destinationChainSelector] = false; | ||
} | ||
|
||
function mint( | ||
uint64 destinationChainSelector, | ||
address receiver, | ||
PayFeesIn payFeesIn | ||
) external { | ||
uint64 _destinationChainSelector, | ||
address _receiver, | ||
address _token, | ||
uint256 _amount, | ||
string _metadataURL | ||
) | ||
external | ||
onlyWhitelistedChain(_destinationChainSelector) | ||
returns (bytes32 messageId) | ||
{ | ||
Client.EVMTokenAmount[] | ||
memory tokenAmounts = new Client.EVMTokenAmount[](1); | ||
Client.EVMTokenAmount memory tokenAmount = Client.EVMTokenAmount({ | ||
token: _token, | ||
amount: _amount | ||
}); | ||
tokenAmounts[0] = tokenAmount; | ||
|
||
// Build the CCIP Message | ||
Client.EVM2AnyMessage memory message = Client.EVM2AnyMessage({ | ||
receiver: abi.encode(receiver), | ||
data: abi.encodeWithSignature("mint(address)", msg.sender), | ||
tokenAmounts: new Client.EVMTokenAmount[](0), | ||
extraArgs: "", | ||
feeToken: payFeesIn == PayFeesIn.LINK ? i_link : address(0) | ||
receiver: abi.encode(_receiver), | ||
data: abi.encodeWithSignature("mint(address, _metadataURL)", msg.sender), | ||
tokenAmounts: tokenAmounts, | ||
extraArgs: Client._argsToBytes( | ||
Client.EVMExtraArgsV1({gasLimit: 200_000, strict: false}) | ||
), | ||
feeToken: address(linkToken) | ||
}); | ||
|
||
// CCIP Fees Management | ||
uint256 fees = router.getFee(_destinationChainSelector, message); | ||
|
||
uint256 fee = IRouterClient(i_router).getFee( | ||
destinationChainSelector, | ||
message | ||
if (fees > linkToken.balanceOf(address(this))) | ||
revert NotEnoughBalance(linkToken.balanceOf(address(this)), fees); | ||
|
||
linkToken.approve(address(router), fees); | ||
|
||
// Approve Router to spend CCIP-BnM tokens we send | ||
IERC20(_token).approve(address(router), _amount); | ||
|
||
// Send CCIP Message | ||
messageId = router.ccipSend(_destinationChainSelector, message); | ||
|
||
emit TokensTransferred( | ||
messageId, | ||
_destinationChainSelector, | ||
_receiver, | ||
_token, | ||
_amount, | ||
fees | ||
); | ||
} | ||
|
||
bytes32 messageId; | ||
|
||
if (payFeesIn == PayFeesIn.LINK) { | ||
// LinkTokenInterface(i_link).approve(i_router, fee); | ||
messageId = IRouterClient(i_router).ccipSend( | ||
destinationChainSelector, | ||
message | ||
); | ||
} else { | ||
messageId = IRouterClient(i_router).ccipSend{value: fee}( | ||
destinationChainSelector, | ||
message | ||
); | ||
} | ||
|
||
emit MessageSent(messageId); | ||
function withdrawToken( | ||
address _beneficiary, | ||
address _token | ||
) public onlyOwner { | ||
uint256 amount = IERC20(_token).balanceOf(address(this)); | ||
|
||
if (amount == 0) revert NothingToWithdraw(); | ||
|
||
IERC20(_token).transfer(_beneficiary, amount); | ||
} | ||
|
||
receive() external payable {} | ||
} |