From 2e468838b68f7b504068d2bba65e237455898180 Mon Sep 17 00:00:00 2001
From: Kai Hirota <34954529+kaihirota@users.noreply.github.com>
Date: Fri, 11 Oct 2024 14:59:11 +1100
Subject: [PATCH 1/4] update
---
Assets/Shared/Scripts/SequenceManager.cs | 3 -
Assets/Shared/Scripts/UI/MintScreen.cs | 138 +++++++++++++++++-
.../Shared/Scripts/UI/UnlockedSkinScreen.cs | 54 +++++--
contracts/contracts/RunnerToken.sol | 2 +-
mint-backend/dest/index.js | 29 ++--
mint-backend/package.json | 23 +--
mint-backend/src/index.ts | 33 +++--
mint-backend/tsconfig.json | 2 +-
8 files changed, 228 insertions(+), 56 deletions(-)
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/MintScreen.cs b/Assets/Shared/Scripts/UI/MintScreen.cs
index c13f50f8b..8abec9090 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 = BigInteger.Multiply(new BigInteger(coinsCollected), BigInteger.Pow(10, 18));
+ 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/UnlockedSkinScreen.cs b/Assets/Shared/Scripts/UI/UnlockedSkinScreen.cs
index 0b563d450..51949f5d6 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 = "0xd14983206B7f2348b976878cCF097E55E1461977";
- 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/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/mint-backend/dest/index.js b/mint-backend/dest/index.js
index 2748d0bcf..d0c499cd0 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,12 +30,13 @@ 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
@@ -44,25 +46,29 @@ router.post('/mint/fox', async (req, res) => {
// Mints the number of tokens specified
const tx = await contract.mintByQuantity(to, quantity, 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
@@ -72,16 +78,19 @@ router.post('/mint/token', async (req, res) => {
// Mints the number of tokens specified
const tx = await contract.mint(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..61e75b77f 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
@@ -54,20 +56,22 @@ router.post('/mint/fox', async (req: Request, res: Response) => {
const tx = await contract.mintByQuantity(to, quantity, 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
@@ -87,21 +91,22 @@ router.post('/mint/token', async (req: Request, res: Response) => {
const tx = await contract.mint(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,
From 1d9c501b6cc19e1fcb3fee83e240086bea10a81f Mon Sep 17 00:00:00 2001
From: Kai Hirota <34954529+kaihirota@users.noreply.github.com>
Date: Mon, 14 Oct 2024 11:26:32 +1100
Subject: [PATCH 2/4] add clientID
---
Assets/Shared/Scripts/UI/MainMenu.cs | 47 ++++++++++++++++++++++++++--
1 file changed, 44 insertions(+), 3 deletions(-)
diff --git a/Assets/Shared/Scripts/UI/MainMenu.cs b/Assets/Shared/Scripts/UI/MainMenu.cs
index 39c0643c0..a6bba2ed6 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,41 @@ 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;
+
+#if (UNITY_ANDROID && !UNITY_EDITOR_WIN) || (UNITY_IPHONE && !UNITY_EDITOR_WIN) || UNITY_STANDALONE_OSX
+ redirectUri = "immutablerunner://callback";
+ logoutUri = "immutablerunner://logout";
+#endif
+ 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 +85,7 @@ void OnStartButtonClick()
AudioManager.Instance.PlayEffect(SoundID.ButtonSound);
}
- void OnLogoutButtonClick()
+ async void OnLogoutButtonClick()
{
try
{
@@ -59,6 +95,11 @@ void OnLogoutButtonClick()
ShowLoading(true);
// Logout
+#if (UNITY_ANDROID && !UNITY_EDITOR_WIN) || (UNITY_IPHONE && !UNITY_EDITOR_WIN) || UNITY_STANDALONE_OSX
+ await passport.LogoutPKCE();
+#else
+ await passport.Logout();
+#endif
// Reset the login flag
SaveManager.Instance.IsLoggedIn = false;
From 4aba9713a7d59d680dc81d13600df5dcea18e358 Mon Sep 17 00:00:00 2001
From: Kai Hirota <34954529+kaihirota@users.noreply.github.com>
Date: Mon, 14 Oct 2024 13:17:59 +1100
Subject: [PATCH 3/4] fix login
---
.../Shared/Scripts/UI/LevelCompleteScreen.cs | 126 +++++++++++++++++-
Assets/Shared/Scripts/UI/MainMenu.cs | 8 --
2 files changed, 119 insertions(+), 15 deletions(-)
diff --git a/Assets/Shared/Scripts/UI/LevelCompleteScreen.cs b/Assets/Shared/Scripts/UI/LevelCompleteScreen.cs
index e98d46263..ec9340835 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 = BigInteger.Multiply(new BigInteger(m_CoinCount), BigInteger.Pow(10, 18));
+ 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 a6bba2ed6..970fa6ed5 100644
--- a/Assets/Shared/Scripts/UI/MainMenu.cs
+++ b/Assets/Shared/Scripts/UI/MainMenu.cs
@@ -43,10 +43,6 @@ async void OnEnable()
string redirectUri = null;
string logoutUri = null;
-#if (UNITY_ANDROID && !UNITY_EDITOR_WIN) || (UNITY_IPHONE && !UNITY_EDITOR_WIN) || UNITY_STANDALONE_OSX
- redirectUri = "immutablerunner://callback";
- logoutUri = "immutablerunner://logout";
-#endif
passport = await Passport.Init(clientId, environment, redirectUri, logoutUri);
// Check if the player is supposed to be logged in and if there are credentials saved
@@ -95,11 +91,7 @@ async void OnLogoutButtonClick()
ShowLoading(true);
// Logout
-#if (UNITY_ANDROID && !UNITY_EDITOR_WIN) || (UNITY_IPHONE && !UNITY_EDITOR_WIN) || UNITY_STANDALONE_OSX
- await passport.LogoutPKCE();
-#else
await passport.Logout();
-#endif
// Reset the login flag
SaveManager.Instance.IsLoggedIn = false;
From 35a317dea7d618bc4dcdd8b7ec071e420d0a1ba7 Mon Sep 17 00:00:00 2001
From: Kai Hirota <34954529+kaihirota@users.noreply.github.com>
Date: Wed, 23 Oct 2024 12:59:47 +1100
Subject: [PATCH 4/4] fix
---
Assets/Shared/Scripts/UI/SetupWalletScreen.cs | 7 +++--
Packages/manifest.json | 2 ++
Packages/packages-lock.json | 26 ++++++++++++++++++-
3 files changed, 32 insertions(+), 3 deletions(-)
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/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"