diff --git a/Assets/Shared/Scripts/SequenceManager.cs b/Assets/Shared/Scripts/SequenceManager.cs index c6a721611..75a0f0199 100644 --- a/Assets/Shared/Scripts/SequenceManager.cs +++ b/Assets/Shared/Scripts/SequenceManager.cs @@ -5,9 +5,6 @@ using HyperCasual.Runner; using UnityEngine; using UnityEngine.SceneManagement; -using System; -using System.Collections; -using UnityEngine; namespace HyperCasual.Gameplay { diff --git a/Assets/Shared/Scripts/UI/LevelCompleteScreen.cs b/Assets/Shared/Scripts/UI/LevelCompleteScreen.cs index e98d46263..50dc125d6 100644 --- a/Assets/Shared/Scripts/UI/LevelCompleteScreen.cs +++ b/Assets/Shared/Scripts/UI/LevelCompleteScreen.cs @@ -5,6 +5,10 @@ using UnityEngine.UI; using System; using System.Collections.Generic; +using Immutable.Passport; +using Cysharp.Threading.Tasks; +using System.Numerics; +using System.Net.Http; namespace HyperCasual.Runner { @@ -102,13 +106,13 @@ public int CoinCount } } - public void OnEnable() + public async void OnEnable() { // Set listener to 'Next' button m_NextButton.RemoveListener(OnNextButtonClicked); m_NextButton.AddListener(OnNextButtonClicked); - // Set listener to "Continue with Passport" button + // Set listener to "Continue with Passport" button m_ContinuePassportButton.RemoveListener(OnContinueWithPassportButtonClicked); m_ContinuePassportButton.AddListener(OnContinueWithPassportButtonClicked); @@ -116,20 +120,128 @@ public void OnEnable() m_TryAgainButton.RemoveListener(OnTryAgainButtonClicked); m_TryAgainButton.AddListener(OnTryAgainButtonClicked); - ShowNextButton(true); + ShowError(false); + ShowLoading(false); + + // If player is logged into Passport mint coins to player + if (SaveManager.Instance.IsLoggedIn) + { + // Mint collected coins to player + await MintCoins(); + } + else + { + // Show 'Next' button if player is already logged into Passport + ShowNextButton(SaveManager.Instance.IsLoggedIn); + // Show "Continue with Passport" button if the player is not logged into Passport + ShowContinueWithPassportButton(!SaveManager.Instance.IsLoggedIn); + } } - private void OnContinueWithPassportButtonClicked() + /// + /// Mints collected coins (i.e. Immutable Runner Token) to the player's wallet + /// + private async UniTask MintCoins() { + // This function is similar to MintCoins() in MintScreen.cs. Consider refactoring duplicate code in production. + Debug.Log("Minting coins..."); + bool success = false; + + // Show loading + ShowLoading(true); + ShowNextButton(false); + ShowError(false); + + try + { + // Don't mint any coins if player did not collect any + if (m_CoinCount == 0) + { + success = true; + } + else + { + // Get the player's wallet address to mint the coins to + List accounts = await Passport.Instance.ZkEvmRequestAccounts(); + string address = accounts[0]; + if (address != null) + { + // Calculate the quantity to mint + // Need to take into account Immutable Runner Token decimal value i.e. 18 + BigInteger quantity = new BigInteger(m_CoinCount); + Debug.Log($"Quantity: {quantity}"); + var nvc = new List> + { + // Set 'to' to the player's wallet address + new KeyValuePair("to", address), + // Set 'quanity' to the number of coins collected + new KeyValuePair("quantity", quantity.ToString()) + }; + using var client = new HttpClient(); + string url = $"http://localhost:3000/mint/token"; // Endpoint to mint token + using var req = new HttpRequestMessage(HttpMethod.Post, url) { Content = new FormUrlEncodedContent(nvc) }; + using var res = await client.SendAsync(req); + success = res.IsSuccessStatusCode; + } + } + } + catch (Exception ex) + { + Debug.Log($"Failed to mint coins: {ex.Message}"); + } + + ShowLoading(false); + ShowNextButton(success); + ShowError(!success); } - private void OnTryAgainButtonClicked() + private async void OnContinueWithPassportButtonClicked() { + try + { + // Show loading + ShowContinueWithPassportButton(false); + ShowLoading(true); + + // Log into Passport + await Passport.Instance.Login(); + + // Successfully logged in + // Save a persistent flag in the game that the player is logged in + SaveManager.Instance.IsLoggedIn = true; + // Show 'Next' button + ShowNextButton(true); + ShowLoading(false); + // Take the player to the Setup Wallet screen + m_SetupWalletEvent.Raise(); + } + catch (Exception ex) + { + Debug.Log($"Failed to log into Passport: {ex.Message}"); + // Show Continue with Passport button again + ShowContinueWithPassportButton(true); + ShowLoading(false); + } + } + + private async void OnTryAgainButtonClicked() + { + await MintCoins(); } private void OnNextButtonClicked() { - m_NextLevelEvent.Raise(); + // Check if the player is already using a new skin + if (!SaveManager.Instance.UseNewSkin) + { + // Player is not using a new skin, take player to Unlocked Skin screen + m_UnlockedSkinEvent.Raise(); + } + else + { + // Player is already using a new skin, take player to the next level + m_NextLevelEvent.Raise(); + } } private void ShowCompletedContainer(bool show) @@ -171,4 +283,4 @@ void DisplayCoins(int count) } } } -} +} \ No newline at end of file diff --git a/Assets/Shared/Scripts/UI/MainMenu.cs b/Assets/Shared/Scripts/UI/MainMenu.cs index 39c0643c0..970fa6ed5 100644 --- a/Assets/Shared/Scripts/UI/MainMenu.cs +++ b/Assets/Shared/Scripts/UI/MainMenu.cs @@ -4,6 +4,7 @@ using UnityEngine; using UnityEngine.UI; using TMPro; +using Immutable.Passport; namespace HyperCasual.Runner { @@ -23,7 +24,9 @@ public class MainMenu : View [SerializeField] GameObject m_Loading; - void OnEnable() + Passport passport; + + async void OnEnable() { ShowLoading(true); @@ -34,8 +37,37 @@ void OnEnable() m_LogoutButton.RemoveListener(OnLogoutButtonClick); m_LogoutButton.AddListener(OnLogoutButtonClick); + // Initialise Passport + string clientId = "Jf3dcKYrYUC6MLCMsDsOvKGoirlAzGJR"; + string environment = Immutable.Passport.Model.Environment.SANDBOX; + string redirectUri = null; + string logoutUri = null; + + passport = await Passport.Init(clientId, environment, redirectUri, logoutUri); + + // Check if the player is supposed to be logged in and if there are credentials saved + if (SaveManager.Instance.IsLoggedIn && await Passport.Instance.HasCredentialsSaved()) + { + // Try to log in using saved credentials + bool success = await Passport.Instance.Login(useCachedSession: true); + // Update the login flag + SaveManager.Instance.IsLoggedIn = success; + // Set up wallet if successful + if (success) + { + await Passport.Instance.ConnectEvm(); + await Passport.Instance.ZkEvmRequestAccounts(); + } + } + else + { + // No saved credentials to re-login the player, reset the login flag + SaveManager.Instance.IsLoggedIn = false; + } + ShowLoading(false); - ShowStartButton(true); + // Show the logout button if the player is logged in + ShowLogoutButton(SaveManager.Instance.IsLoggedIn); } void OnDisable() @@ -49,7 +81,7 @@ void OnStartButtonClick() AudioManager.Instance.PlayEffect(SoundID.ButtonSound); } - void OnLogoutButtonClick() + async void OnLogoutButtonClick() { try { @@ -59,6 +91,7 @@ void OnLogoutButtonClick() ShowLoading(true); // Logout + await passport.Logout(); // Reset the login flag SaveManager.Instance.IsLoggedIn = false; diff --git a/Assets/Shared/Scripts/UI/MintScreen.cs b/Assets/Shared/Scripts/UI/MintScreen.cs index c13f50f8b..8e105d07f 100644 --- a/Assets/Shared/Scripts/UI/MintScreen.cs +++ b/Assets/Shared/Scripts/UI/MintScreen.cs @@ -5,6 +5,10 @@ using System; using System.Collections.Generic; using System.Threading; +using System.Net.Http; +using Immutable.Passport; +using Cysharp.Threading.Tasks; +using System.Numerics; namespace HyperCasual.Runner { @@ -33,6 +37,10 @@ public class MintScreen : View [SerializeField] HyperCasualButton m_WalletButton; + // If there's an error minting, these values will be used when the player clicks the "Try again" button + private bool mintedFox = false; + private bool mintedCoins = false; + public void OnEnable() { // Set listener to 'Next' button @@ -47,10 +55,14 @@ public void OnEnable() m_WalletButton.RemoveListener(OnWalletClicked); m_WalletButton.AddListener(OnWalletClicked); + // Reset values + mintedFox = false; + mintedCoins = false; + Mint(); } - private void Mint() + private async void Mint() { try { @@ -59,12 +71,27 @@ private void Mint() ShowError(false); ShowNextButton(false); - // Mint + // Mint fox if not minted yet + if (!mintedFox) + { + mintedFox = await MintFox(); + } + // Mint coins if not minted yet + if (!mintedCoins) + { + mintedCoins = await MintCoins(); + } - ShowMintedMessage(); + // Show minted message if minted both fox and coins successfully + if (mintedFox && mintedCoins) + { + ShowMintedMessage(); + } ShowLoading(false); - ShowError(false); - ShowNextButton(true); + // Show error if failed to mint fox or coins + ShowError(!mintedFox || !mintedCoins); + // Show next button is minted both fox and coins successfully + ShowNextButton(mintedFox && mintedCoins); } catch (Exception ex) { @@ -76,6 +103,101 @@ private void Mint() } } + /// + /// Gets the wallet address of the player. + /// + private async UniTask GetWalletAddress() + { + List accounts = await Passport.Instance.ZkEvmRequestAccounts(); + return accounts[0]; // Get the first wallet address + } + + /// + /// Mints a fox (i.e. Immutable Runner Fox) to the player's wallet + /// + /// True if minted a fox successfully to player's wallet. Otherwise, false. + private async UniTask MintFox() + { + Debug.Log("Minting fox..."); + try + { + string address = await GetWalletAddress(); // Get the player's wallet address to mint the fox to + + if (address != null) + { + var nvc = new List> + { + // Set 'to' to the player's wallet address + new KeyValuePair("to", address) + }; + using var client = new HttpClient(); + string url = $"http://localhost:3000/mint/fox"; // Endpoint to mint fox + using var req = new HttpRequestMessage(HttpMethod.Post, url) { Content = new FormUrlEncodedContent(nvc) }; + using var res = await client.SendAsync(req); + + string content = await res.Content.ReadAsStringAsync(); + Debug.Log($"Mint fox response: {content}"); + + return res.IsSuccessStatusCode; + } + + return false; + } + catch (Exception ex) + { + Debug.Log($"Failed to mint fox: {ex.Message}"); + return false; + } + } + + /// + /// Mints collected coins (i.e. Immutable Runner Token) to the player's wallet + /// + /// True if minted coins successfully to player's wallet. Otherwise, false. + private async UniTask MintCoins() + { + Debug.Log("Minting coins..."); + try + { + int coinsCollected = GetNumCoinsCollected(); // Get number of coins collected + if (coinsCollected == 0) // Don't mint any coins if player did not collect any + { + return true; + } + + string address = await GetWalletAddress(); // Get the player's wallet address to mint the coins to + if (address != null) + { + // Calculate the quantity to mint + // Need to take into account Immutable Runner Token decimal value i.e. 18 + BigInteger quantity = new BigInteger(coinsCollected); + Debug.Log($"Quantity: {quantity}"); + var nvc = new List> { + // Set 'to' to the player's wallet address + new KeyValuePair("to", address), + // Set 'quanity' to the number of coins collected + new KeyValuePair("quantity", quantity.ToString()) + }; + using var client = new HttpClient(); + string url = $"http://localhost:3000/mint/token"; // Endpoint to mint token + using var req = new HttpRequestMessage(HttpMethod.Post, url) { Content = new FormUrlEncodedContent(nvc) }; + using var res = await client.SendAsync(req); + + string content = await res.Content.ReadAsStringAsync(); + Debug.Log($"Mint tokens response: {content}"); + + return res.IsSuccessStatusCode; + } + + return false; + } + catch (Exception ex) + { + Debug.Log($"Failed to mint coins: {ex.Message}"); + return false; + } + } + private void OnNextButtonClicked() { m_NextEvent.Raise(); @@ -141,8 +263,12 @@ private void ShowMintedMessage() } } - private void OnWalletClicked() + private async void OnWalletClicked() { + // Get the player's wallet address to mint the fox to + string address = await GetWalletAddress(); + // Show the player's tokens on the block explorer page. + Application.OpenURL($"https://explorer.testnet.immutable.com/address/{address}?tab=tokens"); } } } diff --git a/Assets/Shared/Scripts/UI/SetupWalletScreen.cs b/Assets/Shared/Scripts/UI/SetupWalletScreen.cs index f8cf64f94..26b188426 100644 --- a/Assets/Shared/Scripts/UI/SetupWalletScreen.cs +++ b/Assets/Shared/Scripts/UI/SetupWalletScreen.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Threading; +using Immutable.Passport; namespace HyperCasual.Runner { @@ -44,7 +45,7 @@ public void OnEnable() SetupWallet(); } - private void SetupWallet() + private async void SetupWallet() { try { @@ -54,6 +55,8 @@ private void SetupWallet() ShowSuccess(false); // Set up wallet + await Passport.Instance.ConnectEvm(); + await Passport.Instance.ZkEvmRequestAccounts(); m_Title.text = "Your wallet has been successfully set up!"; ShowLoading(false); @@ -72,7 +75,7 @@ private void SetupWallet() private void OnNextButtonClicked() { - m_NextEvent.Raise(); + m_MintEvent.Raise(); } private void ShowNextButton(bool show) diff --git a/Assets/Shared/Scripts/UI/UnlockedSkinScreen.cs b/Assets/Shared/Scripts/UI/UnlockedSkinScreen.cs index 0b563d450..3cd8892eb 100644 --- a/Assets/Shared/Scripts/UI/UnlockedSkinScreen.cs +++ b/Assets/Shared/Scripts/UI/UnlockedSkinScreen.cs @@ -5,7 +5,27 @@ using UnityEngine.UI; using System; using System.Collections.Generic; +using System.Net.Http; +using System.Threading; using System.Threading.Tasks; +using Cysharp.Threading.Tasks; +using Immutable.Passport; +using Immutable.Passport.Model; + + +[Serializable] +public class GetAssetsResponse +{ + public Asset[] result; +} + +[Serializable] +public class Asset +{ + public string id; + public string token_id; + public string token_address; +} namespace HyperCasual.Runner { @@ -45,7 +65,7 @@ public CraftSkinState? CraftState get => m_CraftState; set { - CraftState = value; + m_CraftState = value; switch (m_CraftState) { case CraftSkinState.Crafting: @@ -113,18 +133,32 @@ public async void OnEnable() private async void Craft() { - CraftState = CraftSkinState.Crafting; + m_CraftState = CraftSkinState.Crafting; // Burn tokens and mint a new skin i.e. crafting a skin - await Task.Delay(TimeSpan.FromSeconds(5)); + const string runnerTokenContractAddress = "0x3b28c82a6e5e3d6023e2a440b2b1b3480d4c6fd3"; - CraftState = CraftSkinState.Crafted; + try { + TransactionReceiptResponse response = await Passport.Instance.ZkEvmSendTransactionWithConfirmation(new TransactionRequest() + { + to = runnerTokenContractAddress, // Immutable Runner Token contract address + data = "0x1e957f1e", // Call craftSkin() in the contract + value = "0" + }); + Debug.Log($"Craft transaction hash: {response.transactionHash}"); - // If successfully crafted skin and this screen is visible, go to collect skin screen - // otherwise it will be picked in the OnEnable function above when this screen reappears - if (m_CraftState == CraftSkinState.Crafted && gameObject.active) - { - CollectSkin(); + m_CraftState = response.status != "1" ? CraftSkinState.Failed : CraftSkinState.Crafted; + + + // If successfully crafted skin and this screen is visible, go to collect skin screen + // otherwise it will be picked in the OnEnable function above when this screen reappears + if (m_CraftState == CraftSkinState.Crafted && gameObject.active) + { + CollectSkin(); + } + } catch (Exception ex) { + Debug.Log($"Failed to craft skin: {ex.Message}"); + m_CraftState = CraftSkinState.Failed; } } @@ -136,9 +170,9 @@ private void CollectSkin() private void OnCraftButtonClicked() { - m_NextLevelEvent.Raise(); // Craft in the background, while the player plays the next level Craft(); + // m_NextLevelEvent.Raise(); } private void OnTryAgainButtonClicked() diff --git a/Packages/manifest.json b/Packages/manifest.json index 8e0ea5080..46170b4f9 100644 --- a/Packages/manifest.json +++ b/Packages/manifest.json @@ -1,5 +1,7 @@ { "dependencies": { + "com.cysharp.unitask": "https://github.com/Cysharp/UniTask.git?path=src/UniTask/Assets/Plugins/UniTask", + "com.immutable.passport": "https://github.com/immutable/unity-immutable-sdk.git?path=/src/Packages/Passport", "com.unity.collab-proxy": "2.0.4", "com.unity.ide.rider": "3.0.21", "com.unity.ide.visualstudio": "2.0.18", diff --git a/Packages/packages-lock.json b/Packages/packages-lock.json index c439fc2fa..3bb586d1b 100644 --- a/Packages/packages-lock.json +++ b/Packages/packages-lock.json @@ -1,5 +1,22 @@ { "dependencies": { + "com.cysharp.unitask": { + "version": "https://github.com/Cysharp/UniTask.git?path=src/UniTask/Assets/Plugins/UniTask", + "depth": 0, + "source": "git", + "dependencies": {}, + "hash": "f9fd769be7c634610f2a61aa914a1a55f34740e1" + }, + "com.immutable.passport": { + "version": "https://github.com/immutable/unity-immutable-sdk.git?path=/src/Packages/Passport", + "depth": 0, + "source": "git", + "dependencies": { + "com.unity.nuget.newtonsoft-json": "3.2.0", + "com.cysharp.unitask": "2.3.3" + }, + "hash": "9a3384926b73629516fc28c940a3ce4847edc503" + }, "com.unity.burst": { "version": "1.8.4", "depth": 1, @@ -81,6 +98,13 @@ "dependencies": {}, "url": "https://packages.unity.com" }, + "com.unity.nuget.newtonsoft-json": { + "version": "3.2.0", + "depth": 1, + "source": "registry", + "dependencies": {}, + "url": "https://packages.unity.com" + }, "com.unity.postprocessing": { "version": "3.2.2", "depth": 0, @@ -159,9 +183,9 @@ "depth": 0, "source": "registry", "dependencies": { + "com.unity.modules.audio": "1.0.0", "com.unity.modules.director": "1.0.0", "com.unity.modules.animation": "1.0.0", - "com.unity.modules.audio": "1.0.0", "com.unity.modules.particlesystem": "1.0.0" }, "url": "https://packages.unity.com" diff --git a/contracts/contracts/RunnerToken.sol b/contracts/contracts/RunnerToken.sol index 86e10ade6..df99d9be9 100644 --- a/contracts/contracts/RunnerToken.sol +++ b/contracts/contracts/RunnerToken.sol @@ -24,7 +24,7 @@ contract RunnerToken is ERC20, ERC20Burnable, MintingAccessControl { _skinContract = ImmutableERC721(skinContractAddr); // Uncomment the line below to grant minter role to contract deployer - // _grantRole(MINTER_ROLE, msg.sender); + _grantRole(MINTER_ROLE, msg.sender); } // Mints the number of tokens specified diff --git a/contracts/contracts/RunnerToken1155.sol b/contracts/contracts/RunnerToken1155.sol new file mode 100644 index 000000000..90795c1e2 --- /dev/null +++ b/contracts/contracts/RunnerToken1155.sol @@ -0,0 +1,81 @@ +// Copyright Immutable Pty Ltd 2018 - 2023 +// SPDX-License-Identifier: Apache 2.0 +pragma solidity 0.8.19; + +import "@imtbl/contracts/contracts/token/erc1155/preset/ImmutableERC1155.sol"; +import "@imtbl/contracts/contracts/token/erc721/preset/ImmutableERC721.sol"; + + +/** + * @title ImmutableERC1155 + * @author @jasonzwli, Immutable + */ +contract RunnerToken1155 is ImmutableERC1155 { + /// ===== Constructor ===== + uint256 public constant FOXTOKENID = 1; + uint256 public constant COINTOKENID = 2; + + // A reference to the Immutable Runner Skin contract for the craftSkin function to use. + // Note: Immutable Runner Skin contract simply extends ImmutableERC721, so we can set + // the type to ImmutableERC721 + ImmutableERC721 private _skinContract; + + /** + * @notice Grants `DEFAULT_ADMIN_ROLE` to the supplied `owner` address + * + * Sets the name and symbol for the collection + * Sets the default admin to `owner` + * Sets the `baseURI` + * Sets the royalty receiver and amount (this can not be changed once set) + * @param owner The address that will be granted the `DEFAULT_ADMIN_ROLE` + * @param name_ The name of the collection + * @param baseURI_ The base URI for the collection + * @param contractURI_ The contract URI for the collection + * @param _operatorAllowlist The address of the OAL + * @param _receiver The address that will receive the royalty payments + * @param _feeNumerator The percentage of the sale price that will be paid as a royalty + */ + constructor( + address owner, + string memory name_, + string memory baseURI_, + string memory contractURI_, + address _operatorAllowlist, + address _receiver, + uint96 _feeNumerator, + address skinContractAddr + ) ImmutableERC1155(owner, name_, baseURI_, contractURI_, _operatorAllowlist, _receiver, _feeNumerator) { + // Grant the contract deployer the default admin role: it will be able + // to grant and revoke any roles + _setupRole(DEFAULT_ADMIN_ROLE, msg.sender); + // Save the Immutable Runner Skin contract address + _skinContract = ImmutableERC721(skinContractAddr); + + // Uncomment the line below to grant minter role to contract deployer + _grantRole(MINTER_ROLE, msg.sender); + } + + function mintNFT(address to) external onlyRole(MINTER_ROLE) { + super._mint(to, FOXTOKENID, 1, ""); + } + + function mintCoins(address to, uint256 quantity) external onlyRole(MINTER_ROLE) { + super._mint(to, COINTOKENID, quantity, ""); + } + + // Burns three tokens and crafts a skin to the caller + function craftSkin() external { + require( + balanceOf(msg.sender, COINTOKENID) >= 3, + "craftSkin: Caller does not have enough tokens" + ); + + // Burn caller's three tokens + _burn(msg.sender, COINTOKENID, 3); + + // Mint one Immutable Runner Skin to the caller + // Note: To mint a skin, the Immutable Runner Token contract must have the + // Immutable Runner Skin contract minter role. + _skinContract.mintByQuantity(msg.sender, 1); + } +} diff --git a/contracts/scripts/deploy.ts b/contracts/scripts/deploy.ts index f05679cbf..26a26981c 100644 --- a/contracts/scripts/deploy.ts +++ b/contracts/scripts/deploy.ts @@ -2,11 +2,22 @@ import { ethers } from 'hardhat'; // eslint-disable-line import/no-extraneous-de async function main() { // Load the Immutable Runner Tokencontract and get the contract factory - const contractFactory = await ethers.getContractFactory('RunnerToken'); + const contractFactory = await ethers.getContractFactory('RunnerToken1155'); // Deploy the contract to the zkEVM network + const ownerEthAddress = "" + const collectionName = "Immutable Runner Fox 1155" + const baseURI = "https://immutable-runner-json-server-0q8u.onrender.com/fox/" + const contractURI = "https://crimson-specified-vicuna-718.mypinata.cloud/ipfs/QmY7zUHJ6ti3Vg1NPBEjpsNELaMrHvJLwyXQzRd1Pjj68H" const contract = await contractFactory.deploy( - 'YOUR_IMMUTABLE_RUNNER_SKIN_CONTRACT_ADDRESS', // Immutable Runner Skin contract address + ownerEthAddress, + collectionName, + baseURI, + contractURI, // contract / collection metadata URI + "0x6b969FD89dE634d8DE3271EbE97734FEFfcd58eE", // operator allow list https://docs.immutable.com/products/zkevm/contracts/erc721/#operator-allowlist-royalty-and-protocol-fee-enforcement + ownerEthAddress, // royalty recipient + 5, // royalty fee percentage + "0x9b74823f4bcbcb3e0f74cadc3a1d2552a18777ed" // Immutable Runner Skin contract address ); console.log('Contract deployed to:', await contract.getAddress()); diff --git a/mint-backend/dest/index.js b/mint-backend/dest/index.js index 2748d0bcf..532f1083d 100644 --- a/mint-backend/dest/index.js +++ b/mint-backend/dest/index.js @@ -7,6 +7,7 @@ const express_1 = __importDefault(require("express")); const cors_1 = __importDefault(require("cors")); const http_1 = __importDefault(require("http")); const ethers_1 = require("ethers"); +const providers_1 = require("@ethersproject/providers"); const morgan_1 = __importDefault(require("morgan")); const dotenv_1 = __importDefault(require("dotenv")); dotenv_1.default.config(); @@ -16,7 +17,7 @@ app.use(express_1.default.urlencoded({ extended: false })); // Parse request app.use(express_1.default.json()); // Handle JSON app.use((0, cors_1.default)()); // Enable CORS const router = express_1.default.Router(); -const zkEvmProvider = new ethers_1.JsonRpcProvider('https://rpc.testnet.immutable.com'); +const zkEvmProvider = new providers_1.JsonRpcProvider('https://rpc.testnet.immutable.com'); // Contract addresses const foxContractAddress = process.env.FOX_CONTRACT_ADDRESS; const tokenContractAddress = process.env.TOKEN_CONTRACT_ADDRESS; @@ -29,59 +30,67 @@ const gasOverrides = { }; // Mint Immutable Runner Fox router.post('/mint/fox', async (req, res) => { + console.log(req.body); try { if (foxContractAddress && privateKey) { // Get the address to mint the fox to let to = req.body.to ?? null; // Get the quantity to mint if specified, default is one - let quantity = parseInt(req.body.quantity ?? "1"); + let quantity = parseInt(req.body.quantity ?? '1'); // Connect to wallet with minter role const signer = new ethers_1.Wallet(privateKey).connect(zkEvmProvider); // Specify the function to call - const abi = ['function mintByQuantity(address to, uint256 quantity)']; + const abi = ["function mintNFT(address to)"]; // Connect contract to the signer const contract = new ethers_1.Contract(foxContractAddress, abi, signer); - // Mints the number of tokens specified - const tx = await contract.mintByQuantity(to, quantity, gasOverrides); + // Mints 1 fox NFT + const tx = await contract.mintNFT(to, gasOverrides); await tx.wait(); - return res.status(200).json({}); + res.writeHead(200); + res.end(JSON.stringify({ message: "Minted foxes" })); } else { - return res.status(500).json({}); + res.writeHead(400); + res.end(JSON.stringify({ message: "Failed to mint" })); } } catch (error) { console.log(error); - return res.status(400).json({ message: "Failed to mint to user" }); + res.writeHead(500); + res.end(JSON.stringify({ message: error })); } }); // Mint Immutable Runner Token router.post('/mint/token', async (req, res) => { + console.log(req.body); try { if (tokenContractAddress && privateKey) { // Get the address to mint the token to let to = req.body.to ?? null; // Get the quantity to mint if specified, default is one - let quantity = BigInt(req.body.quantity ?? "1"); + let quantity = BigInt(req.body.quantity ?? '1'); // Connect to wallet with minter role const signer = new ethers_1.Wallet(privateKey).connect(zkEvmProvider); // Specify the function to call - const abi = ['function mint(address to, uint256 quantity)']; + const abi = ["function mintCoins(address to, uint256 quantity)"]; // Connect contract to the signer const contract = new ethers_1.Contract(tokenContractAddress, abi, signer); // Mints the number of tokens specified - const tx = await contract.mint(to, quantity, gasOverrides); + const tx = await contract.mintCoins(to, quantity, gasOverrides); await tx.wait(); - return res.status(200).json({}); + res.writeHead(200); + res.end(JSON.stringify({ message: "Minted ERC20 tokens" })); } else { - return res.status(500).json({}); + res.writeHead(400); + res.end(JSON.stringify({ message: "Failed to mint ERC20 tokens" })); } } catch (error) { console.log(error); - return res.status(400).json({ message: "Failed to mint to user" }); + res.writeHead(500); + res.end(JSON.stringify({ message: error })); } }); app.use('/', router); -http_1.default.createServer(app).listen(3000, () => console.log(`Listening on port 3000`)); +http_1.default.createServer(app).listen(3000, () => console.log('Listening on port 3000')); diff --git a/mint-backend/package.json b/mint-backend/package.json index 71406e648..61c35f30a 100644 --- a/mint-backend/package.json +++ b/mint-backend/package.json @@ -10,24 +10,25 @@ }, "devDependencies": { "@types/cors": "^2.8.13", - "@types/express": "^4.17.17", + "@types/express": "^5.0.0", "@types/morgan": "^1.9.9", - "@typescript-eslint/eslint-plugin": "^7.11.0", - "eslint": "^8.56.0", + "@typescript-eslint/eslint-plugin": "^8.8.0", + "eslint": "^9.11.1", "eslint-config-airbnb": "^19.0.4", "eslint-config-airbnb-typescript": "^18.0.0", - "eslint-plugin-import": "^2.29.1", - "eslint-plugin-jsx-a11y": "^6.8.0", - "eslint-plugin-react": "^7.34.2", + "eslint-plugin-import": "^2.30.0", + "eslint-plugin-jsx-a11y": "^6.10.0", + "eslint-plugin-react": "^7.37.1", "eslint-plugin-react-hooks": "^4.6.2", - "typescript": "^5.4.3", - "typescript-eslint": "^7.11.0" + "typescript": "^5.6.2", + "typescript-eslint": "^8.8.0" }, "dependencies": { + "@ethersproject/providers": "^5.7.2", + "@imtbl/sdk": "1.55.0", "cors": "^2.8.5", "dotenv": "^16.4.5", - "ethers": "^6.11.1", - "express": "^4.20.0", + "express": "^5.0.0", "morgan": "^1.10.0" } -} \ No newline at end of file +} diff --git a/mint-backend/src/index.ts b/mint-backend/src/index.ts index 4d4c11037..ac09f9c70 100644 --- a/mint-backend/src/index.ts +++ b/mint-backend/src/index.ts @@ -6,7 +6,8 @@ import express, { } from 'express'; import cors from 'cors'; import http from 'http'; -import { JsonRpcProvider, Wallet, Contract } from 'ethers'; +import { Wallet, Contract } from 'ethers'; +import { JsonRpcProvider } from '@ethersproject/providers'; import morgan from 'morgan'; import dotenv from 'dotenv'; @@ -35,6 +36,7 @@ const gasOverrides = { // Mint Immutable Runner Fox router.post('/mint/fox', async (req: Request, res: Response) => { + console.log(req.body); try { if (foxContractAddress && privateKey) { // Get the address to mint the fox to @@ -46,28 +48,30 @@ router.post('/mint/fox', async (req: Request, res: Response) => { const signer = new Wallet(privateKey).connect(zkEvmProvider); // Specify the function to call - const abi = ['function mintByQuantity(address to, uint256 quantity)']; + const abi = ["function mintNFT(address to)"]; // Connect contract to the signer const contract = new Contract(foxContractAddress, abi, signer); - // Mints the number of tokens specified - const tx = await contract.mintByQuantity(to, quantity, gasOverrides); + // Mints 1 fox NFT + const tx = await contract.mintNFT(to, gasOverrides); await tx.wait(); - return res.status(200).json({}); + res.writeHead(200); + res.end(JSON.stringify({ message: "Minted foxes" })); } else { - return res.status(500).json({}); + res.writeHead(400); + res.end(JSON.stringify({ message: "Failed to mint" })); } - } catch (error) { console.log(error); - return res.status(400).json({ message: 'Failed to mint to user' }); + res.writeHead(500); + res.end(JSON.stringify({ message: error })); } -}, -); +}); // Mint Immutable Runner Token router.post('/mint/token', async (req: Request, res: Response) => { + console.log(req.body); try { if (tokenContractAddress && privateKey) { // Get the address to mint the token to @@ -79,29 +83,30 @@ router.post('/mint/token', async (req: Request, res: Response) => { const signer = new Wallet(privateKey).connect(zkEvmProvider); // Specify the function to call - const abi = ['function mint(address to, uint256 quantity)']; + const abi = ["function mintCoins(address to, uint256 quantity)"]; // Connect contract to the signer const contract = new Contract(tokenContractAddress, abi, signer); // Mints the number of tokens specified - const tx = await contract.mint(to, quantity, gasOverrides); + const tx = await contract.mintCoins(to, quantity, gasOverrides); await tx.wait(); - return res.status(200).json({}); + res.writeHead(200); + res.end(JSON.stringify({ message: "Minted ERC20 tokens" })); } else { - return res.status(500).json({}); + res.writeHead(400); + res.end(JSON.stringify({ message: "Failed to mint ERC20 tokens" })); } - } catch (error) { console.log(error); - return res.status(400).json({ message: 'Failed to mint to user' }); + res.writeHead(500); + res.end(JSON.stringify({ message: error })); } -}, -); +}); app.use('/', router); http.createServer(app).listen( 3000, () => console.log('Listening on port 3000'), -); \ No newline at end of file +); diff --git a/mint-backend/tsconfig.json b/mint-backend/tsconfig.json index c8b808b5b..6fd82388b 100644 --- a/mint-backend/tsconfig.json +++ b/mint-backend/tsconfig.json @@ -1,7 +1,7 @@ { "compilerOptions": { "target": "ES2020", - "module": "Node16", + "module": "CommonJS", "esModuleInterop": true, "outDir": "./dest", "skipLibCheck": true,