forked from axelarnetwork/axelar-examples
-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathNftLinker.sol
117 lines (105 loc) · 5.38 KB
/
NftLinker.sol
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
// SPDX-License-Identifier: MIT
pragma solidity 0.8.9;
import '@openzeppelin/contracts/token/ERC721/ERC721.sol';
import { IERC20 } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/interfaces/IERC20.sol';
import { IAxelarGasService } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/interfaces/IAxelarGasService.sol';
import { IAxelarGateway } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/interfaces/IAxelarGateway.sol';
import { AxelarExecutable } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/executable/AxelarExecutable.sol';
import { Upgradable } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/upgradable/Upgradable.sol';
import { StringToAddress, AddressToString } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/utils/AddressString.sol';
contract NftLinker is ERC721, AxelarExecutable, Upgradable {
using StringToAddress for string;
using AddressToString for address;
error AlreadyInitialized();
mapping(uint256 => bytes) public original; //abi.encode(originaChain, operator, tokenId);
string public chainName; //To check if we are the source chain.
IAxelarGasService public immutable gasService;
constructor(address gateway_, address gasReceiver_) ERC721('Axelar NFT Linker', 'ANL') AxelarExecutable(gateway_) {
gasService = IAxelarGasService(gasReceiver_);
}
function _setup(bytes calldata params) internal override {
string memory chainName_ = abi.decode(params, (string));
if (bytes(chainName).length != 0) revert AlreadyInitialized();
chainName = chainName_;
}
//The main function users will interract with.
function sendNFT(
address operator,
uint256 tokenId,
string memory destinationChain,
address destinationAddress
) external payable {
//If we are the operator then this is a minted token that lives remotely.
if (operator == address(this)) {
require(ownerOf(tokenId) == _msgSender(), 'NOT_YOUR_TOKEN');
_sendMintedToken(tokenId, destinationChain, destinationAddress);
} else {
IERC721(operator).transferFrom(_msgSender(), address(this), tokenId);
_sendNativeToken(operator, tokenId, destinationChain, destinationAddress);
}
}
//Burns and sends a token.
function _sendMintedToken(
uint256 tokenId,
string memory destinationChain,
address destinationAddress
) internal {
_burn(tokenId);
//Get the original information.
(string memory originalChain, address operator, uint256 originalTokenId) = abi.decode(
original[tokenId],
(string, address, uint256)
);
//Create the payload.
bytes memory payload = abi.encode(originalChain, operator, originalTokenId, destinationAddress);
string memory stringAddress = address(this).toString();
//Pay for gas. We could also send the contract call here but then the sourceAddress will be that of the gas receiver which is a problem later.
gasService.payNativeGasForContractCall{ value: msg.value }(address(this), destinationChain, stringAddress, payload, msg.sender);
//Call the remote contract.
gateway.callContract(destinationChain, stringAddress, payload);
}
//Locks and sends a token.
function _sendNativeToken(
address operator,
uint256 tokenId,
string memory destinationChain,
address destinationAddress
) internal {
//Create the payload.
bytes memory payload = abi.encode(chainName, operator, tokenId, destinationAddress);
string memory stringAddress = address(this).toString();
//Pay for gas. We could also send the contract call here but then the sourceAddress will be that of the gas receiver which is a problem later.
gasService.payNativeGasForContractCall{ value: msg.value }(address(this), destinationChain, stringAddress, payload, msg.sender);
//Call remote contract.
gateway.callContract(destinationChain, stringAddress, payload);
}
//This is automatically executed by Axelar Microservices since gas was payed for.
function _execute(
string calldata, /*sourceChain*/
string calldata sourceAddress,
bytes calldata payload
) internal override {
//Check that the sender is another token linker.
require(sourceAddress.toAddress() == address(this), 'NOT_A_LINKER');
//Decode the payload.
(string memory originalChain, address operator, uint256 tokenId, address destinationAddress) = abi.decode(
payload,
(string, address, uint256, address)
);
//If this is the original chain then we give the NFT locally.
if (keccak256(bytes(originalChain)) == keccak256(bytes(chainName))) {
IERC721(operator).transferFrom(address(this), destinationAddress, tokenId);
//Otherwise we need to mint a new one.
} else {
//We need to save all the relevant information.
bytes memory originalData = abi.encode(originalChain, operator, tokenId);
//Avoids tokenId collisions.
uint256 newTokenId = uint256(keccak256(originalData));
original[newTokenId] = originalData;
_safeMint(destinationAddress, newTokenId);
}
}
function contractId() external pure returns (bytes32) {
return keccak256('example');
}
}