Created
December 4, 2022 11:43
-
-
Save ArpitxGit/328b67bdd70a55bba5241a43397ba4d5 to your computer and use it in GitHub Desktop.
NFT Bridge for Starknet
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
// SPDX-License-Identifier: MIT | |
pragma solidity ^0.8.13; | |
import "@openzeppelin/contracts/token/ERC721/IERC721.sol"; | |
import "./IStarknetCore.sol"; | |
contract Gateway { | |
uint256 public endpointGateway; | |
IStarknetCore public starknetCore; | |
uint256 constant ENDPOINT_GATEWAY_SELECTOR = | |
1738423374452994793145864788013146788518531877200292826651981332061687045062; | |
uint256 constant BRIDGE_MODE_DEPOSIT = 0; | |
uint256 constant BRIDGE_MODE_WITHDRAW = 1; | |
// Bootstrap | |
constructor(address _starknetCore, uint256 _endpointGateway) { | |
require( | |
_starknetCore != address(0), | |
"Gateway/invalid-starknet-core-address" | |
); | |
require( | |
_endpointGateway != 0, | |
"Gateway/invalid-starknet-gateway-address" | |
); | |
starknetCore = IStarknetCore(_starknetCore); | |
endpointGateway = _endpointGateway; | |
} | |
// Utils | |
function addressToUint(address value) | |
internal | |
pure | |
returns (uint256 convertedValue) | |
{ | |
convertedValue = uint256(uint160(address(value))); | |
} | |
event BridgeToStarknet( | |
address indexed l1ERC721, | |
uint256 indexed l2ERC721, | |
uint256 indexed l2Account, | |
uint256 tokenId | |
); | |
event BridgeFromStarknet( | |
address indexed l1ERC721, | |
uint256 indexed l2ERC721, | |
address indexed l1Account, | |
uint256 tokenId | |
); | |
// Bridging to Starknet | |
function bridgeToStarknet( | |
IERC721 _l1TokenContract, | |
uint256 _l2TokenContract, | |
uint256 _tokenId, | |
uint256 _account | |
) external { | |
uint256[] memory payload = new uint256[](4); | |
// optimistic transfer, should revert if no approved or not owner | |
_l1TokenContract.transferFrom(msg.sender, address(this), _tokenId); | |
// build deposit message payload | |
payload[0] = _account; | |
payload[1] = addressToUint(address(_l1TokenContract)); | |
payload[2] = _l2TokenContract; | |
payload[3] = _tokenId; | |
// send message | |
starknetCore.sendMessageToL2( | |
endpointGateway, | |
ENDPOINT_GATEWAY_SELECTOR, | |
payload | |
); | |
emit BridgeToStarknet( | |
address(_l1TokenContract), | |
_l2TokenContract, | |
_account, | |
_tokenId | |
); | |
} | |
function bridgeFromStarknetAvailable( | |
IERC721 _l1TokenContract, | |
uint256 _l2TokenContract, | |
uint256 _tokenId | |
) external view returns (bool) { | |
uint256[] memory payload = new uint256[](5); | |
// build withdraw message payload | |
payload[0] = BRIDGE_MODE_WITHDRAW; | |
payload[1] = addressToUint(msg.sender); | |
payload[2] = addressToUint(address(_l1TokenContract)); | |
payload[3] = _l2TokenContract; | |
payload[4] = _tokenId; | |
bytes32 msgHash = keccak256( | |
abi.encodePacked( | |
endpointGateway, | |
addressToUint(address(this)), | |
payload.length, | |
payload | |
) | |
); | |
return starknetCore.l2ToL1Messages(msgHash) > 0; | |
} | |
function debug_bridgeFromStarknetAvailable( | |
IERC721 _l1TokenContract, | |
uint256 _l2TokenContract, | |
uint256 _tokenId | |
) external view returns (bytes32) { | |
uint256[] memory payload = new uint256[](5); | |
// build withdraw message payload | |
payload[0] = BRIDGE_MODE_WITHDRAW; | |
payload[1] = addressToUint(msg.sender); | |
payload[2] = addressToUint(address(_l1TokenContract)); | |
payload[3] = _l2TokenContract; | |
payload[4] = _tokenId; | |
bytes32 msgHash = keccak256( | |
abi.encodePacked( | |
endpointGateway, | |
addressToUint(address(this)), | |
payload.length, | |
payload | |
) | |
); | |
return msgHash; | |
} | |
// Bridging back from Starknet | |
function bridgeFromStarknet( | |
IERC721 _l1TokenContract, | |
uint256 _l2TokenContract, | |
uint256 _tokenId | |
) external { | |
uint256[] memory payload = new uint256[](5); | |
// build withdraw message payload | |
payload[0] = BRIDGE_MODE_WITHDRAW; | |
payload[1] = addressToUint(msg.sender); | |
payload[2] = addressToUint(address(_l1TokenContract)); | |
payload[3] = _l2TokenContract; | |
payload[4] = _tokenId; | |
// consum withdraw message | |
starknetCore.consumeMessageFromL2(endpointGateway, payload); | |
// optimistic transfer, should revert if gateway is not token owner | |
_l1TokenContract.transferFrom(address(this), msg.sender, _tokenId); | |
emit BridgeFromStarknet( | |
address(_l1TokenContract), | |
_l2TokenContract, | |
msg.sender, | |
_tokenId | |
); | |
} | |
} |
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
// SPDX-License-Identifier: MIT | |
pragma solidity ^0.8.13; | |
interface IStarknetCore { | |
/** | |
Sends a message to an L2 contract. | |
*/ | |
function sendMessageToL2( | |
uint256 to_address, | |
uint256 selector, | |
uint256[] calldata payload | |
) external; | |
/** | |
Consumes a message that was sent from an L2 contract. | |
*/ | |
function consumeMessageFromL2( | |
uint256 fromAddress, | |
uint256[] calldata payload | |
) external; | |
/** | |
Message registry | |
*/ | |
function l2ToL1Messages(bytes32 msgHash) external view returns (uint256); | |
} |
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
//SPDX-License-Identifier: MIT | |
pragma solidity ^0.8.13; | |
import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; | |
// import "@openzeppelin/contracts/access/Ownable.sol"; | |
import "@openzeppelin/contracts/utils/Counters.sol"; | |
/* | |
Can't Be Evil license from a16z https://a16zcrypto.com/wp-content/uploads/2022/08/Cant-Be-Evil-Licenses.pdf | |
License Version CBE-CC0 https://7q7win2vvm2wnqvltzauqamrnuyhq3jn57yqad2nrgau4fe3l5ya.arweave.net/_D9kN1WrNWbCq55BSAGRbTB4bS3v8QAPTYmBThSbX3A/0 | |
*/ | |
import {LicenseVersion, CantBeEvil} from "@a16z/contracts/licenses/CantBeEvil.sol"; | |
contract NFT is ERC721, CantBeEvil { | |
//Counters for assigning and updating tokenIds | |
using Counters for Counters.Counter; | |
Counters.Counter private _tokenIdCounter; | |
mapping(uint256 => address) internal tokenIdToOwner; | |
//contructor that gets executed while compiling including ERC721 and Can'tBeEvil Interface | |
constructor( ) ERC721("BridgeNFT", "NFT") CantBeEvil(LicenseVersion.CBE_CC0) {} | |
function mint() external { | |
uint256 newTokenId = _tokenIdCounter.current(); | |
_tokenIdCounter.increment(); | |
tokenIdToOwner[newTokenId] = msg.sender; | |
_mint(msg.sender, newTokenId); | |
} | |
//supportsInterface as two or more base classes define function with same name and parameter types. | |
function supportsInterface(bytes4 interfaceId) public view virtual override(ERC721, CantBeEvil) returns (bool) { | |
return super.supportsInterface(interfaceId); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment