diff --git a/contracts/bridge/NFT/NFTBase.sol b/contracts/bridge/NFT/NFTBase.sol new file mode 100644 index 00000000..57bf93bf --- /dev/null +++ b/contracts/bridge/NFT/NFTBase.sol @@ -0,0 +1,359 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.13; + +import "solmate/utils/ReentrancyGuard.sol"; +import "solmate/tokens/ERC721.sol"; +import "solmate/tokens/ERC1155.sol"; +import {IConnector} from "../../interfaces/IConnector.sol"; +import "../../interfaces/INFTHook.sol"; +import "../../interfaces/INFTBridge.sol"; +import "../../interfaces/INFTMetadata.sol"; +import "../../common/Errors.sol"; +import "../../common/Constants.sol"; +import "../../utils/AccessControl.sol"; + +abstract contract NFTBase is + ReentrancyGuard, + INFTBridge, + ERC721TokenReceiver, + ERC1155TokenReceiver, + AccessControl +{ + address public immutable token; + bytes32 public bridgeType; + INFTHook public hook__; + // message identifier => cache + mapping(bytes32 => bytes) public identifierCache; + + // connector => cache + mapping(address => bytes) public connectorCache; + + mapping(address => bool) public validConnectors; + + event ConnectorStatusUpdated(address connector, bool status); + + event HookUpdated(address newHook); + + event BridgingTokens( + address connector, + address sender, + address receiver, + uint256 tokenId, + uint256 amount, + bytes32 messageId + ); + event TokensBridged( + address connecter, + address receiver, + uint256 tokenId, + uint256 amount, + bytes32 messageId + ); + + constructor(address token_) AccessControl(msg.sender) { + if (token_ != ETH_ADDRESS && token_.code.length == 0) + revert InvalidTokenContract(); + token = token_; + } + + /** + * @notice this function is used to update hook + * @dev it can only be updated by owner + * @dev should be carefully migrated as it can risk user funds + * @param hook_ new hook address + */ + function updateHook( + address hook_, + bool approve_ + ) external virtual onlyOwner { + // remove the approval from the old hook + if (bridgeType == ERC721_VAULT) { + if ( + ERC721(token).isApprovedForAll(address(this), address(hook__)) + ) { + ERC721(token).setApprovalForAll(address(hook__), false); + } + if (approve_) { + ERC721(token).setApprovalForAll(hook_, approve_); + } + } else { + if ( + ERC1155(token).isApprovedForAll(address(this), address(hook__)) + ) { + ERC1155(token).setApprovalForAll(address(hook__), false); + } + if (approve_) { + ERC1155(token).setApprovalForAll(hook_, approve_); + } + } + hook__ = INFTHook(hook_); + + emit HookUpdated(hook_); + } + + function updateConnectorStatus( + address[] calldata connectors, + bool[] calldata statuses + ) external onlyOwner { + uint256 length = connectors.length; + for (uint256 i; i < length; i++) { + validConnectors[connectors[i]] = statuses[i]; + emit ConnectorStatusUpdated(connectors[i], statuses[i]); + } + } + + /** + * @notice Executes pre-bridge operations before initiating a token bridge transfer. + * @dev This internal function is called before initiating a token bridge transfer. + * It validates the receiver address and the connector, and if a pre-hook contract is defined, + * it executes the source pre-hook call. + * @param connector_ The address of the connector responsible for the transfer. + * @param transferInfo_ Information about the transfer. + * @return transferInfo Information about the transfer after pre-bridge operations. + * @return postHookData Data returned from the pre-hook call. + * @dev Reverts with `ZeroAddressReceiver` if the receiver address is zero. + * Reverts with `InvalidConnector` if the connector address is not valid. + */ + function _beforeBridge( + address connector_, + NFTTransferInfo memory transferInfo_ + ) + internal + returns (NFTTransferInfo memory transferInfo, bytes memory postHookData) + { + if (transferInfo_.receiver == address(0)) revert ZeroAddressReceiver(); + if (!validConnectors[connector_]) revert InvalidConnector(); + + if (address(hook__) != address(0)) { + (transferInfo, postHookData) = hook__.srcPreHookCall( + SrcPreHookNFTCallParams(connector_, msg.sender, transferInfo_) + ); + } else { + transferInfo = transferInfo_; + } + } + + /** + * @notice Executes post-bridge operations after completing a token bridge transfer. + * @dev This internal function is called after completing a token bridge transfer. + * It executes the source post-hook call if a hook contract is defined, calculates fees, + * calls the outbound function of the connector, and emits an event for tokens withdrawn. + * @param msgGasLimit_ The gas limit for the outbound call. + * @param connector_ The address of the connector responsible for the transfer. + * @param options_ Additional options for the outbound call. + * @param postHookData_ Data returned from the source post-hook call. + * @param transferInfo_ Information about the transfer. + * @dev Reverts with `MessageIdMisMatched` if the returned message ID does not match the expected message ID. + */ + function _afterBridge( + uint256 msgGasLimit_, + address connector_, + bytes memory options_, + bytes memory postHookData_, + NFTTransferInfo memory transferInfo_ + ) internal { + NFTTransferInfo memory transferInfo = transferInfo_; + if (address(hook__) != address(0)) { + transferInfo = hook__.srcPostHookCall( + SrcPostHookNFTCallParams( + connector_, + options_, + postHookData_, + transferInfo_ + ) + ); + } + + uint256 fees = msg.value; + bytes32 messageId = IConnector(connector_).getMessageId(); + bytes32 returnedMessageId = IConnector(connector_).outbound{ + value: fees + }( + msgGasLimit_, + abi.encode( + transferInfo.receiver, + transferInfo.tokenId, + transferInfo.amount, + messageId, + transferInfo.extraData + ), + options_ + ); + if (returnedMessageId != messageId) revert MessageIdMisMatched(); + + emit BridgingTokens( + connector_, + msg.sender, + transferInfo.receiver, + transferInfo.tokenId, + transferInfo.amount, + messageId + ); + } + + /** + * @notice Executes pre-mint operations before minting tokens. + * @dev This internal function is called before minting tokens. + * It validates the caller as a valid connector, checks if the receiver is not this contract, the bridge contract, + * or the token contract, and executes the destination pre-hook call if a hook contract is defined. + * @param transferInfo_ Information about the transfer. + * @return postHookData Data returned from the destination pre-hook call. + * @return transferInfo Information about the transfer after pre-mint operations. + * @dev Reverts with `InvalidConnector` if the caller is not a valid connector. + * Reverts with `CannotTransferOrExecuteOnBridgeContracts` if the receiver is this contract, the bridge contract, + * or the token contract. + */ + function _beforeMint( + uint32, + NFTTransferInfo memory transferInfo_ + ) + internal + returns (bytes memory postHookData, NFTTransferInfo memory transferInfo) + { + if (!validConnectors[msg.sender]) revert InvalidConnector(); + + // no need of source check here, as if invalid caller, will revert with InvalidPoolId + if ( + transferInfo_.receiver == address(this) || + // transferInfo_.receiver == address(bridge__) || + transferInfo_.receiver == token + ) revert CannotTransferOrExecuteOnBridgeContracts(); + + if (address(hook__) != address(0)) { + (postHookData, transferInfo) = hook__.dstPreHookCall( + DstPreHookNFTCallParams( + msg.sender, + connectorCache[msg.sender], + transferInfo_ + ) + ); + } else { + transferInfo = transferInfo_; + } + } + + /** + * @notice Executes post-mint operations after minting tokens. + * @dev This internal function is called after minting tokens. + * It executes the destination post-hook call if a hook contract is defined and updates cache data. + * @param messageId_ The unique identifier for the mint transaction. + * @param postHookData_ Data returned from the destination pre-hook call. + * @param transferInfo_ Information about the mint transaction. + */ + function _afterMint( + uint256, + bytes32 messageId_, + bytes memory postHookData_, + NFTTransferInfo memory transferInfo_ + ) internal { + if (address(hook__) != address(0)) { + CacheData memory cacheData = hook__.dstPostHookCall( + DstPostHookNFTCallParams( + msg.sender, + messageId_, + connectorCache[msg.sender], + postHookData_, + transferInfo_ + ) + ); + + identifierCache[messageId_] = cacheData.identifierCache; + connectorCache[msg.sender] = cacheData.connectorCache; + } + + if (transferInfo_.extraData.length > 0) { + INFTMetadata(token).setMetadata( + transferInfo_.tokenId, + transferInfo_.extraData + ); + } + + emit TokensBridged( + msg.sender, + transferInfo_.receiver, + transferInfo_.tokenId, + transferInfo_.amount, + messageId_ + ); + } + + /** + * @notice Executes pre-retry operations before retrying a failed transaction. + * @dev This internal function is called before retrying a failed transaction. + * It validates the connector, retrieves cache data for the given message ID, + * and executes the pre-retry hook if defined. + * @param connector_ The address of the connector responsible for the failed transaction. + * @param messageId_ The unique identifier for the failed transaction. + * @return postHookData Data returned from the pre-retry hook call. + * @return transferInfo Information about the transfer. + * @dev Reverts with `InvalidConnector` if the connector is not valid. + * Reverts with `NoPendingData` if there is no pending data for the given message ID. + */ + function _beforeRetry( + address connector_, + bytes32 messageId_ + ) + internal + returns (bytes memory postHookData, NFTTransferInfo memory transferInfo) + { + if (!validConnectors[connector_]) revert InvalidConnector(); + + CacheData memory cacheData = CacheData( + identifierCache[messageId_], + connectorCache[connector_] + ); + + if (cacheData.identifierCache.length == 0) revert NoPendingData(); + (postHookData, transferInfo) = hook__.preRetryHook( + PreRetryHookCallParams(connector_, cacheData) + ); + } + + /** + * @notice Executes post-retry operations after retrying a failed transaction. + * @dev This internal function is called after retrying a failed transaction. + * It retrieves cache data for the given message ID, executes the post-retry hook if defined, + * and updates cache data. + * @param connector_ The address of the connector responsible for the failed transaction. + * @param messageId_ The unique identifier for the failed transaction. + * @param postHookData Data returned from the pre-retry hook call. + */ + function _afterRetry( + address connector_, + bytes32 messageId_, + bytes memory postHookData + ) internal { + CacheData memory cacheData = CacheData( + identifierCache[messageId_], + connectorCache[connector_] + ); + + (cacheData) = hook__.postRetryHook( + PostRetryHookCallParams( + connector_, + messageId_, + postHookData, + cacheData + ) + ); + identifierCache[messageId_] = cacheData.identifierCache; + connectorCache[connector_] = cacheData.connectorCache; + } + + /** + * @notice Retrieves the minimum fees required for a transaction from a connector. + * @dev This function returns the minimum fees required for a transaction from the specified connector, + * based on the provided message gas limit and payload size. + * @param connector_ The address of the connector. + * @param msgGasLimit_ The gas limit for the transaction. + * @param payloadSize_ The size of the payload for the transaction. + * @return totalFees The total minimum fees required for the transaction. + */ + function getMinFees( + address connector_, + uint256 msgGasLimit_, + uint256 payloadSize_ + ) external view returns (uint256 totalFees) { + return IConnector(connector_).getMinFees(msgGasLimit_, payloadSize_); + } +} diff --git a/contracts/bridge/NFT/NFTController.sol b/contracts/bridge/NFT/NFTController.sol new file mode 100644 index 00000000..1aaf4258 --- /dev/null +++ b/contracts/bridge/NFT/NFTController.sol @@ -0,0 +1,142 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.13; + +import "./NFTBase.sol"; +import {IMintableERC721} from "../../interfaces/IMintableERC721.sol"; +import {IMintableERC1155} from "../../interfaces/IMintableERC1155.sol"; + +contract NFTController is NFTBase { + mapping(uint256 => uint256) public totalMinted; + bytes4 private interfaceId; + + constructor(address token_, bytes4 interfaceId_) NFTBase(token_) { + if (interfaceId_ != ID_ERC721 && interfaceId_ != ID_ERC1155) + revert InvalidTokenContract(); + bridgeType = NORMAL_CONTROLLER; + interfaceId = interfaceId_; + } + + /** + * @notice Bridges tokens between chains. + * @dev This function allows bridging tokens between different chains. + * @param receiver_ The address to receive the bridged tokens. + * @param tokenOwner_ The owner address of tokens to bridge. + * @param tokenId_ The id of token to bridge. + * @param amount_ The amount of tokens to bridge. + * @param msgGasLimit_ The gas limit for the execution of the bridging process. + * @param connector_ The address of the connector contract responsible for the bridge. + * @param extraData_ The extra data passed to hook functions. + * @param options_ Additional options for the bridging process. + */ + function bridge( + address receiver_, + address tokenOwner_, + uint256 tokenId_, + uint256 amount_, + uint256 msgGasLimit_, + address connector_, + bytes calldata extraData_, + bytes calldata options_ + ) external payable nonReentrant { + ( + NFTTransferInfo memory transferInfo, + bytes memory postHookData + ) = _beforeBridge( + connector_, + NFTTransferInfo(receiver_, tokenId_, amount_, extraData_) + ); + + // to maintain socket dl specific accounting for super token + totalMinted[transferInfo.tokenId] -= transferInfo.amount; + _burn(tokenOwner_, transferInfo.tokenId, transferInfo.amount); + _afterBridge( + msgGasLimit_, + connector_, + options_, + postHookData, + transferInfo + ); + } + + /** + * @notice Receives inbound tokens from another chain. + * @dev This function is used to receive tokens from another chain. + * @param siblingChainSlug_ The identifier of the sibling chain. + * @param payload_ The payload containing the inbound tokens. + */ + function receiveInbound( + uint32 siblingChainSlug_, + bytes memory payload_ + ) external payable override nonReentrant { + ( + address receiver, + uint256 tokenId, + uint256 lockAmount, + bytes32 messageId, + bytes memory extraData + ) = abi.decode(payload_, (address, uint256, uint256, bytes32, bytes)); + + // convert to shares + NFTTransferInfo memory transferInfo = NFTTransferInfo( + receiver, + tokenId, + lockAmount, + extraData + ); + bytes memory postHookData; + (postHookData, transferInfo) = _beforeMint( + siblingChainSlug_, + transferInfo + ); + + _mint(transferInfo.receiver, transferInfo.tokenId, transferInfo.amount); + totalMinted[transferInfo.tokenId] += transferInfo.amount; + + _afterMint(lockAmount, messageId, postHookData, transferInfo); + } + + /** + * @notice Retry a failed transaction. + * @dev This function allows retrying a failed transaction sent through a connector. + * @param connector_ The address of the connector contract responsible for the failed transaction. + * @param messageId_ The unique identifier of the failed transaction. + */ + function retry( + address connector_, + bytes32 messageId_ + ) external nonReentrant { + ( + bytes memory postHookData, + NFTTransferInfo memory transferInfo + ) = _beforeRetry(connector_, messageId_); + _mint(transferInfo.receiver, transferInfo.tokenId, transferInfo.amount); + totalMinted[transferInfo.tokenId] += transferInfo.amount; + + _afterRetry(connector_, messageId_, postHookData); + } + + function _burn( + address user_, + uint256 burnTokenId_, + uint256 burnAmount_ + ) internal virtual { + if (interfaceId == ID_ERC721) { + IMintableERC721(token).burn(user_, burnTokenId_); + } else { + IMintableERC1155(token).burn(user_, burnTokenId_, burnAmount_); + } + } + + function _mint( + address user_, + uint256 mintTokenId_, + uint256 mintAmount_ + ) internal virtual { + if (mintAmount_ == 0) return; + if (interfaceId == ID_ERC721) { + IMintableERC721(token).mint(user_, mintTokenId_); + } else { + IMintableERC1155(token).mint(user_, mintTokenId_, mintAmount_); + } + } +} diff --git a/contracts/bridge/NFT/NFTVault.sol b/contracts/bridge/NFT/NFTVault.sol new file mode 100644 index 00000000..d701db43 --- /dev/null +++ b/contracts/bridge/NFT/NFTVault.sol @@ -0,0 +1,172 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.13; + +import "./NFTBase.sol"; +import "../../interfaces/IConnector.sol"; +import "solmate/tokens/ERC721.sol"; +import "solmate/tokens/ERC1155.sol"; + +/** + * @title SuperToken + * @notice A contract which enables bridging a token to its sibling chains. + * @dev This contract implements ISuperTokenOrVault to support message bridging through IMessageBridge compliant contracts. + */ +contract NFTVault is NFTBase { + /** + * @notice constructor for creating a new SuperTokenVault. + * @param token_ token contract address which is to be bridged. + * @param interfaceId_ EIP-165 interface id of token contract which is to be bridged. + */ + + constructor(address token_, bytes4 interfaceId_) NFTBase(token_) { + if (interfaceId_ != ID_ERC721 && interfaceId_ != ID_ERC1155) + revert InvalidTokenContract(); + bridgeType = interfaceId_ == ID_ERC721 ? ERC721_VAULT : ERC1155_VAULT; + } + + /** + * @notice Bridges tokens between chains. + * @dev This function allows bridging tokens between different chains. + * @param receiver_ The address to receive the bridged tokens. + * @param tokenOwner_ The owner address of tokens to bridge. + * @param tokenId_ The id of token to bridge. + * @param amount_ The amount of tokens to bridge. + * @param msgGasLimit_ The gas limit for the execution of the bridging process. + * @param connector_ The address of the connector contract responsible for the bridge. + * @param extraData_ The extra data passed to hook functions. + * @param options_ Additional options for the bridging process. + */ + function bridge( + address receiver_, + address tokenOwner_, + uint256 tokenId_, + uint256 amount_, + uint256 msgGasLimit_, + address connector_, + bytes calldata extraData_, + bytes calldata options_ + ) external payable nonReentrant { + ( + NFTTransferInfo memory transferInfo, + bytes memory postHookData + ) = _beforeBridge( + connector_, + NFTTransferInfo(receiver_, tokenId_, amount_, extraData_) + ); + + _receiveTokens(tokenOwner_, transferInfo.tokenId, transferInfo.amount); + + _afterBridge( + msgGasLimit_, + connector_, + options_, + postHookData, + transferInfo + ); + } + + /** + * @notice Receives inbound tokens from another chain. + * @dev This function is used to receive tokens from another chain. + * @param siblingChainSlug_ The identifier of the sibling chain. + * @param payload_ The payload containing the inbound tokens. + */ + function receiveInbound( + uint32 siblingChainSlug_, + bytes memory payload_ + ) external payable override nonReentrant { + ( + address receiver, + uint256 tokenId, + uint256 unlockAmount, + bytes32 messageId, + bytes memory extraData + ) = abi.decode(payload_, (address, uint256, uint256, bytes32, bytes)); + + NFTTransferInfo memory transferInfo = NFTTransferInfo( + receiver, + tokenId, + unlockAmount, + extraData + ); + + bytes memory postHookData; + (postHookData, transferInfo) = _beforeMint( + siblingChainSlug_, + transferInfo + ); + + _transferTokens( + transferInfo.receiver, + transferInfo.tokenId, + transferInfo.amount + ); + + _afterMint(unlockAmount, messageId, postHookData, transferInfo); + } + + /** + * @notice Retry a failed transaction. + * @dev This function allows retrying a failed transaction sent through a connector. + * @param connector_ The address of the connector contract responsible for the failed transaction. + * @param messageId_ The unique identifier of the failed transaction. + */ + function retry( + address connector_, + bytes32 messageId_ + ) external nonReentrant { + ( + bytes memory postHookData, + NFTTransferInfo memory transferInfo + ) = _beforeRetry(connector_, messageId_); + _transferTokens( + transferInfo.receiver, + transferInfo.tokenId, + transferInfo.amount + ); + + _afterRetry(connector_, messageId_, postHookData); + } + + function _transferTokens( + address receiver_, + uint256 tokenId_, + uint256 amount_ + ) internal { + if (amount_ == 0) return; + if (bridgeType == ERC721_VAULT) { + ERC721(token).safeTransferFrom(address(this), receiver_, tokenId_); + } else { + ERC1155(token).safeTransferFrom( + address(this), + receiver_, + tokenId_, + amount_, + new bytes(0) + ); + } + } + + function _receiveTokens( + address tokenOwner_, + uint256 tokenId_, + uint256 amount_ + ) internal { + if (amount_ == 0) return; + if (bridgeType == ERC721_VAULT) { + ERC721(token).safeTransferFrom( + tokenOwner_, + address(this), + tokenId_ + ); + } else { + ERC1155(token).safeTransferFrom( + tokenOwner_, + address(this), + tokenId_, + amount_, + new bytes(0) + ); + } + } +} diff --git a/contracts/common/Constants.sol b/contracts/common/Constants.sol index d381dd4f..a7251dfe 100644 --- a/contracts/common/Constants.sol +++ b/contracts/common/Constants.sol @@ -19,3 +19,8 @@ bytes32 constant LIMIT_EXECUTION_YIELD_TOKEN_HOOK = keccak256( bytes32 constant ERC20_VAULT = keccak256("ERC20_VAULT"); bytes32 constant NATIVE_VAULT = keccak256("NATIVE_VAULT"); +bytes32 constant ERC721_VAULT = keccak256("ERC721_VAULT"); +bytes32 constant ERC1155_VAULT = keccak256("ERC1155_VAULT"); + +bytes4 constant ID_ERC721 = 0x80ac58cd; // EIP-165 interface id +bytes4 constant ID_ERC1155 = 0xd9b67a26; // EIP-165 interface id diff --git a/contracts/common/NFTStructs.sol b/contracts/common/NFTStructs.sol new file mode 100644 index 00000000..8ead2ca1 --- /dev/null +++ b/contracts/common/NFTStructs.sol @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.13; + +struct SrcPreHookNFTCallParams { + address connector; + address msgSender; + NFTTransferInfo transferInfo; +} + +struct SrcPostHookNFTCallParams { + address connector; + bytes options; + bytes postHookData; + NFTTransferInfo transferInfo; +} + +struct DstPreHookNFTCallParams { + address connector; + bytes connectorCache; + NFTTransferInfo transferInfo; +} + +struct DstPostHookNFTCallParams { + address connector; + bytes32 messageId; + bytes connectorCache; + bytes postHookData; + NFTTransferInfo transferInfo; +} + +struct NFTTransferInfo { + address receiver; + uint256 tokenId; + uint256 amount; + bytes extraData; +} diff --git a/contracts/interfaces/IMintableERC1155.sol b/contracts/interfaces/IMintableERC1155.sol new file mode 100644 index 00000000..1b4c73ff --- /dev/null +++ b/contracts/interfaces/IMintableERC1155.sol @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.13; + +interface IMintableERC1155 { + function mint(address receiver_, uint256 id_, uint256 amount_) external; + + function burn(address burner_, uint256 id_, uint256 amount_) external; +} diff --git a/contracts/interfaces/IMintableERC721.sol b/contracts/interfaces/IMintableERC721.sol new file mode 100644 index 00000000..c6e8a7d7 --- /dev/null +++ b/contracts/interfaces/IMintableERC721.sol @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.13; + +interface IMintableERC721 { + function mint(address receiver_, uint256 id_) external; + + function burn(address burner_, uint256 id_) external; +} diff --git a/contracts/interfaces/INFTBridge.sol b/contracts/interfaces/INFTBridge.sol new file mode 100644 index 00000000..cb57a111 --- /dev/null +++ b/contracts/interfaces/INFTBridge.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.3; + +interface INFTBridge { + function bridge( + address receiver_, + address tokenOwner_, + uint256 tokenId_, + uint256 amount_, + uint256 msgGasLimit_, + address connector_, + bytes calldata extraData_, + bytes calldata options_ + ) external payable; + + function receiveInbound( + uint32 siblingChainSlug_, + bytes memory payload_ + ) external payable; + + function retry(address connector_, bytes32 messageId_) external; +} diff --git a/contracts/interfaces/INFTHook.sol b/contracts/interfaces/INFTHook.sol new file mode 100644 index 00000000..acb12c37 --- /dev/null +++ b/contracts/interfaces/INFTHook.sol @@ -0,0 +1,76 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.3; +import "../common/Structs.sol"; +import "../common/NFTStructs.sol"; + +interface INFTHook { + /** + * @notice Executes pre-hook call for source underlyingAsset. + * @dev This function is used to execute a pre-hook call for the source underlyingAsset before initiating a transfer. + * @param params_ Parameters for the pre-hook call. + * @return transferInfo Information about the transfer. + * @return postHookData returned from the pre-hook call. + */ + function srcPreHookCall( + SrcPreHookNFTCallParams calldata params_ + ) + external + returns ( + NFTTransferInfo memory transferInfo, + bytes memory postHookData + ); + + function srcPostHookCall( + SrcPostHookNFTCallParams calldata params_ + ) external returns (NFTTransferInfo memory transferInfo); + + /** + * @notice Executes pre-hook call for destination underlyingAsset. + * @dev This function is used to execute a pre-hook call for the destination underlyingAsset before initiating a transfer. + * @param params_ Parameters for the pre-hook call. + */ + function dstPreHookCall( + DstPreHookNFTCallParams calldata params_ + ) + external + returns ( + bytes memory postHookData, + NFTTransferInfo memory transferInfo + ); + + /** + * @notice Executes post-hook call for destination underlyingAsset. + * @dev This function is used to execute a post-hook call for the destination underlyingAsset after completing a transfer. + * @param params_ Parameters for the post-hook call. + * @return cacheData Cached data for the post-hook call. + */ + function dstPostHookCall( + DstPostHookNFTCallParams calldata params_ + ) external returns (CacheData memory cacheData); + + /** + * @notice Executes a pre-retry hook for a failed transaction. + * @dev This function is used to execute a pre-retry hook for a failed transaction. + * @param params_ Parameters for the pre-retry hook. + * @return postHookData Data from the post-retry hook. + * @return transferInfo Information about the transfer. + */ + function preRetryHook( + PreRetryHookCallParams calldata params_ + ) + external + returns ( + bytes memory postHookData, + NFTTransferInfo memory transferInfo + ); + + /** + * @notice Executes a post-retry hook for a failed transaction. + * @dev This function is used to execute a post-retry hook for a failed transaction. + * @param params_ Parameters for the post-retry hook. + * @return cacheData Cached data for the post-retry hook. + */ + function postRetryHook( + PostRetryHookCallParams calldata params_ + ) external returns (CacheData memory cacheData); +} diff --git a/contracts/interfaces/INFTMetadata.sol b/contracts/interfaces/INFTMetadata.sol new file mode 100644 index 00000000..c4fad67e --- /dev/null +++ b/contracts/interfaces/INFTMetadata.sol @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.3; + +interface INFTMetadata { + function setMetadata(uint256 tokenId, bytes memory data) external; +} diff --git a/deployments/superbridge/prod_aavegotchi_gotchi_bridge_mainnet_addresses.json b/deployments/superbridge/prod_aavegotchi_gotchi_bridge_mainnet_addresses.json new file mode 100644 index 00000000..c5d3640f --- /dev/null +++ b/deployments/superbridge/prod_aavegotchi_gotchi_bridge_mainnet_addresses.json @@ -0,0 +1,26 @@ +{ + "137": { + "GOTCHI": { + "isAppChain": false, + "NonMintableToken": "0x86935F11C86623deC8a25696E1C19a8659CbF95d", + "Vault": "0xF1D1d61EEDDa7a10b494aF7af87D932AC910f3C5", + "connectors": { + "63157": { + "FAST": "0x8992e26290073183DA46aF2589ff3025be3CdF29" + } + } + } + }, + "63157": { + "GOTCHI": { + "isAppChain": true, + "MintableToken": "0x6Acc828BbbC6874de40Ca20bfeA7Cd2a2DA8DA8c", + "Controller": "0x5964823256B9C8aC7Ba7Dd94807FDF4DFf728215", + "connectors": { + "137": { + "FAST": "0x870cCEc2CB64270cEd3A4BE581Ddc87673882758" + } + } + } + } +} \ No newline at end of file diff --git a/deployments/superbridge/prod_aavegotchi_gotchi_bridge_mainnet_verification.json b/deployments/superbridge/prod_aavegotchi_gotchi_bridge_mainnet_verification.json new file mode 100644 index 00000000..d4638d7a --- /dev/null +++ b/deployments/superbridge/prod_aavegotchi_gotchi_bridge_mainnet_verification.json @@ -0,0 +1,46 @@ +{ + "137": [ + [ + "0x8992e26290073183DA46aF2589ff3025be3CdF29", + "ConnectorPlug", + "contracts/ConnectorPlug.sol", + [ + "0xF1D1d61EEDDa7a10b494aF7af87D932AC910f3C5", + "0xc20687f8dc0ad51d01003013d1c5b02d10DED001", + 63157, + "0x0000000000000000000000000000000000000000000000000000000000000000" + ] + ], + [ + "0xF1D1d61EEDDa7a10b494aF7af87D932AC910f3C5", + "NFTVault", + "contracts/bridge/NFT/NFTVault.sol", + [ + "0x86935F11C86623deC8a25696E1C19a8659CbF95d", + "0x80ac58cd" + ] + ] + ], + "63157": [ + [ + "0x870cCEc2CB64270cEd3A4BE581Ddc87673882758", + "ConnectorPlug", + "contracts/ConnectorPlug.sol", + [ + "0x5964823256B9C8aC7Ba7Dd94807FDF4DFf728215", + "0x34464194a4aCD38F42060D144BF3729F625Ff047", + 137, + "0x0000000000000000000000000000000000000000000000000000000000000000" + ] + ], + [ + "0x5964823256B9C8aC7Ba7Dd94807FDF4DFf728215", + "NFTController", + "contracts/bridge/NFT/NFTController.sol", + [ + "0x6Acc828BbbC6874de40Ca20bfeA7Cd2a2DA8DA8c", + "0x80ac58cd" + ] + ] + ] +} \ No newline at end of file diff --git a/deployments/superbridge/prod_aavegotchi_items_bridge_mainnet_addresses.json b/deployments/superbridge/prod_aavegotchi_items_bridge_mainnet_addresses.json new file mode 100644 index 00000000..a0bdcccf --- /dev/null +++ b/deployments/superbridge/prod_aavegotchi_items_bridge_mainnet_addresses.json @@ -0,0 +1,26 @@ +{ + "137": { + "GOTCHI_ITEM": { + "isAppChain": false, + "NonMintableToken": "0x58de9AaBCaeEC0f69883C94318810ad79Cc6a44f", + "Vault": "0xA421Ed8a4E3cfbFbFd2F621b27bd3C27D71C8b97", + "connectors": { + "63157": { + "FAST": "0x1A9E2D17122B747871a1088FC38952c7dc5d8334" + } + } + } + }, + "63157": { + "GOTCHI_ITEM": { + "isAppChain": true, + "MintableToken": "0xaC336aB3CFC58698B582205A861A5C6B798c01B9", + "Controller": "0x4924E6B720D7283bF2d90104A480a9CFadAb1b77", + "connectors": { + "137": { + "FAST": "0xaf6b8fbacEec187A954C1e85D3614c58C393AA28" + } + } + } + } +} \ No newline at end of file diff --git a/deployments/superbridge/prod_aavegotchi_items_bridge_mainnet_verification.json b/deployments/superbridge/prod_aavegotchi_items_bridge_mainnet_verification.json new file mode 100644 index 00000000..6a5ed613 --- /dev/null +++ b/deployments/superbridge/prod_aavegotchi_items_bridge_mainnet_verification.json @@ -0,0 +1,46 @@ +{ + "137": [ + [ + "0x1A9E2D17122B747871a1088FC38952c7dc5d8334", + "ConnectorPlug", + "contracts/ConnectorPlug.sol", + [ + "0xA421Ed8a4E3cfbFbFd2F621b27bd3C27D71C8b97", + "0xc20687f8dc0ad51d01003013d1c5b02d10DED001", + 63157, + "0x0000000000000000000000000000000000000000000000000000000000000000" + ] + ], + [ + "0xA421Ed8a4E3cfbFbFd2F621b27bd3C27D71C8b97", + "NFTVault", + "contracts/bridge/NFT/NFTVault.sol", + [ + "0x58de9AaBCaeEC0f69883C94318810ad79Cc6a44f", + "0xd9b67a26" + ] + ] + ], + "63157": [ + [ + "0xaf6b8fbacEec187A954C1e85D3614c58C393AA28", + "ConnectorPlug", + "contracts/ConnectorPlug.sol", + [ + "0x4924E6B720D7283bF2d90104A480a9CFadAb1b77", + "0x34464194a4aCD38F42060D144BF3729F625Ff047", + 137, + "0x0000000000000000000000000000000000000000000000000000000000000000" + ] + ], + [ + "0x4924E6B720D7283bF2d90104A480a9CFadAb1b77", + "NFTController", + "contracts/bridge/NFT/NFTController.sol", + [ + "0xaC336aB3CFC58698B582205A861A5C6B798c01B9", + "0xd9b67a26" + ] + ] + ] +} \ No newline at end of file diff --git a/deployments/superbridge/prod_addresses.json b/deployments/superbridge/prod_addresses.json index 484af6b4..1ffa2726 100644 --- a/deployments/superbridge/prod_addresses.json +++ b/deployments/superbridge/prod_addresses.json @@ -2404,5 +2404,57 @@ } } } + }, + "aavegotchi_gotchi_bridge_mainnet": { + "137": { + "GOTCHI": { + "isAppChain": false, + "NonMintableToken": "0x86935F11C86623deC8a25696E1C19a8659CbF95d", + "Vault": "0xF1D1d61EEDDa7a10b494aF7af87D932AC910f3C5", + "connectors": { + "63157": { + "FAST": "0x8992e26290073183DA46aF2589ff3025be3CdF29" + } + } + } + }, + "63157": { + "GOTCHI": { + "isAppChain": true, + "MintableToken": "0x6Acc828BbbC6874de40Ca20bfeA7Cd2a2DA8DA8c", + "Controller": "0x5964823256B9C8aC7Ba7Dd94807FDF4DFf728215", + "connectors": { + "137": { + "FAST": "0x870cCEc2CB64270cEd3A4BE581Ddc87673882758" + } + } + } + } + }, + "aavegotchi_items_bridge_mainnet": { + "137": { + "GOTCHI_ITEM": { + "isAppChain": false, + "NonMintableToken": "0x58de9AaBCaeEC0f69883C94318810ad79Cc6a44f", + "Vault": "0xA421Ed8a4E3cfbFbFd2F621b27bd3C27D71C8b97", + "connectors": { + "63157": { + "FAST": "0x1A9E2D17122B747871a1088FC38952c7dc5d8334" + } + } + } + }, + "63157": { + "GOTCHI_ITEM": { + "isAppChain": true, + "MintableToken": "0xaC336aB3CFC58698B582205A861A5C6B798c01B9", + "Controller": "0x4924E6B720D7283bF2d90104A480a9CFadAb1b77", + "connectors": { + "137": { + "FAST": "0xaf6b8fbacEec187A954C1e85D3614c58C393AA28" + } + } + } + } } -} +} \ No newline at end of file diff --git a/deployments/superbridge/surge_aavegotchi_gotchis_bridge_testnet_addresses.json b/deployments/superbridge/surge_aavegotchi_gotchis_bridge_testnet_addresses.json new file mode 100644 index 00000000..6ebf9ad5 --- /dev/null +++ b/deployments/superbridge/surge_aavegotchi_gotchis_bridge_testnet_addresses.json @@ -0,0 +1,26 @@ +{ + "84532": { + "GOTCHI": { + "isAppChain": false, + "NonMintableToken": "0xf81FFb9E2a72574d3C4Cf4E293D4Fec4A708F2B1", + "Vault": "0x92641fDD13A1B2099675fd1F3798fa0d9e6CDAfa", + "connectors": { + "631571": { + "FAST": "0x8Ca0b8D5a04F902171113A4Ac76a8f1F52FCDF6f" + } + } + } + }, + "631571": { + "GOTCHI": { + "isAppChain": true, + "MintableToken": "0x226625C1B1174e7BaaE8cDC0432Db0e2ED83b7Ba", + "Controller": "0xde3c698E15806384C667b0478C1072867571f6cd", + "connectors": { + "84532": { + "FAST": "0x8Dc7AED7a2A2729D762e63BaDecCd7D50474020B" + } + } + } + } +} \ No newline at end of file diff --git a/deployments/superbridge/surge_aavegotchi_gotchis_bridge_testnet_verification.json b/deployments/superbridge/surge_aavegotchi_gotchis_bridge_testnet_verification.json new file mode 100644 index 00000000..ed4ebc45 --- /dev/null +++ b/deployments/superbridge/surge_aavegotchi_gotchis_bridge_testnet_verification.json @@ -0,0 +1,46 @@ +{ + "84532": [ + [ + "0x8Ca0b8D5a04F902171113A4Ac76a8f1F52FCDF6f", + "ConnectorPlug", + "contracts/ConnectorPlug.sol", + [ + "0x92641fDD13A1B2099675fd1F3798fa0d9e6CDAfa", + "0x5C4186D343EeF952c9ED886E45F8243edf0A503F", + 631571, + "0x0000000000000000000000000000000000000000000000000000000000000000" + ] + ], + [ + "0x92641fDD13A1B2099675fd1F3798fa0d9e6CDAfa", + "NFTVault", + "contracts/bridge/NFT/NFTVault.sol", + [ + "0xf81FFb9E2a72574d3C4Cf4E293D4Fec4A708F2B1", + "0x80ac58cd" + ] + ] + ], + "631571": [ + [ + "0x8Dc7AED7a2A2729D762e63BaDecCd7D50474020B", + "ConnectorPlug", + "contracts/ConnectorPlug.sol", + [ + "0xde3c698E15806384C667b0478C1072867571f6cd", + "0x5C4186D343EeF952c9ED886E45F8243edf0A503F", + 84532, + "0x0000000000000000000000000000000000000000000000000000000000000000" + ] + ], + [ + "0xde3c698E15806384C667b0478C1072867571f6cd", + "NFTController", + "contracts/bridge/NFT/NFTController.sol", + [ + "0x226625C1B1174e7BaaE8cDC0432Db0e2ED83b7Ba", + "0x80ac58cd" + ] + ] + ] +} \ No newline at end of file diff --git a/deployments/superbridge/surge_aavegotchi_items_bridge_testnet_addresses.json b/deployments/superbridge/surge_aavegotchi_items_bridge_testnet_addresses.json new file mode 100644 index 00000000..cd0ca293 --- /dev/null +++ b/deployments/superbridge/surge_aavegotchi_items_bridge_testnet_addresses.json @@ -0,0 +1,26 @@ +{ + "84532": { + "GOTCHI_ITEM": { + "isAppChain": false, + "NonMintableToken": "0x87C969d083189927049f8fF3747703FB9f7a8AEd", + "Vault": "0x2709f098E8C641796B495bED28A34F9FEA858ac8", + "connectors": { + "631571": { + "FAST": "0xB2B948d421Ce7c0eC075a073682236269614D32d" + } + } + } + }, + "631571": { + "GOTCHI_ITEM": { + "isAppChain": true, + "MintableToken": "0x954B9F6DaB28F92c88192E2F52FDa5A6Df4A0334", + "Controller": "0x60d629c876E455eFdca83e2b4c85DfB9d4C3C58C", + "connectors": { + "84532": { + "FAST": "0xc14Bc9857A27b428b3eB84cB561412a42C9B9798" + } + } + } + } +} diff --git a/deployments/superbridge/surge_aavegotchi_items_bridge_testnet_verification.json b/deployments/superbridge/surge_aavegotchi_items_bridge_testnet_verification.json new file mode 100644 index 00000000..a7ec93d2 --- /dev/null +++ b/deployments/superbridge/surge_aavegotchi_items_bridge_testnet_verification.json @@ -0,0 +1,40 @@ +{ + "84532": [ + [ + "0xB2B948d421Ce7c0eC075a073682236269614D32d", + "ConnectorPlug", + "contracts/ConnectorPlug.sol", + [ + "0x2709f098E8C641796B495bED28A34F9FEA858ac8", + "0x5C4186D343EeF952c9ED886E45F8243edf0A503F", + 631571, + "0x0000000000000000000000000000000000000000000000000000000000000000" + ] + ], + [ + "0x2709f098E8C641796B495bED28A34F9FEA858ac8", + "NFTVault", + "contracts/bridge/NFT/NFTVault.sol", + ["0x87C969d083189927049f8fF3747703FB9f7a8AEd", "0xd9b67a26"] + ] + ], + "631571": [ + [ + "0xc14Bc9857A27b428b3eB84cB561412a42C9B9798", + "ConnectorPlug", + "contracts/ConnectorPlug.sol", + [ + "0x60d629c876E455eFdca83e2b4c85DfB9d4C3C58C", + "0x5C4186D343EeF952c9ED886E45F8243edf0A503F", + 84532, + "0x0000000000000000000000000000000000000000000000000000000000000000" + ] + ], + [ + "0x60d629c876E455eFdca83e2b4c85DfB9d4C3C58C", + "NFTController", + "contracts/bridge/NFT/NFTController.sol", + ["0x954B9F6DaB28F92c88192E2F52FDa5A6Df4A0334", "0xd9b67a26"] + ] + ] +} diff --git a/deployments/superbridge/surge_addresses.json b/deployments/superbridge/surge_addresses.json index 4feb2952..bf269032 100644 --- a/deployments/superbridge/surge_addresses.json +++ b/deployments/superbridge/surge_addresses.json @@ -26,5 +26,135 @@ } } } + }, + "aavegotchi_bridge_testnet": { + "84532": { + "GOTCHI": { + "isAppChain": false, + "NonMintableToken": "0x87C969d083189927049f8fF3747703FB9f7a8AEd", + "Vault": "0x110A646276961C2d8a54b951bbC8B169E0F573c4", + "connectors": { + "631571": { + "FAST": "0xd912F40C27E317db2334e210de892e9dc92816af" + } + } + } + }, + "631571": { + "GOTCHI": { + "isAppChain": true, + "MintableToken": "0x1F0eb9099b9c398323dcf2F133dFdAD9dE7cF994", + "Controller": "0x5ABB7E28160f82A84e389aDcc9d8CE3F7a0C8D92", + "connectors": { + "84532": { + "FAST": "0xE7af5160334aded39DD9826cBcBa0B51A1B184e9" + } + } + } + } + }, + "aavegotchi_item_bridge_testnet": { + "84532": { + "GOTCHI_ITEM": { + "isAppChain": false, + "NonMintableToken": "0x87C969d083189927049f8fF3747703FB9f7a8AEd", + "Vault": "0x130119B300049A80C20B2D3bfdFCfd021373E5e7", + "connectors": { + "631571": { + "FAST": "0xb8388b23222876FAC04b464fA0d6A064c67A14FC" + } + } + } + }, + "631571": { + "GOTCHI_ITEM": { + "isAppChain": true, + "MintableToken": "0x1F0eb9099b9c398323dcf2F133dFdAD9dE7cF994", + "Controller": "0x10Cf0D5C1986a7Aa98aDb3bfa3529c1BBDa59FB9", + "connectors": { + "84532": { + "FAST": "0x27fA28c1f241E5dEA9AA583751E5D968a28FD9D5" + } + } + } + } + }, + "aavegotchi_gotchi_bridge_testnet": { + "84532": { + "GOTCHI": { + "isAppChain": false, + "NonMintableToken": "0x87C969d083189927049f8fF3747703FB9f7a8AEd", + "Vault": "0xEccF8B72c6A354532F27053e54A5b4b912D1e6D6", + "connectors": { + "631571": { + "FAST": "0x3C43820A77d3Ff7Df81f212851857c46684f8b2F" + } + } + } + }, + "631571": { + "GOTCHI": { + "isAppChain": true, + "MintableToken": "0xD66C6C61D5a535eD69E3769582895daec28a4651", + "Controller": "0x143B8D0e2b6d7791F571A68bf07da2253C0d52CB", + "connectors": { + "84532": { + "FAST": "0x3A643fc25C721971314119a50a7fdF18385b7eD9" + } + } + } + } + }, + "aavegotchi_items_bridge_testnet": { + "84532": { + "GOTCHI_ITEM": { + "isAppChain": false, + "NonMintableToken": "0x87C969d083189927049f8fF3747703FB9f7a8AEd", + "Vault": "0x2709f098E8C641796B495bED28A34F9FEA858ac8", + "connectors": { + "631571": { + "FAST": "0xB2B948d421Ce7c0eC075a073682236269614D32d" + } + } + } + }, + "631571": { + "GOTCHI_ITEM": { + "isAppChain": true, + "MintableToken": "0x954B9F6DaB28F92c88192E2F52FDa5A6Df4A0334", + "Controller": "0x60d629c876E455eFdca83e2b4c85DfB9d4C3C58C", + "connectors": { + "84532": { + "FAST": "0xc14Bc9857A27b428b3eB84cB561412a42C9B9798" + } + } + } + } + }, + "aavegotchi_gotchis_bridge_testnet": { + "84532": { + "GOTCHI": { + "isAppChain": false, + "NonMintableToken": "0xf81FFb9E2a72574d3C4Cf4E293D4Fec4A708F2B1", + "Vault": "0x92641fDD13A1B2099675fd1F3798fa0d9e6CDAfa", + "connectors": { + "631571": { + "FAST": "0x8Ca0b8D5a04F902171113A4Ac76a8f1F52FCDF6f" + } + } + } + }, + "631571": { + "GOTCHI": { + "isAppChain": true, + "MintableToken": "0x226625C1B1174e7BaaE8cDC0432Db0e2ED83b7Ba", + "Controller": "0xde3c698E15806384C667b0478C1072867571f6cd", + "connectors": { + "84532": { + "FAST": "0x8Dc7AED7a2A2729D762e63BaDecCd7D50474020B" + } + } + } + } } -} +} \ No newline at end of file diff --git a/package.json b/package.json index 9951e36e..f9c4d9bc 100644 --- a/package.json +++ b/package.json @@ -54,8 +54,11 @@ "script:bridge": "npx ts-node script/bridge/bridge.ts", "project:new": "npx ts-node script/script.ts new", "project:edit": "npx ts-node script/script.ts edit", - "project:addToken": "npx ts-node script/script.ts add_token", - "bridge": "npx hardhat run script/bridge/bridge.ts" + "project:addToken": "npx ts-node script/script.ts addToken", + "project:addNFT": "npx ts-node script/script.ts addNFT", + "bridge": "npx hardhat run script/bridge/bridge.ts", + "bridgeERC721": "npx hardhat run script/bridge/bridgeERC721.ts", + "bridgeERC1155": "npx hardhat run script/bridge/bridgeERC1155.ts" }, "pre-commit": [ "lint", diff --git a/script/admin/updateConnectorStatus.ts b/script/admin/updateConnectorStatus.ts index cefea896..98e73941 100644 --- a/script/admin/updateConnectorStatus.ts +++ b/script/admin/updateConnectorStatus.ts @@ -25,7 +25,7 @@ export enum ConnectorStatus { let projectType: ProjectType; let pc: { [token: string]: TokenConstants } = {}; let projectName: string; -let tokens: Tokens[]; +let tokens: string[]; export const main = async () => { try { diff --git a/script/admin/updateLimits.ts b/script/admin/updateLimits.ts index 7e4dd333..1b875f5e 100644 --- a/script/admin/updateLimits.ts +++ b/script/admin/updateLimits.ts @@ -22,7 +22,7 @@ import { import { Tokens } from "../../src/enums"; let pc: { [token: string]: TokenConstants } = {}; -let tokens: Tokens[]; +let tokens: string[]; export const main = async () => { try { @@ -75,7 +75,7 @@ export const main = async () => { ) { await updateLimitsAndPoolId( chain, - token, + token as Tokens, siblingSlugs, addr, connectors, diff --git a/script/bridge/bridgeERC1155.ts b/script/bridge/bridgeERC1155.ts new file mode 100644 index 00000000..9b262c6a --- /dev/null +++ b/script/bridge/bridgeERC1155.ts @@ -0,0 +1,185 @@ +import { BigNumber, utils } from "ethers"; + +import { ChainSlug } from "@socket.tech/dl-core"; +import yargs from "yargs"; +import { + SBAddresses, + SBTokenAddresses, + STAddresses, + STTokenAddresses, +} from "../../src"; +import { Tokens, tokenDecimals } from "../../src/enums"; +import { getProjectAddresses } from "../helpers"; +import { getBridgeContract, getTokenContract } from "../helpers/common"; +import { getOverrides, getSignerFromChainSlug } from "../helpers/networks"; +import { checkSendingLimit, getDLAPIBaseUrl } from "./utils"; + +const gasLimit = 500_000; + +export const main = async () => { + try { + // const argv = await yargs + // .option({ + // srcChain: { + // description: "srcChainSlug", + // type: "string", + // demandOption: true, + // }, + // }) + // .option({ + // dstChain: { + // description: "dstChainSlug", + // type: "string", + // demandOption: true, + // }, + // }) + // .option({ + // amount: { + // description: "token amount to bridge (formatted value)", + // type: "string", + // demandOption: true, + // }, + // }) + // .option({ + // tokenId: { + // description: "tokenId", + // type: "string", + // demandOption: true, + // }, + // }) + // .option({ + // token: { + // description: "token", + // type: "string", + // demandOption: true, + // }, + // }).argv; + + const srcChain = 63157 as ChainSlug; //Number(argv.srcChain) as ChainSlug; + const dstChain = 137 as ChainSlug; //Number(argv.dstChain) as ChainSlug; + const amount = 1; + const token = "GOTCHI_ITEM" as Tokens; + const tokenId = "228"; //argv.tokenId; + + // if (!Object.values(Tokens).includes(token)) + // throw Error("token not allowed"); + + const amountBN = utils.parseUnits(amount.toString(), tokenDecimals[token]); + + let addresses: SBAddresses | STAddresses | undefined = + getProjectAddresses(); + + console.log("addresses:", addresses); + + const srcAddresses: SBTokenAddresses | STTokenAddresses | undefined = + addresses[srcChain]?.[token]; + + console.log("srcAddresses:", srcAddresses); + + const dstAddresses: SBTokenAddresses | STTokenAddresses | undefined = + addresses[dstChain]?.[token]; + + console.log("dstAddresses:", dstAddresses); + if (!srcAddresses || !dstAddresses) + throw new Error("chain addresses not found"); + + const bridgeContract = await getBridgeContract( + srcChain, + token, + srcAddresses, + "ERC1155" + ); + const tokenContract = await getTokenContract( + srcChain, + token, + srcAddresses, + "ERC1155" + ); + const connectorAddr = srcAddresses.connectors?.[dstChain]?.FAST; + + if (!connectorAddr) throw new Error("connector contract addresses missing"); + + const socketSigner = getSignerFromChainSlug(srcChain); + + console.log("checking balance and approval..."); + // approve + const balance: BigNumber = await tokenContract.balanceOf( + socketSigner.address, + tokenId + ); + + console.log("balance:", balance); + + // if (balance.lt(amountBN)) throw new Error("Not enough balance"); + + const currentApproval: boolean = await tokenContract.isApprovedForAll( + socketSigner.address, + bridgeContract.address + ); + + console.log("currentApproval:", currentApproval); + + if (!currentApproval) { + const approveTx = await tokenContract.setApprovalForAll( + bridgeContract.address, + true, + { + ...getOverrides(srcChain), + } + ); + console.log("Tokens approved: ", approveTx.hash); + await approveTx.wait(); + } + + console.log("checking sending limit..."); + await checkSendingLimit( + srcChain, + token, + srcAddresses, + connectorAddr, + amountBN + ); + + // deposit + console.log(`depositing ${amount} to ${dstChain} from ${srcChain}`); + const fees = await bridgeContract.getMinFees(connectorAddr, gasLimit, 0); + + // address receiver_, + // address tokenOwner_, + // uint256 tokenId_, + // uint256 amount_, + // uint256 msgGasLimit_, + // address connector_, + + console.log("address:", socketSigner.address); + + const depositTx = await bridgeContract.bridge( + "0xC3c2e1Cf099Bc6e1fA94ce358562BCbD5cc59FE5", + "0xC3c2e1Cf099Bc6e1fA94ce358562BCbD5cc59FE5", + tokenId, + amount, + gasLimit, + connectorAddr, + "0x", + "0x", + + { ...getOverrides(srcChain), value: fees } + ); + console.log("Tokens deposited: ", depositTx.hash); + console.log( + `Track message here: ${getDLAPIBaseUrl()}/messages-from-tx?srcChainSlug=${srcChain}&srcTxHash=${ + depositTx.hash + }` + ); + await depositTx.wait(); + } catch (error) { + console.log("Error while sending transaction", error); + } +}; + +main() + .then(() => process.exit(0)) + .catch((error: Error) => { + console.error(error); + process.exit(1); + }); diff --git a/script/bridge/bridgeERC721.ts b/script/bridge/bridgeERC721.ts new file mode 100644 index 00000000..1b093f95 --- /dev/null +++ b/script/bridge/bridgeERC721.ts @@ -0,0 +1,204 @@ +import { BigNumber, utils } from "ethers"; +import { ethers } from "hardhat"; + +import { ChainSlug } from "@socket.tech/dl-core"; +import { + SBAddresses, + SBTokenAddresses, + STAddresses, + STTokenAddresses, +} from "../../src"; +import { Tokens, tokenDecimals } from "../../src/enums"; +import { getProjectAddresses } from "../helpers"; +import { getBridgeContract, getTokenContract } from "../helpers/common"; +import { getOverrides, getSignerFromChainSlug } from "../helpers/networks"; +import { checkSendingLimit, getDLAPIBaseUrl } from "./utils"; +import { network } from "hardhat"; + +const gasLimit = 500_000; + +export const main = async () => { + try { + // const argv = await yargs + // .option({ + // srcChain: { + // description: "srcChainSlug", + // type: "string", + // demandOption: true, + // }, + // }) + // .option({ + // dstChain: { + // description: "dstChainSlug", + // type: "string", + // demandOption: true, + // }, + // }) + // .option({ + // amount: { + // description: "token amount to bridge (formatted value)", + // type: "string", + // demandOption: true, + // }, + // }) + // .option({ + // tokenId: { + // description: "tokenId", + // type: "string", + // demandOption: true, + // }, + // }) + // .option({ + // token: { + // description: "token", + // type: "string", + // demandOption: true, + // }, + // }).argv; + + const srcChain = 137 as ChainSlug; //Number(argv.srcChain) as ChainSlug; + const dstChain = 63157 as ChainSlug; //Number(argv.dstChain) as ChainSlug; + const amount = 1; + const token = "GOTCHI" as Tokens; + const tokenId = "6018"; //argv.tokenId; + + console.log("network:", network.name); + + if (network.name !== "hardhat") { + throw new Error("script can only be run on hardhat network"); + } + + // if (!Object.values(Tokens).includes(token)) + // throw Error("token not allowed"); + + const amountBN = utils.parseUnits(amount.toString(), tokenDecimals[token]); + + let addresses: SBAddresses | STAddresses | undefined = + getProjectAddresses(); + + console.log("addresses:", addresses); + + const srcAddresses: SBTokenAddresses | STTokenAddresses | undefined = + addresses[srcChain]?.[token]; + + console.log("srcAddresses:", srcAddresses); + + const dstAddresses: SBTokenAddresses | STTokenAddresses | undefined = + addresses[dstChain]?.[token]; + + console.log("dstAddresses:", dstAddresses); + if (!srcAddresses || !dstAddresses) + throw new Error("chain addresses not found"); + + const bridgeContract = await getBridgeContract( + srcChain, + token, + srcAddresses, + "ERC721" + ); + const tokenContract = await getTokenContract( + srcChain, + token, + srcAddresses, + "ERC721" + ); + const connectorAddr = srcAddresses.connectors?.[dstChain]?.FAST; + + if (!connectorAddr) throw new Error("connector contract addresses missing"); + + let socketSigner; + if (network.name === "hardhat") { + socketSigner = await ethers.getImpersonatedSigner( + "0xC3c2e1Cf099Bc6e1fA94ce358562BCbD5cc59FE5" + ); + } else { + socketSigner = getSignerFromChainSlug(srcChain); + } + + console.log( + "checking balance and approval of account:", + socketSigner.address + ); + // approve + const balance: BigNumber = await tokenContract.balanceOf( + socketSigner.address + ); + + console.log("balance:", balance); + + if (balance.eq(0)) throw new Error("Not enough balance"); + + // if (balance.lt(amountBN)) throw new Error("Not enough balance"); + + const currentApproval: boolean = await tokenContract.isApprovedForAll( + socketSigner.address, + bridgeContract.address + ); + if (!currentApproval) { + const approveTx = await tokenContract.setApprovalForAll( + bridgeContract.address, + true, + { + ...getOverrides(srcChain), + } + ); + console.log("Tokens approved: ", approveTx.hash); + await approveTx.wait(); + } + + console.log("checking sending limit..."); + await checkSendingLimit( + srcChain, + token, + srcAddresses, + connectorAddr, + amountBN + ); + + // deposit + console.log(`depositing ${amount} to ${dstChain} from ${srcChain}`); + const fees = await bridgeContract.getMinFees(connectorAddr, gasLimit, 0); + + console.log("fees:", fees); + + return; + + // address receiver_, + // address tokenOwner_, + // uint256 tokenId_, + // uint256 amount_, + // uint256 msgGasLimit_, + // address connector_, + + console.log("address:", socketSigner.address); + + const depositTx = await bridgeContract.bridge( + "0xC3c2e1Cf099Bc6e1fA94ce358562BCbD5cc59FE5", + "0xC3c2e1Cf099Bc6e1fA94ce358562BCbD5cc59FE5", + tokenId, + amount, + gasLimit, + connectorAddr, + "0x", + "0x", + + { ...getOverrides(srcChain), value: fees } + ); + console.log("Tokens deposited: ", depositTx.hash); + console.log( + `Track message here: ${getDLAPIBaseUrl()}/messages-from-tx?srcChainSlug=${srcChain}&srcTxHash=${ + depositTx.hash + }` + ); + await depositTx.wait(); + } catch (error) { + console.log("Error while sending transaction", error); + } +}; + +main() + .then(() => process.exit(0)) + .catch((error: Error) => { + console.error(error); + process.exit(1); + }); diff --git a/script/constants/config.ts b/script/constants/config.ts index 5f85871c..a7a52221 100644 --- a/script/constants/config.ts +++ b/script/constants/config.ts @@ -1,11 +1,12 @@ import { config as dotenvConfig } from "dotenv"; -dotenvConfig(); - import { DeploymentMode } from "@socket.tech/dl-core"; -import { Project, Tokens } from "../../src/enums"; -import { ProjectType } from "../../src"; +import { NFTs, Project, Tokens } from "../../src/enums"; +import { ProjectType, TokenType } from "../../src"; import { ProjectTypeMap } from "../../src/enums/projectType"; import { ProdTestnetProjects } from "../../src/enums/prodTestnetProjects"; + +dotenvConfig(); + // import { Project, ProjectType, Tokens } from "../../src"; export const getOwner = () => { @@ -73,16 +74,24 @@ export const isSuperToken = () => getProjectType() === ProjectType.SUPERTOKEN; export const getTokens = () => { if (!process.env.TOKENS) throw new Error("TOKENS not mentioned"); - let tokens = process.env.TOKENS.split(",").map( - (token) => token.trim() as Tokens - ); + let tokens = process.env.TOKENS.split(",").map((token) => token.trim()); tokens.forEach((token) => { - if (!Object.values(Tokens).includes(token as Tokens)) + if ( + !Object.values(Tokens).includes(token as Tokens) && + !Object.values(NFTs).includes(token as NFTs) + ) throw new Error("TOKENS are invalid"); }); return tokens; }; +export const getTokenTypes = () => { + if (!process.env.TOKEN_TYPES) throw new Error("TOKEN_TYPES not mentioned"); + return process.env.TOKEN_TYPES.split(",").map( + (tokenType) => tokenType.trim() as TokenType + ); +}; + export const getDryRun = () => { if (!process.env.DRY_RUN) return false; if (process.env.DRY_RUN !== "true" && process.env.DRY_RUN !== "false") @@ -94,17 +103,20 @@ export const getConfigs = () => { let projectType = getProjectType(); let projectName = getProjectName(); let tokens = getTokens(); + let tokenTypes = getTokenTypes(); let mode = getMode(); let socketOwner = getOwner(); - return { projectType, projectName, tokens, mode, socketOwner }; + return { projectType, projectName, tokens, tokenTypes, mode, socketOwner }; }; export const printConfigs = () => { - let { projectType, projectName, tokens, mode, socketOwner } = getConfigs(); + let { projectType, projectName, tokens, tokenTypes, mode, socketOwner } = + getConfigs(); console.log("========================================================"); console.log("PROJECT", projectName); console.log("PROJECT_TYPE", projectType); console.log("TOKENS", tokens); + console.log("TOKEN_TYPES", tokenTypes); console.log("MODE", mode); console.log( `Make sure ${mode}_${projectName}_addresses.json and ${mode}_${projectName}_verification.json is cleared for given networks if redeploying!!` diff --git a/script/constants/deploymentConfig.ts b/script/constants/deploymentConfig.ts index 2a624e99..88e3c485 100644 --- a/script/constants/deploymentConfig.ts +++ b/script/constants/deploymentConfig.ts @@ -54,6 +54,8 @@ export const getAddresses = (chainSlug: number, mode: DeploymentMode) => { }; export const getChainName = (chainSlug: number) => { + if (chainSlug === 84532) return "BASE_SEPOLIA"; + let chainName = chainSlugReverseMap.get(String(chainSlug)) ?? chains[chainSlug].chainName; return chainName.toUpperCase().replace(/[\s-]/g, "_"); // convert to uppercase, replace space and - with _ diff --git a/script/constants/projectConstants/superbridge/aavegotchi_gotchi_bridge_mainnet.ts b/script/constants/projectConstants/superbridge/aavegotchi_gotchi_bridge_mainnet.ts new file mode 100644 index 00000000..1cc3f00b --- /dev/null +++ b/script/constants/projectConstants/superbridge/aavegotchi_gotchi_bridge_mainnet.ts @@ -0,0 +1,25 @@ +import { + ChainSlug, + DeploymentMode, + IntegrationTypes, +} from "@socket.tech/dl-core"; +import { Hooks, ProjectConstants } from "../../../../src"; +import { NFTs, Tokens } from "../../../../src/enums"; + +// For testnet deployments, ChainSlug enum may not have some chains, therefore some keys will look like {421614:{}} instead of {[ChainSlug.ARBITRUM_SEPOLIA]:{}}. This wont affect the functionality of the project. +export const pc: ProjectConstants = { + [DeploymentMode.PROD]: { + [NFTs.GOTCHI]: { + vaultChains: [ChainSlug.POLYGON_MAINNET], + controllerChains: [ChainSlug.GEIST], + tokenAddresses: { + [ChainSlug.POLYGON_MAINNET]: + "0x86935F11C86623deC8a25696E1C19a8659CbF95d", + [ChainSlug.GEIST]: "0x6Acc828BbbC6874de40Ca20bfeA7Cd2a2DA8DA8c", + }, + hook: { + hookType: Hooks.NO_HOOK, + }, + }, + }, +}; diff --git a/script/constants/projectConstants/superbridge/aavegotchi_gotchis_bridge_testnet.ts b/script/constants/projectConstants/superbridge/aavegotchi_gotchis_bridge_testnet.ts new file mode 100644 index 00000000..3fa3c6b4 --- /dev/null +++ b/script/constants/projectConstants/superbridge/aavegotchi_gotchis_bridge_testnet.ts @@ -0,0 +1,25 @@ + +import { + ChainSlug, + DeploymentMode, + IntegrationTypes, +} from "@socket.tech/dl-core"; +import { Hooks, ProjectConstants } from "../../../../src"; +import { NFTs, Tokens } from "../../../../src/enums"; + +// For testnet deployments, ChainSlug enum may not have some chains, therefore some keys will look like {421614:{}} instead of {[ChainSlug.ARBITRUM_SEPOLIA]:{}}. This wont affect the functionality of the project. +export const pc: ProjectConstants = { + [DeploymentMode.SURGE]: { + [NFTs.GOTCHI]: { + vaultChains: [84532], + controllerChains: [631571], + tokenAddresses: { + 84532: "0xf81FFb9E2a72574d3C4Cf4E293D4Fec4A708F2B1", + 631571: "0x226625C1B1174e7BaaE8cDC0432Db0e2ED83b7Ba" + }, + hook: { + hookType: Hooks.NO_HOOK + } + } + } +}; diff --git a/script/constants/projectConstants/superbridge/aavegotchi_items_bridge_mainnet.ts b/script/constants/projectConstants/superbridge/aavegotchi_items_bridge_mainnet.ts new file mode 100644 index 00000000..50914263 --- /dev/null +++ b/script/constants/projectConstants/superbridge/aavegotchi_items_bridge_mainnet.ts @@ -0,0 +1,25 @@ +import { + ChainSlug, + DeploymentMode, + IntegrationTypes, +} from "@socket.tech/dl-core"; +import { Hooks, ProjectConstants } from "../../../../src"; +import { NFTs, Tokens } from "../../../../src/enums"; + +// For testnet deployments, ChainSlug enum may not have some chains, therefore some keys will look like {421614:{}} instead of {[ChainSlug.ARBITRUM_SEPOLIA]:{}}. This wont affect the functionality of the project. +export const pc: ProjectConstants = { + [DeploymentMode.PROD]: { + [NFTs.GOTCHI_ITEM]: { + vaultChains: [ChainSlug.POLYGON_MAINNET], + controllerChains: [ChainSlug.GEIST], + tokenAddresses: { + [ChainSlug.POLYGON_MAINNET]: + "0x58de9AaBCaeEC0f69883C94318810ad79Cc6a44f", + [ChainSlug.GEIST]: "0xaC336aB3CFC58698B582205A861A5C6B798c01B9", + }, + hook: { + hookType: Hooks.NO_HOOK, + }, + }, + }, +}; diff --git a/script/constants/projectConstants/superbridge/aavegotchi_items_bridge_testnet.ts b/script/constants/projectConstants/superbridge/aavegotchi_items_bridge_testnet.ts new file mode 100644 index 00000000..0077c255 --- /dev/null +++ b/script/constants/projectConstants/superbridge/aavegotchi_items_bridge_testnet.ts @@ -0,0 +1,24 @@ +import { + ChainSlug, + DeploymentMode, + IntegrationTypes, +} from "@socket.tech/dl-core"; +import { Hooks, ProjectConstants } from "../../../../src"; +import { NFTs, Tokens } from "../../../../src/enums"; + +// For testnet deployments, ChainSlug enum may not have some chains, therefore some keys will look like {421614:{}} instead of {[ChainSlug.ARBITRUM_SEPOLIA]:{}}. This wont affect the functionality of the project. +export const pc: ProjectConstants = { + [DeploymentMode.SURGE]: { + [NFTs.GOTCHI_ITEM]: { + vaultChains: [84532], + controllerChains: [631571], + tokenAddresses: { + 84532: "0x87C969d083189927049f8fF3747703FB9f7a8AEd", + 631571: "0x954B9F6DaB28F92c88192E2F52FDa5A6Df4A0334", + }, + hook: { + hookType: Hooks.NO_HOOK, + }, + }, + }, +}; diff --git a/script/deploy/configure.ts b/script/deploy/configure.ts index 5680b421..07a4b391 100644 --- a/script/deploy/configure.ts +++ b/script/deploy/configure.ts @@ -43,7 +43,7 @@ import { getAddresses } from "../constants"; let projectType: ProjectType; let pc: { [token: string]: TokenConstants } = {}; let projectName: string; -let tokens: Tokens[]; +let tokens: string[]; let socketSignerAddress: string; @@ -126,7 +126,7 @@ export const configure = async (allAddresses: SBAddresses | STAddresses) => { await configureHooks( chain, - token, + token as Tokens, bridgeContract, socketSigner, siblingSlugs, @@ -161,7 +161,7 @@ const connect = async ( addr: SBTokenAddresses | STTokenAddresses, addresses: SBAddresses | STAddresses, chain: ChainSlug, - token: Tokens, + token: string, siblingSlugs: ChainSlug[], socketSigner: Wallet ) => { diff --git a/script/deploy/deploy.ts b/script/deploy/deploy.ts index e6730df4..1466c1e4 100644 --- a/script/deploy/deploy.ts +++ b/script/deploy/deploy.ts @@ -1,19 +1,15 @@ import { config as dotenvConfig } from "dotenv"; -dotenvConfig(); - import { EventEmitter } from "events"; -EventEmitter.defaultMaxListeners = 20; - -import { Contract, Wallet } from "ethers"; +import { constants, Contract, Wallet } from "ethers"; import { getSignerFromChainSlug } from "../helpers/networks"; import { ChainSlug, IntegrationTypes } from "@socket.tech/dl-core"; import { + getConfigs, + getDryRun, getMode, isSuperBridge, isSuperToken, - getConfigs, printConfigs, - getDryRun, } from "../constants/config"; import { createObj, @@ -24,47 +20,54 @@ import { storeTokenAddresses, } from "../helpers"; import { - SuperBridgeContracts, + CommonContracts, + DeployParams, Hooks, ProjectType, - TokenContracts, - CommonContracts, - SuperTokenContracts, - TokenConstants, - STTokenAddresses, - SBTokenAddresses, + ReturnObj, SBAddresses, + SBTokenAddresses, STAddresses, - DeployParams, - ReturnObj, + STTokenAddresses, + SuperBridgeContracts, + SuperTokenContracts, + TokenConstants, + TokenContracts, + TokenType, } from "../../src"; -import { isSBAppChain, getTokenConstants } from "../helpers/projectConstants"; +import { getTokenConstants, isSBAppChain } from "../helpers/projectConstants"; import { ExistingTokenAddresses } from "../../src/enums/existing-token-addresses"; import { deployHookContracts } from "./deployHook"; import { verifyConstants } from "../helpers/verifyConstants"; import { getBridgeContract } from "../helpers/common"; -import { Project, Tokens } from "../../src/enums"; +import { NFTs, Project, Tokens } from "../../src/enums"; import { parseUnits } from "ethers/lib/utils"; - -import { constants } from "ethers"; import { getAddresses } from "../constants"; + +dotenvConfig(); + +EventEmitter.defaultMaxListeners = 20; + const { AddressZero } = constants; let projectType: ProjectType; let pc: { [token: string]: TokenConstants } = {}; let projectName: string; -let tokens: Tokens[]; +let tokens: string[]; +let tokenTypes: TokenType[]; /** * Deploys contracts for all networks */ export const deploy = async () => { await verifyConstants(); - ({ projectName, projectType, tokens } = getConfigs()); + ({ projectName, projectType, tokens, tokenTypes } = getConfigs()); printConfigs(); let allAddresses: SBAddresses | STAddresses = {}; - for (let token of tokens) { + for (let index = 0; index < tokens.length; index++) { + const token = tokens[index]; + const tokenType = tokenTypes[index]; console.log(`Deploying contracts for ${token}...`); pc[token] = getTokenConstants(token); @@ -109,6 +112,7 @@ export const deploy = async () => { signer, chain, token, + tokenType, siblings, hookType, tokenAddresses, @@ -134,7 +138,8 @@ const deployChainContracts = async ( isVaultChain: boolean, socketSigner: Wallet, chainSlug: number, - token: Tokens, + token: string, + tokenType: TokenType, siblings: number[], hookType: Hooks, deployedAddresses: SBTokenAddresses | STTokenAddresses, @@ -148,6 +153,7 @@ const deployChainContracts = async ( signer: socketSigner, currentChainSlug: chainSlug, currentToken: token, + currentTokenType: tokenType, hookType, mergeInboundWithTokens: tc.mergeInboundWithTokens ?? [], tc, @@ -281,7 +287,8 @@ export const deployControllerChainContracts = async ( controller: Contract, contractName: string = "", controllerAddress: string = "", - contractPath: string = ""; + contractPath: string = "", + contractArgs: any[]; if (isSuperToken()) { deployParams = await deploySuperToken(deployParams); @@ -308,13 +315,26 @@ export const deployControllerChainContracts = async ( deployParams.addresses[SuperBridgeContracts.MintableToken] = mintableToken; - contractName = deployParams.tc.isFiatTokenV2_1 - ? SuperBridgeContracts.FiatTokenV2_1_Controller - : SuperBridgeContracts.Controller; - contractPath = deployParams.tc.isFiatTokenV2_1 - ? "contracts/bridge/FiatTokenV2_1/FiatTokenV2_1_Controller.sol" - : "contracts/bridge/Controller.sol"; + contractName = + deployParams.currentTokenType === TokenType.ERC20 + ? deployParams.tc.isFiatTokenV2_1 + ? SuperBridgeContracts.FiatTokenV2_1_Controller + : SuperBridgeContracts.Controller + : SuperBridgeContracts.NFTController; + contractPath = + deployParams.currentTokenType === TokenType.ERC20 + ? deployParams.tc.isFiatTokenV2_1 + ? "contracts/bridge/FiatTokenV2_1/FiatTokenV2_1_Controller.sol" + : "contracts/bridge/Controller.sol" + : "contracts/bridge/NFT/NFTController.sol"; + contractArgs = + deployParams.currentTokenType === TokenType.ERC721 + ? [mintableToken, "0x80ac58cd"] + : deployParams.currentTokenType === TokenType.ERC1155 + ? [mintableToken, "0xd9b67a26"] + : [mintableToken]; } + if (!contractArgs) contractArgs = [mintableToken]; // If controller address is already in addresses object, skip // If mergeInboundWithTokens is provided, pick the first token's controller address which is present. @@ -345,7 +365,7 @@ export const deployControllerChainContracts = async ( controller = await getOrDeploy( contractName, contractPath, - [mintableToken], + contractArgs, deployParams ); @@ -389,10 +409,24 @@ export const deployVaultChainContracts = async ( deployParams.addresses[SuperBridgeContracts.NonMintableToken] = nonMintableToken; + const contractName = + deployParams.currentTokenType === TokenType.ERC20 + ? SuperBridgeContracts.Vault + : SuperBridgeContracts.NFTVault; + const contractPath = + deployParams.currentTokenType === TokenType.ERC20 + ? "contracts/bridge/Vault.sol" + : "contracts/bridge/NFT/NFTVault.sol"; + const contractArgs = + deployParams.currentTokenType === TokenType.ERC721 + ? [nonMintableToken, "0x80ac58cd"] + : deployParams.currentTokenType === TokenType.ERC1155 + ? [nonMintableToken, "0xd9b67a26"] + : [nonMintableToken]; const vault: Contract = await getOrDeploy( - SuperBridgeContracts.Vault, - "contracts/bridge/Vault.sol", - [nonMintableToken], + contractName, + contractPath, + contractArgs, deployParams ); diff --git a/script/helpers/common.ts b/script/helpers/common.ts index 62b0c777..01c961bb 100644 --- a/script/helpers/common.ts +++ b/script/helpers/common.ts @@ -81,22 +81,37 @@ export const updateConnectorStatus = async ( export const getBridgeContract = async ( chain: ChainSlug, - token: Tokens, - addr: SBTokenAddresses | STTokenAddresses + token: string, + addr: SBTokenAddresses | STTokenAddresses, + tokenType?: "ERC20" | "ERC721" | "ERC1155" ) => { const socketSigner = getSignerFromChainSlug(chain); let bridgeContract: Contract, bridgeAddress: string = "", bridgeContractName: string = ""; + + if (!tokenType) tokenType = "ERC20"; + if (isSuperBridge()) { if (isSBAppChain(chain, token)) { const a = addr as AppChainAddresses; bridgeAddress = a.Controller; - bridgeContractName = SuperBridgeContracts.Controller; + + if (["ERC721", "ERC1155"].includes(tokenType)) { + bridgeContractName = SuperBridgeContracts.NFTController; + } else { + bridgeContractName = SuperBridgeContracts.Controller; + } } else { const a = addr as NonAppChainAddresses; bridgeAddress = a.Vault; - bridgeContractName = SuperBridgeContracts.Vault; + + if (["ERC721", "ERC1155"].includes(tokenType)) { + console.log("use nft vault"); + bridgeContractName = SuperBridgeContracts.NFTVault; + } else { + bridgeContractName = SuperBridgeContracts.Vault; + } } } if (isSuperToken()) { @@ -124,12 +139,13 @@ export const getBridgeContract = async ( export const getTokenContract = async ( chain: ChainSlug, token: Tokens, - addr: SBTokenAddresses | STTokenAddresses + addr: SBTokenAddresses | STTokenAddresses, + tokenType?: "ERC20" | "ERC721" | "ERC1155" ) => { const socketSigner = getSignerFromChainSlug(chain); let tokenContract: Contract, tokenAddress: string = "", - tokenContractName: string = "ERC20"; + tokenContractName: string = tokenType ? tokenType : "ERC20"; if (isSuperBridge()) { if (isSBAppChain(chain, token)) { const a = addr as AppChainAddresses; @@ -161,7 +177,7 @@ export const getTokenContract = async ( export const getHookContract = async ( chain: ChainSlug, - token: Tokens, + token: string, addr: SBTokenAddresses | STTokenAddresses ) => { const socketSigner = getSignerFromChainSlug(chain); diff --git a/script/helpers/deployUtils.ts b/script/helpers/deployUtils.ts index a3296604..dc332c7e 100644 --- a/script/helpers/deployUtils.ts +++ b/script/helpers/deployUtils.ts @@ -208,7 +208,7 @@ export const getSocket = (chain: ChainSlug, signer: Wallet): Contract => { export const storeTokenAddresses = async ( addresses: SBTokenAddresses, chainSlug: ChainSlug, - tokenName: Tokens + tokenName: string ) => { if (getDryRun()) return; diff --git a/script/helpers/networks.ts b/script/helpers/networks.ts index 60b8e6a4..e9625355 100644 --- a/script/helpers/networks.ts +++ b/script/helpers/networks.ts @@ -93,6 +93,7 @@ export const overrides: { // gasLimit: 5_000_000, // gasPrice, }, + [ChainSlug.REYA_CRONOS]: { type: 1, // gasLimit, @@ -108,6 +109,11 @@ export const overrides: { gasLimit: 1_000_000_000, gasPrice: 100_000, }, + 84532: { + type: 1, + gasLimit: 20_000_000, + gasPrice, + }, }; export const getOverrides = (chainSlug: ChainSlug): Overrides => { diff --git a/script/helpers/verifyConstants.ts b/script/helpers/verifyConstants.ts index 6c226c20..194f2a32 100644 --- a/script/helpers/verifyConstants.ts +++ b/script/helpers/verifyConstants.ts @@ -11,7 +11,7 @@ import { Tokens } from "../../src/enums"; let projectType: ProjectType; let pc: { [token: string]: TokenConstants } = {}; let projectName: string; -let tokens: Tokens[]; +let tokens: string[]; export const verifyConstants = async () => { ({ projectName, projectType, tokens } = getConfigs()); diff --git a/script/script.ts b/script/script.ts index e0302769..e43d2cd1 100755 --- a/script/script.ts +++ b/script/script.ts @@ -4,6 +4,7 @@ import { initDeploymentConfig } from "./constants"; import { addProject } from "./setup/newProject/main"; import { addNewToken } from "./setup/addNewToken"; import { editProject } from "./setup/editProject"; +import { addNewNFT } from "./setup/addNewNFT"; async function main() { const args = process.argv.slice(2); @@ -16,9 +17,12 @@ async function main() { case "edit": await editProject(); break; - case "add_token": + case "addToken": await addNewToken(); break; + case "addNFT": + await addNewNFT(); + break; default: console.log("Unknown command"); } diff --git a/script/setup/addNewNFT.ts b/script/setup/addNewNFT.ts new file mode 100644 index 00000000..6192aad5 --- /dev/null +++ b/script/setup/addNewNFT.ts @@ -0,0 +1,149 @@ +import prompts from "prompts"; +import { appendToEnvFile, updateNFTEnums } from "./configUtils"; +import { ChainSlug, MainnetIds, TestnetIds } from "@socket.tech/dl-core"; +import { ExistingNFTAddresses, NFTs } from "../../src/enums"; +import { generateNFTAddressesFile } from "./updateExistingNFTAddresses"; +import { + NewNFTInfo, + NFTType, + validateEmptyValue, + validateEthereumAddress, + validateRPC, +} from "./common"; +import { chainSlugReverseMap } from "./enumMaps"; +import { rpcKeys } from "../helpers"; + +export const addNewNFT = async () => { + let chainOptions = [...MainnetIds, ...TestnetIds].map((chain) => ({ + title: chainSlugReverseMap.get(String(chain)), + value: chain, + })); + let newNFTInfo: NewNFTInfo = await getNewNFTInfo(chainOptions); + console.log("newNFTInfo", newNFTInfo); + if (!newNFTInfo.name) return; + console.log("Adding new token: ", newNFTInfo); + if (!Object.keys(NFTs).includes(newNFTInfo.symbol.toUpperCase())) { + await updateNFTEnums(newNFTInfo); + } + + const newNFTsEnum = { + ...NFTs, + [newNFTInfo.symbol.toUpperCase()]: newNFTInfo.symbol.toUpperCase(), + }; + + generateNFTAddressesFile( + [ + { + chainSlug: newNFTInfo.chainSlug as ChainSlug, + token: newNFTInfo.symbol.toUpperCase() as NFTs, + address: newNFTInfo.address, + }, + ], + newNFTsEnum + ); +}; + +export const getNewNFTInfo = async ( + chainOptions: { title: string; value: number }[] +) => { + let newNFTInfo: NewNFTInfo = { + name: "", + symbol: "", + type: NFTType.ERC721, + address: "", + chainSlug: 0 as ChainSlug, + }; + + const { name, symbol, type, chainSlug, address } = await prompts([ + { + name: "name", + type: "text", + message: "Enter NFT name", + validate: (value) => validateEmptyValue(value.trim()), + }, + { + name: "type", + type: "select", + message: "Select NFT type", + choices: [ + { + title: "ERC721", + value: NFTType.ERC721, + }, + { + title: "ERC1155", + value: NFTType.ERC1155, + }, + ], + }, + { + name: "symbol", + type: "text", + message: "Enter NFT symbol", + validate: (value) => validateEmptyValue(value.trim()), + }, + { + name: "chainSlug", + type: "select", + message: "Select chain where token is deployed", + choices: chainOptions, + }, + { + name: "address", + type: "text", + message: "Enter token address", + validate: (value) => validateEthereumAddress(value.trim()), + }, + ]); + newNFTInfo.name = name; + newNFTInfo.symbol = symbol; + newNFTInfo.type = type; + newNFTInfo.chainSlug = chainSlug as ChainSlug; + newNFTInfo.address = address.trim(); + + if (ExistingNFTAddresses[chainSlug]) { + for (let [symbol, address] of Object.entries( + ExistingNFTAddresses[newNFTInfo.chainSlug] + )) { + if (address.toLowerCase() === newNFTInfo.address.toLowerCase()) { + console.log( + `NFT already present in the repo as ${symbol} on chain ${chainSlugReverseMap.get( + String(chainSlug) + )}` + ); + return newNFTInfo; + } + } + } + + let rpcKey = rpcKeys(chainSlug); + let rpc = process.env[rpcKey]; + if (!rpc) { + const rpcInfo = await prompts([ + { + name: "rpc", + type: "text", + message: `Enter RPC url for the chain ${chainSlug} (for fetching token metadata)`, + validate: (value) => validateRPC(chainSlug, value.trim()), + }, + ]); + rpc = rpcInfo.rpc.trim(); + appendToEnvFile(rpcKey, rpc); + } + + newNFTInfo.address = newNFTInfo.address.trim(); + if ( + ExistingNFTAddresses[chainSlug]?.[ + newNFTInfo.symbol.toUpperCase() + ]?.toLowerCase() === newNFTInfo.address.toLowerCase() + ) { + console.log("Token already present in the list"); + return newNFTInfo; + } + newNFTInfo = { + ...newNFTInfo, + symbol: newNFTInfo.symbol.toUpperCase(), + chainSlug, + }; + return newNFTInfo; +}; diff --git a/script/setup/common.ts b/script/setup/common.ts index a95e4550..14819c81 100644 --- a/script/setup/common.ts +++ b/script/setup/common.ts @@ -5,7 +5,7 @@ import { isContractAtAddress, rpcKeys, } from "../helpers"; -import { Hooks, ProjectType } from "../../src"; +import { Hooks, ProjectType, TokenType } from "../../src"; import { StaticJsonRpcProvider } from "@ethersproject/providers"; import { Tokens } from "../../src/enums"; @@ -14,6 +14,7 @@ export type ProjectConfig = { projectName: string; hookType: Hooks; owner: string; + tokenType: TokenType; isMainnet: boolean; newToken?: boolean; }; @@ -26,6 +27,19 @@ export type NewTokenInfo = { address: string; }; +export enum NFTType { + ERC721 = "erc721", + ERC1155 = "erc1155", +} + +export type NewNFTInfo = { + name: string; + type: NFTType; + symbol: string; + chainSlug: ChainSlug; + address: string; +}; + type TokenRateLimits = Record< string, { sendingLimit: number; receivingLimit: number } diff --git a/script/setup/configUtils.ts b/script/setup/configUtils.ts index ce2680fb..d8196cf8 100644 --- a/script/setup/configUtils.ts +++ b/script/setup/configUtils.ts @@ -2,13 +2,13 @@ import path from "path"; import fs, { appendFile, appendFileSync, writeFileSync } from "fs"; import { writeFile } from "fs/promises"; import { ChainSlug } from "@socket.tech/dl-core"; -import { Tokens } from "../../src/enums"; +import { NFTs, Tokens } from "../../src/enums"; import { chainSlugReverseMap, getEnumMaps, projectReverseMap, } from "./enumMaps"; -import { ProjectType } from "../../src"; +import { ProjectType, TokenType } from "../../src"; import { NewTokenInfo } from "./common"; import { getChainName } from "../constants"; @@ -19,7 +19,8 @@ export const buildEnvFile = async ( projectName: string, projectType: ProjectType, ownerAddress: string, - tokens: Tokens[], + tokens: Tokens[] | NFTs[], + tokenTypes: TokenType[], chains: ChainSlug[] ) => { let { publicEnvData, privateEnvData } = getProjectEnvData( @@ -27,6 +28,7 @@ export const buildEnvFile = async ( projectType, ownerAddress, tokens, + tokenTypes, chains ); let finalEnvData: Record; @@ -60,12 +62,14 @@ export const getProjectEnvData = ( projectName: string, projectType: ProjectType, ownerAddress: string, - tokens: Tokens[], + tokens: Tokens[] | NFTs[], + tokenTypes: TokenType[], chains: ChainSlug[] ) => { let publicEnvData: Record = { PROJECT: projectName, TOKENS: tokens.join(","), + TOKEN_TYPES: tokenTypes.join(","), OWNER_ADDRESS: ownerAddress, DRY_RUN: "false", }; @@ -154,6 +158,28 @@ export const updateTokenEnums = async (newTokenInfo: { console.log(`✔ Updated Enums : Tokens, Symbols, Decimals, Token Names`); }; +export const updateNFTEnums = async (newNFTInfo: { + name: string; + symbol: string; +}) => { + if (!newNFTInfo.name) return; + + let { name, symbol } = newNFTInfo; + await updateFile( + "nfts.ts", + `,\n ${symbol.toUpperCase()} = "${symbol.toUpperCase()}",\n}\n`, + ",\n}" + ); + + await updateFile( + "nftName.ts", + `,\n [NFTs.${symbol.toUpperCase()}]: "${name}",\n};\n`, + ",\n};" + ); + + console.log(`✔ Updated Enums : NFTs`); +}; + const updateFile = async (fileName, newChainDetails, replaceWith) => { const filePath = enumFolderPath + fileName; const outputExists = fs.existsSync(filePath); @@ -169,18 +195,25 @@ const updateFile = async (fileName, newChainDetails, replaceWith) => { fs.writeFileSync(filePath, newDataString); }; -const checkValueIfEnum = (value: any, tokensEnum: object = Tokens) => { +const checkValueIfEnum = ( + value: any, + tokensEnum: object = Tokens, + nftsEnum: object = NFTs +) => { const { chainSlugMap, tokensMap, + nftsMap, integrationTypesMap, deploymentModeMap, hookMap, - } = getEnumMaps(tokensEnum); + } = getEnumMaps(tokensEnum, nftsEnum); if (chainSlugMap.has(value)) { return "ChainSlug." + chainSlugMap.get(value); } else if (tokensMap.has(value)) { return "Tokens." + tokensMap.get(value); + } else if (nftsMap.has(value)) { + return "NFTs." + nftsMap.get(value); } else if (integrationTypesMap.has(value)) { return "IntegrationTypes." + integrationTypesMap.get(value); } else if (deploymentModeMap.has(value)) { @@ -194,30 +227,32 @@ const checkValueIfEnum = (value: any, tokensEnum: object = Tokens) => { export const serializeConstants = ( constants: any, depth: number = 0, - tokensEnum: object = Tokens + tokensEnum: object = Tokens, + nftsEnum: object = NFTs ): string => { const indent = " ".repeat(depth * 2); // Increase indent by 2 spaces per depth level const entries = Object.entries(constants); const serializedEntries = entries.map(([key, value]) => { - const enumKey = checkValueIfEnum(key, tokensEnum); + const enumKey = checkValueIfEnum(key, tokensEnum, nftsEnum); const newKey = enumKey ? `[${enumKey}]` : String(key); if (typeof value === "object" && !Array.isArray(value) && value !== null) { return `${indent}${newKey}: {\n${serializeConstants( value, depth + 1, - tokensEnum + tokensEnum, + nftsEnum )}\n${indent}}`; } else if (Array.isArray(value)) { return `${indent}${newKey}: [${value .map((v) => { - const enumValue = checkValueIfEnum(String(v), tokensEnum); + const enumValue = checkValueIfEnum(String(v), tokensEnum, nftsEnum); return enumValue ? `${enumValue}` : JSON.stringify(v); }) .join(", ")}]`; } else { - let valueEnum = checkValueIfEnum(String(value), tokensEnum); + let valueEnum = checkValueIfEnum(String(value), tokensEnum, nftsEnum); let newValue = valueEnum ? valueEnum : JSON.stringify(value); // Currently we don't have chain slugs as values, so can avoid them for now. This is a fix // for the case when we have chain slugs as values, for example sendingLimit = 1. diff --git a/script/setup/enumMaps.ts b/script/setup/enumMaps.ts index ce9fa913..217ab1aa 100644 --- a/script/setup/enumMaps.ts +++ b/script/setup/enumMaps.ts @@ -4,7 +4,7 @@ import { DeploymentMode, IntegrationTypes, } from "@socket.tech/dl-core"; -import { Project, Tokens } from "../../src/enums"; +import { NFTs, Project, Tokens } from "../../src/enums"; import { Hooks } from "../../src"; export const chainSlugReverseMap = createReverseEnumMap(ChainSlug); @@ -25,12 +25,17 @@ function createReverseEnumMap(enumObj: any) { return reverseMap; } -export const getEnumMaps = (tokensEnum: object = Tokens) => { +export const getEnumMaps = ( + tokensEnum: object = Tokens, + nftsEnum: object = NFTs +) => { // tokens is calculating separately because it is updated during setupScript with new token const tokensMap = createReverseEnumMap(tokensEnum); + const nftsMap = createReverseEnumMap(nftsEnum); return { chainSlugMap: chainSlugReverseMap, tokensMap, + nftsMap, integrationTypesMap: integrationTypesreverseMap, deploymentModeMap: deploymentModeReverseMap, hookMap: hookReverseMap, diff --git a/script/setup/generateConstants.ts b/script/setup/generateConstants.ts index d238c056..9f7851c8 100644 --- a/script/setup/generateConstants.ts +++ b/script/setup/generateConstants.ts @@ -2,13 +2,14 @@ import fs from "fs"; import { ProjectConstants, ProjectType } from "../../src"; import path from "path"; import { serializeConstants } from "./configUtils"; -import { Tokens } from "../../src/enums"; +import { NFTs, Tokens } from "../../src/enums"; export const generateConstantsFile = ( projectType: ProjectType, projectName: string, projectConstants: ProjectConstants, - tokensEnum: object = Tokens + tokensEnum: object = Tokens, + nftsEnum: object = NFTs ) => { let filePath = path.join( __dirname, @@ -22,11 +23,11 @@ import { IntegrationTypes, } from "@socket.tech/dl-core"; import { Hooks, ProjectConstants } from "../../../../src"; -import { Tokens } from "../../../../src/enums"; +import { NFTs, Tokens } from "../../../../src/enums"; // For testnet deployments, ChainSlug enum may not have some chains, therefore some keys will look like {421614:{}} instead of {[ChainSlug.ARBITRUM_SEPOLIA]:{}}. This wont affect the functionality of the project. export const pc: ProjectConstants = { -${serializeConstants(projectConstants, 1, tokensEnum)} +${serializeConstants(projectConstants, 1, tokensEnum, nftsEnum)} }; `; fs.writeFileSync(filePath, content); diff --git a/script/setup/newProject/main.ts b/script/setup/newProject/main.ts index 97639955..d2358e0c 100644 --- a/script/setup/newProject/main.ts +++ b/script/setup/newProject/main.ts @@ -1,12 +1,14 @@ import { ChainSlug } from "@socket.tech/dl-core"; -import { Tokens } from "../../../src/enums"; +import { NFTs, Tokens } from "../../../src/enums"; import { buildEnvFile, updateProjectEnums } from "../configUtils"; import { generateConstantsFile } from "../generateConstants"; import { getChainsInfo } from "./chainInfo"; import { getHookRelatedInfo } from "./hookInfo"; import { getProjectInfo } from "./projectInfo"; import { getProjectTokenListInfo } from "./tokenInfo"; -import { buildProjectConstants } from "./utils"; +import { buildNFTProjectConstants, buildProjectConstants } from "./utils"; +import { TokenType } from "../../../src"; +import { getProjectNFTInfo } from "./nftInfo"; export type TokenRateLimits = Record< string, @@ -37,11 +39,21 @@ export type TokenInfo = { }; }; +export type NFTInfo = { + nft: NFTs; + nftAddresses?: { + [key in NFTs]?: { + [chainslug in ChainSlug]?: string; + }; + }; +}; + export type ChainsInfo = { vaultChains: ChainSlug[]; controllerChains: ChainSlug[]; }; export const tokenEnum = Tokens; +export const nftEnum = NFTs; export const addProject = async () => { const projectConfig = await getProjectInfo(); @@ -50,6 +62,7 @@ export const addProject = async () => { projectType, hookType, owner, + tokenType, isLimitsRequired, chainOptions, } = projectConfig; @@ -58,38 +71,64 @@ export const addProject = async () => { const { vaultChains, controllerChains } = chainsInfo; const allChains = [...chainsInfo.vaultChains, ...chainsInfo.controllerChains]; - const tokenInfo: TokenInfo = await getProjectTokenListInfo( - projectType, - owner, - vaultChains, - controllerChains - ); - const { tokenLimitInfo } = await getHookRelatedInfo( - projectType, - isLimitsRequired, - tokenInfo.tokens, - tokenInfo.superTokenInfoMap - ); - await updateProjectEnums(projectConfig.projectName, projectType); - console.log(`✔ Updated Enums :Project`); - await buildEnvFile( - projectConfig.projectName, - projectConfig.projectType, - projectConfig.owner, - tokenInfo.tokens, - allChains - ); + if (tokenType === TokenType.ERC20) { + const tokenInfo: TokenInfo = await getProjectTokenListInfo( + projectType, + owner, + vaultChains, + controllerChains + ); + const { tokenLimitInfo } = await getHookRelatedInfo( + projectType, + isLimitsRequired, + tokenInfo.tokens, + tokenInfo.superTokenInfoMap + ); + await updateProjectEnums(projectName, projectType); + console.log(`✔ Updated Enums :Project`); + await buildEnvFile( + projectName, + projectType, + owner, + tokenInfo.tokens, + new Array(tokenInfo.tokens.length).fill(tokenType), + allChains + ); - const projectConstants = await buildProjectConstants( - tokenInfo, - chainsInfo, - hookType, - isLimitsRequired, - tokenLimitInfo, - allChains - ); - generateConstantsFile(projectType, projectName, projectConstants); + const projectConstants = await buildProjectConstants( + tokenInfo, + chainsInfo, + hookType, + isLimitsRequired, + tokenLimitInfo, + allChains + ); + generateConstantsFile(projectType, projectName, projectConstants); + } else { + // ERC721, 1155 + const nftInfo: NFTInfo = await getProjectNFTInfo( + projectType, + vaultChains, + controllerChains + ); + await updateProjectEnums(projectName, projectType); + console.log(`✔ Updated Enums :Project`); + await buildEnvFile( + projectName, + projectType, + owner, + [nftInfo.nft], + [tokenType], + allChains + ); + const projectConstants = await buildNFTProjectConstants( + nftInfo, + chainsInfo, + hookType + ); + generateConstantsFile(projectType, projectName, projectConstants); + } console.log( `✔ Setup done! You can run this script again to add new projects, add new tokens, or edit project` ); diff --git a/script/setup/newProject/nftInfo.ts b/script/setup/newProject/nftInfo.ts new file mode 100644 index 00000000..c357c648 --- /dev/null +++ b/script/setup/newProject/nftInfo.ts @@ -0,0 +1,67 @@ +import { ChainSlug } from "@socket.tech/dl-core"; +import prompts from "prompts"; +import { ProjectType } from "../../../src"; +import { ExistingNFTAddresses, NFTs } from "../../../src/enums"; +import { validateEthereumAddress } from "../common"; +import { nftEnum, NFTInfo } from "./main"; +import { getChainName } from "../../constants"; + +export const getProjectNFTInfo = async ( + projectType: ProjectType, + vaultChains: ChainSlug[], + controllerChains: ChainSlug[] +): Promise => { + const nftChoices = Object.keys(nftEnum).map((nft) => ({ + title: nft, + value: nftEnum[nft], + })); + if (projectType == ProjectType.SUPERBRIDGE) { + const response = await prompts([ + { + name: "nft", + type: "select", + message: + "Select the NFT to connect (the NFT we want to allow users to bridge to app chain)", + choices: nftChoices, + }, + ]); + const nft = response.nft as NFTs; + const nftAddresses = await getSuperbridgeMissingNFTAddresses(nft, [ + ...vaultChains, + ...controllerChains, + ]); + + return { nft, nftAddresses }; + } else if (projectType === ProjectType.SUPERTOKEN) { + console.log(`SUPERTOKEN project not supported for NFT now`); + } +}; + +export const getSuperbridgeMissingNFTAddresses = async ( + nft: NFTs, + chainList: number[] +) => { + const nftAddresses: { + [key in NFTs]?: { + [chainslug: number]: string; + }; + } = {}; + + nftAddresses[nft] = {}; + for (const chain of chainList) { + const currentAddress = ExistingNFTAddresses[chain]?.[nft]; + if (currentAddress) continue; + const response = await prompts([ + { + name: "address", + type: "text", + message: `Enter the address of the deployed NFT ${nft} on the chain ${getChainName( + chain + )}`, + validate: (value) => validateEthereumAddress(value.trim()), + }, + ]); + nftAddresses[nft][chain] = response.address; + } + return nftAddresses; +}; diff --git a/script/setup/newProject/projectInfo.ts b/script/setup/newProject/projectInfo.ts index 7ec5041f..120b2d9b 100644 --- a/script/setup/newProject/projectInfo.ts +++ b/script/setup/newProject/projectInfo.ts @@ -1,5 +1,5 @@ import prompts from "prompts"; -import { DeploymentMode, Hooks, ProjectType } from "../../../src"; +import { DeploymentMode, Hooks, ProjectType, TokenType } from "../../../src"; import { getChainName, getMainnetIds, @@ -60,6 +60,25 @@ export const getProjectInfo = async () => { ], message: "Select Hook type (Recommended: Limit Hook)", }, + { + name: "tokenType", + type: "select", + choices: [ + { + title: "ERC20", + value: TokenType.ERC20, + }, + { + title: "ERC721", + value: TokenType.ERC721, + }, + { + title: "ERC1155", + value: TokenType.ERC1155, + }, + ], + message: "Select Token type", + }, { name: "isMainnet", type: "toggle", diff --git a/script/setup/newProject/utils.ts b/script/setup/newProject/utils.ts index 4f116251..1635cf61 100644 --- a/script/setup/newProject/utils.ts +++ b/script/setup/newProject/utils.ts @@ -8,7 +8,13 @@ import { import { Tokens } from "../../../src/enums"; import { getMode } from "../../constants"; import { initialLimitsForSuperbridge } from "../common"; -import { ChainsInfo, SuperTokenInfo, TokenInfo, TokenRateLimits } from "./main"; +import { + ChainsInfo, + NFTInfo, + SuperTokenInfo, + TokenInfo, + TokenRateLimits, +} from "./main"; export const buildProjectConstants = async ( tokenInfo: TokenInfo, @@ -57,6 +63,28 @@ export const buildProjectConstants = async ( return JSON.parse(JSON.stringify(projectConstants)); // stringify and parse to remove undefined values }; +export const buildNFTProjectConstants = async ( + nftInfo: NFTInfo, + chainsInfo: ChainsInfo, + hookType: Hooks +) => { + const deploymentMode = getMode(); + const projectConstants: ProjectConstants = { + [deploymentMode]: {}, + }; + + const { nft, nftAddresses } = nftInfo; + projectConstants[deploymentMode][nft] = { + vaultChains: chainsInfo.vaultChains, + controllerChains: chainsInfo.controllerChains, + tokenAddresses: nftAddresses?.[nft], + hook: { + hookType, + }, + }; + return JSON.parse(JSON.stringify(projectConstants)); // stringify and parse to remove undefined values +}; + export const getInitialLimitValue = async ( projectType: ProjectType, token: Tokens, diff --git a/script/setup/updateExistingNFTAddresses.ts b/script/setup/updateExistingNFTAddresses.ts new file mode 100644 index 00000000..74248262 --- /dev/null +++ b/script/setup/updateExistingNFTAddresses.ts @@ -0,0 +1,43 @@ +import fs from "fs"; +import { ChainSlug } from "../../src"; +import { enumFolderPath, serializeConstants } from "./configUtils"; +import { NFTs } from "../../src/enums"; +import { ExistingNFTAddresses } from "../../src/enums"; + +export type NFTAddressObj = { + chainSlug: ChainSlug; + token: NFTs | string; + address: string; +}; +export const generateNFTAddressesFile = ( + tokenAddresses: NFTAddressObj[], + nftsEnum: object = NFTs +) => { + for (const tokenAddressObj of tokenAddresses) { + const { chainSlug, token, address } = tokenAddressObj; + if (!ExistingNFTAddresses[chainSlug]) ExistingNFTAddresses[chainSlug] = {}; + ExistingNFTAddresses[chainSlug][token] = address; + } + const serializedContent = serializeConstants( + ExistingNFTAddresses, + 0, + {}, + nftsEnum + ); + const content = ` + import { ChainSlug } from "@socket.tech/dl-core"; + import { NFTs } from "./nfts"; + + export const ExistingNFTAddresses: { + [key in ChainSlug]?: { [key in NFTs]?: string }; + } = { + ${serializedContent} +}; +`; + fs.writeFileSync(enumFolderPath + "existing-nft-addresses.ts", content); + console.log( + `✔ existing nft addresses file updated : ${ + enumFolderPath + "existing-nft-addresses.ts" + }` + ); +}; diff --git a/socket-plugs-details.json b/socket-plugs-details.json index 28750f50..4e81ba4b 100644 --- a/socket-plugs-details.json +++ b/socket-plugs-details.json @@ -166,7 +166,8 @@ "timeswap_test_mainnet", "testing_testnet", "magic_mainnet", - "polter_testnet" + "polter_testnet", + "aavegotchi_bridge_testnet" ], "tokens": [ "USDC.e", diff --git a/src/enum.ts b/src/enum.ts index 23c87dda..d584a827 100644 --- a/src/enum.ts +++ b/src/enum.ts @@ -32,6 +32,8 @@ export enum SuperBridgeContracts { FiatTokenV2_1_Controller = "FiatTokenV2_1_Controller", ExchangeRate = "ExchangeRate", ConnectorPlug = "ConnectorPlug", + NFTVault = "NFTVault", + NFTController = "NFTController", } export enum HookContracts { @@ -45,3 +47,9 @@ export enum SuperTokenContracts { NonSuperToken = "NonSuperToken", SuperToken = "SuperToken", } + +export enum TokenType { + ERC20 = "ERC20", + ERC721 = "ERC721", + ERC1155 = "ERC1155", +} diff --git a/src/enums/existing-nft-addresses.ts b/src/enums/existing-nft-addresses.ts new file mode 100644 index 00000000..37ba4d8f --- /dev/null +++ b/src/enums/existing-nft-addresses.ts @@ -0,0 +1,14 @@ +import { ChainSlug } from "@socket.tech/dl-core"; +import { NFTs } from "./nfts"; + +export const ExistingNFTAddresses: { + [key in ChainSlug]?: { [key in NFTs]?: string }; +} = { + [ChainSlug.POLYGON_MAINNET]: { + [NFTs.GOTCHI]: "0x86935F11C86623deC8a25696E1C19a8659CbF95d", + [NFTs.GOTCHI_ITEM]: "0x58de9AaBCaeEC0f69883C94318810ad79Cc6a44f", + [NFTs.FORGE]: "0x4fDfc1B53Fd1D80d969C984ba7a8CE4c7bAaD442", + [NFTs.REALM]: "0x1D0360BaC7299C86Ec8E99d0c1C9A95FEfaF2a11", + [NFTs.INSTALLATION]: "0x19f870bD94A34b3adAa9CaA439d333DA18d6812A", + }, +}; diff --git a/src/enums/index.ts b/src/enums/index.ts index 2e98d740..098cd0fa 100644 --- a/src/enums/index.ts +++ b/src/enums/index.ts @@ -4,3 +4,6 @@ export * from "./existing-token-addresses"; export * from "./tokenSymbol"; export * from "./tokenName"; export * from "./tokenDecimals"; +export * from "./nfts"; +export * from "./nftName"; +export * from "./existing-nft-addresses"; diff --git a/src/enums/nftName.ts b/src/enums/nftName.ts new file mode 100644 index 00000000..1c301450 --- /dev/null +++ b/src/enums/nftName.ts @@ -0,0 +1,9 @@ +import { NFTs } from "./nfts"; + +export const nftName: { [key in NFTs]: string } = { + [NFTs.GOTCHI]: "Aavegotchi", + [NFTs.GOTCHI_ITEM]: "Aavegotchi Item", + [NFTs.FORGE]: "Aavegotchi Forge", + [NFTs.REALM]: "Gotchiverse Realm", + [NFTs.INSTALLATION]: "Gotchiverse Installation", +}; diff --git a/src/enums/nfts.ts b/src/enums/nfts.ts new file mode 100644 index 00000000..de8ef222 --- /dev/null +++ b/src/enums/nfts.ts @@ -0,0 +1,7 @@ +export enum NFTs { + GOTCHI = "GOTCHI", + GOTCHI_ITEM = "GOTCHI_ITEM", + FORGE = "FORGE", + REALM = "REALM", + INSTALLATION = "INSTALLATION", +} diff --git a/src/enums/projectType.ts b/src/enums/projectType.ts index 4b91c851..cc7ecc87 100644 --- a/src/enums/projectType.ts +++ b/src/enums/projectType.ts @@ -23,4 +23,8 @@ export const ProjectTypeMap: Record = { [Project.TESTING_TESTNET]: ProjectType.SUPERTOKEN, [Project.MAGIC_MAINNET]: ProjectType.SUPERTOKEN, [Project.POLTER_TESTNET]: ProjectType.SUPERBRIDGE, + [Project.AAVEGOTCHI_ITEMS_BRIDGE_TESTNET]: ProjectType.SUPERBRIDGE, + [Project.AAVEGOTCHI_GOTCHIS_BRIDGE_TESTNET]: ProjectType.SUPERBRIDGE, + [Project.AAVEGOTCHI_GOTCHI_BRIDGE_MAINNET]: ProjectType.SUPERBRIDGE, + [Project.AAVEGOTCHI_ITEMS_BRIDGE_MAINNET]: ProjectType.SUPERBRIDGE, }; diff --git a/src/enums/projects.ts b/src/enums/projects.ts index 04ec0f2f..e7243af2 100644 --- a/src/enums/projects.ts +++ b/src/enums/projects.ts @@ -20,4 +20,8 @@ export enum Project { TESTING_TESTNET = "testing_testnet", MAGIC_MAINNET = "magic_mainnet", POLTER_TESTNET = "polter_testnet", + AAVEGOTCHI_ITEMS_BRIDGE_TESTNET = "aavegotchi_items_bridge_testnet", + AAVEGOTCHI_GOTCHIS_BRIDGE_TESTNET = "aavegotchi_gotchis_bridge_testnet", + AAVEGOTCHI_GOTCHI_BRIDGE_MAINNET = "aavegotchi_gotchi_bridge_mainnet", + AAVEGOTCHI_ITEMS_BRIDGE_MAINNET = "aavegotchi_items_bridge_mainnet", } diff --git a/src/types.ts b/src/types.ts index 6be7077d..dbf3d47d 100644 --- a/src/types.ts +++ b/src/types.ts @@ -7,8 +7,9 @@ import { ProjectType, SuperBridgeContracts, SuperTokenContracts, + TokenType, } from "./enum"; -import { Project, Tokens } from "./enums"; +import { NFTs, Project, Tokens } from "./enums"; import { S3ChainConfig } from "@socket.tech/dl-core"; export type ProjectConstantsMap = { @@ -17,7 +18,7 @@ export type ProjectConstantsMap = { export type ProjectConstants = { [key in DeploymentMode]?: { - [key in Tokens]?: TokenConstants; + [key in Tokens | NFTs]?: TokenConstants; }; }; @@ -102,7 +103,7 @@ export type SBTokenAddresses = AppChainAddresses | NonAppChainAddresses; export type SBAddresses = { [chainSlug in ChainSlug]?: { - [token in Tokens]?: SBTokenAddresses; + [token in Tokens | NFTs]?: SBTokenAddresses; }; }; @@ -122,7 +123,7 @@ export type STTokenAddresses = export type STAddresses = { [chainSlug in ChainSlug]?: { - [token in Tokens]?: STTokenAddresses; + [token in Tokens | NFTs]?: STTokenAddresses; }; }; @@ -130,7 +131,8 @@ export interface DeployParams { addresses: SBTokenAddresses | STTokenAddresses; signer: Wallet; currentChainSlug: number; - currentToken: Tokens; + currentToken: string; + currentTokenType: TokenType; hookType?: Hooks; mergeInboundWithTokens: Tokens[]; tc: TokenConstants;