UNPKG

myria-core-sdk

Version:

Latest version SDK

428 lines (413 loc) 33.1 kB
import { ethers } from "ethers"; import Web3 from "web3"; import { CommonAPI, UserAPI } from "../core/apis"; import { ETH_REQUEST_ACCOUNTS } from "../utils/Constants"; import { SIGN_MESSAGE } from "./CommonModule"; const StarkwareLib = require("@starkware-industries/starkware-crypto-utils"); const keyDerivation = StarkwareLib.keyDerivation; const METAMASK_MESSAGE_SIGNATURE = `Welcome to Myria!\n\nSelect 'Sign' to create and sign in to your Myria account.\n\nThis request will not trigger a blockchain transaction or cost any gas fees.\n\n`; /** * Create WalletManager instance object * @class WalletManager * @param {EnvTypes} env Environment types enum params (Ex: EnvTypes.DEV / EnvTypes.STAGING / EnvTypes.PREPROD / EnvTypes.PROD) */ export class WalletManager { constructor(env) { if (typeof Web3.givenProvider === undefined) { throw new Error("Metamask is required to install. Please install the metamask"); } this.userApi = new UserAPI(env); this.commonApi = new CommonAPI(env); this.rawProvider = Web3.givenProvider; this.web3 = new Web3(this.rawProvider); this.web3Provider = new ethers.providers.Web3Provider(this.rawProvider); this.accounts = []; this.currentAccount = ""; } /** * @private * @description Allow to generate the signature for registration process with server time * @param {string} serverTime milliseconds as string * @returns {string} a string as the message for metamask's signing */ generateSignatureAccount(serverTime) { return `${METAMASK_MESSAGE_SIGNATURE}${JSON.stringify({ created_on: serverTime, })}`; } /** * @private * @description Perform the register account through account services * @param walletAddress * @returns {UserWalletApi} Return user's wallet API response */ async registerAccount(walletAddress, userType, referrerId) { if (!walletAddress) { throw new Error("Wallet address is required"); } if (userType && !referrerId) { throw new Error('ReferrerId is required!'); } const serverTime = await this.commonApi.getTimeFromMyriaverse(); const message = this.generateSignatureAccount(serverTime.data.time); const signature = await this.web3.eth.personal.sign(message, walletAddress, ""); const registerData = { wallet_id: this.currentAccount, signature, message, userType, referrerId }; const registerResponse = await this.commonApi.registerUser(registerData); return registerResponse; } /** * @private * @description Register user in L2 system * @param * userType: Partner/Customer * referrerID: starkKey/projectID-gameID * @returns User data information in L2 */ async registerL2User(userType, referrerId) { if (userType && !referrerId) { throw new Error('ReferrerId is required!'); } const msgHash = StarkwareLib.pedersen([ "UserRegistration:", this.currentAccount, ]); const walletSignature = await this.web3.eth.personal.sign(SIGN_MESSAGE, this.currentAccount, ""); const privateStarkKeyInternal = keyDerivation.getPrivateKeyFromEthSignature(walletSignature); const keyPair = StarkwareLib.ec.keyFromPrivate(privateStarkKeyInternal, "hex"); const starkKey = keyDerivation.privateToStarkKey(privateStarkKeyInternal); const pubKey = StarkwareLib.ec.keyFromPublic(keyPair.getPublic(true, "hex"), "hex"); const pureStarkSignature = StarkwareLib.sign(keyPair, msgHash); const verify = StarkwareLib.verify(pubKey, msgHash, pureStarkSignature); if (!verify) { throw new Error("Stark signature generate error - please recheck the data"); } const starkSignature = { r: `0x${pureStarkSignature.r.toJSON()}`, s: `0x${pureStarkSignature.s.toJSON()}`, }; const payload = { ethAddress: this.currentAccount, signature: starkSignature, starkKey: `0x${starkKey}`, userType, referrerId }; const registerResult = await this.userApi.registerUser(payload); return registerResult.data; } /** * @description Perform the connection with current connected metamask account with browser's web session * @returns {RegisteredUserData} Return the new users (wallet address, details info for wallet) * @throws {string} Exception: Need to select and connect to metamask accounts * @example <caption>Sample code</caption> * // Sample code on staging: const walletManager = new WalletManager(EnvTypes.STAGING); const data = await walletManager.connect(); console.log('Data ->', data); // Sample code on Production: const walletManager = new WalletManager(EnvTypes.PRODUCTION); const data = await walletManager.connect(); console.log('Data ->', data); */ async connect() { this.accounts = await this.web3Provider.send(ETH_REQUEST_ACCOUNTS, []); this.currentAccount = this.accounts[0]; if (this.accounts.length === 0) { throw new Error("Need to select and connect to metamask accounts"); } let codeInfo; let checkUserExistResponse; try { checkUserExistResponse = await this.userApi.getUserByWalletAddress(this.currentAccount); codeInfo = "USER_REGISTERED"; } catch (err) { if (err.status === 404) { codeInfo = "USER_NOT_REGISTERED"; } else { codeInfo = "GET_USER_INFO_ERROR"; } } const result = { walletAddress: this.currentAccount, starkKey: (checkUserExistResponse === null || checkUserExistResponse === void 0 ? void 0 : checkUserExistResponse.data.starkKey) || "", codeInfo, }; return result; } /** * @description The function required the wallet, and it performs full registration and normal login to get the user info data * @param {string} walletAddress Required metamask wallet address of user * @param {UserType=} userType Type of user for B2B (PARTNER) and B2C (CUSTOMER) * @param {string=} referrerId Referrer users to onboard to myria system. * In case the type is partner, then the referrerID is PARTNER_GAME_NAME_ID or PROJECT_ID * If type is customer, then the referrerID is stark key of referred's user * @throws {string} Exception: Wallet address is required! * @throws {string} Exception: ReferrerId is required! * @throws {string} Exception: Wallet registration failed: ${INTERNAL_SERVER_ERROR} * @throws {string} Exception: Can't register the user with error: ${INTERNAL_SERVER_ERROR} * @example <caption>Sample code</caption> * // Sample code on staging: const walletManager = new WalletManager(EnvTypes.STAGING); const data = await walletManager.registerAndLoginWithWalletAddress( '0xfb.....', // wallet address UserType.PARTNER, // User type as partner '110', // Testnet (Staging) Project ID for the partner game ); console.log('Testnet Data ->', data); // Sample code on Production: const walletManager = new WalletManager(EnvTypes.PRODUCTION); const data = await walletManager.registerAndLoginWithWalletAddress( '0xfb.....', // wallet address UserType.PARTNER, // User type as partner '10', // Production Project ID for the partner game ); console.log('Production Data ->', data); * @returns {UserDataResponse|undefined} User data response (such as stark key, wallet address, registered signature) */ async registerAndLoginWithWalletAddress(walletAddress, userType, referrerId) { if (!walletAddress) { throw new Error('Wallet address is required!'); } if (userType && !referrerId) { throw new Error('ReferrerId is required!'); } try { const loginResult = await this.userApi.getUserByWalletAddress(walletAddress); return loginResult === null || loginResult === void 0 ? void 0 : loginResult.data; } catch (err) { if (err.status === 404) { const registerRes = await this.registerAccount(this.currentAccount, userType === null || userType === void 0 ? void 0 : userType.toString(), referrerId); if (registerRes.status === "success") { const registerUserData = await this.registerL2User(); return registerUserData; } else { throw new Error(`Wallet registration failed: ${registerRes.data}`); } } else { throw new Error(`Can't register the user with error: ${err}`); } } } /** * @description Perform end to end registration process with metamask connection , connect wallet action and register user in Myria's system * @param {UserType=} userType Type of user (PARTNER / CUSTOMER) * @param {string=} referrerId Game_ID / Project_ID / References Stark Key of another users if userType is customer * @example <caption>Sample code</caption> * // Sample code on staging: const walletManager = new WalletManager(EnvTypes.STAGING); const registerUserData = await walletManager.connectAndLogin( UserType.PARTNER, '110', // Testnet (Staging) Project ID for the partner game ); console.log('Testnet Data ->', registerUserData); // Sample code on Production: const walletManager = new WalletManager(EnvTypes.PRODUCTION); const registerUserData = await walletManager.connectAndLogin( UserType.PARTNER, '10', // Production Project ID for the partner game ); console.log('Production Data ->', registerUserData); * @returns {UserDataResponse|undefined} The details user data response for registration progress (including signature, stark key, wallet address) * @throws {string} Exception: ReferrerId is required! * @throws {string} Exception: Wallet registration failed: ${INTERNAL_SERVER_ERROR} * @throws {string} Exception: Register user failed: ${INTERNAL_SERVER_ERROR} * @throws {string} Exception: Cannot get user information with error: ${INTERNAL_SERVER_ERROR} */ async connectAndLogin(userType, referrerId) { this.accounts = await this.web3Provider.send(ETH_REQUEST_ACCOUNTS, []); this.currentAccount = this.accounts[0]; if (userType && !referrerId) { throw new Error('ReferrerId is required!'); } try { const loginResult = await this.userApi.getUserByWalletAddress(this.currentAccount); return loginResult.data; } catch (err) { if (err.status === 404) { try { const registerRes = await this.registerAccount(this.currentAccount, userType, referrerId); if (registerRes.status === "success") { const registerResult = await this.registerL2User(userType, referrerId); return registerResult; } else { throw new Error(`Wallet registration failed: ${registerRes.data}`); } } catch (error) { throw new Error(`Register user failed: ${error}`); } } else { throw new Error(`Cannot get user information with error: ${err}`); } } } async connectAndLoginV2(userType, referrerId) { this.accounts = await this.web3Provider.send(ETH_REQUEST_ACCOUNTS, []); this.currentAccount = this.accounts[0]; if (userType && !referrerId) { throw new Error('ReferrerId is required!'); } try { const loginResult = await this.userApi.getUserByWalletAddress(this.currentAccount); const serverTime = await this.commonApi.getTimeFromMyriaverse(); const message = this.generateSignatureAccount(serverTime.data.time); const signature = await this.web3.eth.personal.sign(message, this.currentAccount, ""); const loginData = { wallet_id: this.currentAccount, signature, message, userType, referrerId }; const loginWalletResponse = await this.commonApi.loginWalletMyriaverse(loginData); const loggedUserData = { ethAddress: this.currentAccount, starkKey: loginResult.data.starkKey, wallet_id: loginResult.data.ethAddress, access_token: loginWalletResponse.data.access_token, image_url: loginWalletResponse.data.image_url, first_name: loginWalletResponse.data.first_name, last_name: loginWalletResponse.data.last_name, user_id: loginWalletResponse.data.user_id, created_on: loginWalletResponse.data.created_on, email: loginWalletResponse.data.email, username: loginWalletResponse.data.username, referral_code: loginWalletResponse.data.referral_code, session_id: loginWalletResponse.data.session_id }; return loggedUserData; } catch (err) { if (err.status === 404) { try { const registerRes = await this.registerAccount(this.currentAccount, userType, referrerId); if (registerRes.status === "success") { const registerL2Result = await this.registerL2User(userType, referrerId); const registeredUserData = { ethAddress: this.currentAccount, starkKey: (registerL2Result === null || registerL2Result === void 0 ? void 0 : registerL2Result.starkKey) || '', wallet_id: (registerL2Result === null || registerL2Result === void 0 ? void 0 : registerL2Result.ethAddress) || '', access_token: registerRes.data.access_token, image_url: registerRes.data.image_url, first_name: registerRes.data.first_name, last_name: registerRes.data.last_name, user_id: registerRes.data.user_id, created_on: registerRes.data.created_on, email: registerRes.data.email, username: registerRes.data.username, session_id: registerRes.data.session_id }; return registeredUserData; } else { throw new Error(`Wallet registration failed: ${registerRes.data}`); } } catch (error) { throw new Error(`Register user failed: ${error}`); } } else { throw new Error(`Cannot get user information with error: ${err}`); } } } /** * @description Perform the retrieve user wallet by the Stark Key * @param {string} starkKey Stark Key of user in L2 system of Myria * @example <caption>Sample code</caption> * // Sample code on staging: const walletManager = new WalletManager(EnvTypes.STAGING); const starkKey = '0x.....'; const userWalletData = await walletManager.getUserWalletByStarkKey(starkKey); console.log('Testnet Data ->', userWalletData); // Sample code on Production: const walletManager = new WalletManager(EnvTypes.PRODUCTION); const starkKey = '0x.....'; const userWalletData = await walletManager.getUserWalletByStarkKey(starkKey); console.log('Production Data ->', userWalletData); * @returns {UserDataResponse | undefined} The details user data response for registration progress (including signature, stark key, wallet address) * @throws {string} Exception: Stark Key is required! * @throws {string} Http Status Code 404: User 0x... is not registered * @throws {string} Http Status Code 500: Get user data failed - unexpected with internal server error * @throws {string} Http Status Code 500: Internal Server Error with ${Exception} */ async getUserWalletByStarkKey(starkKey) { if (!starkKey) { throw new Error("Stark Key is required"); } let res; try { const registerUserResponse = await this.userApi.getUserByWalletAddress(starkKey); if ((registerUserResponse === null || registerUserResponse === void 0 ? void 0 : registerUserResponse.status) === 'success' && (registerUserResponse === null || registerUserResponse === void 0 ? void 0 : registerUserResponse.data)) { res = registerUserResponse === null || registerUserResponse === void 0 ? void 0 : registerUserResponse.data; } else { throw new Error('Get user data failed - unexpected with internal server error'); } } catch (err) { throw new Error('Internal Server Error with ' + err); } return res; } /** * @description Perform the retrieve full user information by the Wallet address * @param {string} ethAddress The ether wallet address of user (such as Metamask wallet address) * @example <caption>Sample code</caption> * // Sample code on staging: const walletManager = new WalletManager(EnvTypes.STAGING); const ethWalletAddress = '0x.....'; const userWalletData = await walletManager.getUserInfoByWalletAddress(ethWalletAddress); console.log('Testnet Data ->', userWalletData); // Sample code on Production: const walletManager = new WalletManager(EnvTypes.PRODUCTION); const ethWalletAddress = '0x.....'; const userWalletData = await walletManager.getUserInfoByWalletAddress(ethWalletAddress); console.log('Production Data ->', userWalletData); * @returns {UserDataResponse | undefined} The details user data response for registration progress (including signature, stark key, wallet address) * @throws {string} Exception: Eth address is required! * @throws {string} Http Status Code 404: User 0x... is not registered * @throws {string} Http Status Code 500: Get user data failed - unexpected with internal server error * @throws {string} Http Status Code 500: Internal Server Error with ${Exception} */ async getUserInfoByWalletAddress(ethAddress) { if (!ethAddress) { throw new Error("Eth address is required!"); } let res; try { const registerUserResponse = await this.userApi.getUserByWalletAddress(ethAddress); if ((registerUserResponse === null || registerUserResponse === void 0 ? void 0 : registerUserResponse.status) === 'success' && (registerUserResponse === null || registerUserResponse === void 0 ? void 0 : registerUserResponse.data)) { res = registerUserResponse === null || registerUserResponse === void 0 ? void 0 : registerUserResponse.data; } else { throw new Error('Get user data failed - unexpected with internal server error'); } } catch (err) { throw new Error('Internal Server Error with ' + err); } return res; } } //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiV2FsbGV0TWFuYWdlci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uL3NyYy9tb2R1bGVzL1dhbGxldE1hbmFnZXIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQ0EsT0FBTyxFQUFFLE1BQU0sRUFBRSxNQUFNLFFBQVEsQ0FBQztBQUNoQyxPQUFPLElBQUksTUFBTSxNQUFNLENBQUM7QUFDeEIsT0FBTyxFQUFFLFNBQVMsRUFBRSxPQUFPLEVBQUUsTUFBTSxjQUFjLENBQUM7QUFZbEQsT0FBTyxFQUFFLG9CQUFvQixFQUFFLE1BQU0sb0JBQW9CLENBQUM7QUFDMUQsT0FBTyxFQUFFLFlBQVksRUFBRSxNQUFNLGdCQUFnQixDQUFDO0FBRTlDLE1BQU0sWUFBWSxHQUFHLE9BQU8sQ0FBQyw4Q0FBOEMsQ0FBQyxDQUFDO0FBQzdFLE1BQU0sYUFBYSxHQUFHLFlBQVksQ0FBQyxhQUFhLENBQUM7QUFFakQsTUFBTSwwQkFBMEIsR0FBRyxxS0FBcUssQ0FBQztBQUV6TTs7OztHQUlHO0FBQ0gsTUFBTSxPQUFPLGFBQWE7SUFXeEIsWUFBWSxHQUFhO1FBQ3ZCLElBQUksT0FBTyxJQUFJLENBQUMsYUFBYSxLQUFLLFNBQVMsRUFBRTtZQUMzQyxNQUFNLElBQUksS0FBSyxDQUNiLDhEQUE4RCxDQUMvRCxDQUFDO1NBQ0g7UUFFRCxJQUFJLENBQUMsT0FBTyxHQUFHLElBQUksT0FBTyxDQUFDLEdBQUcsQ0FBQyxDQUFDO1FBQ2hDLElBQUksQ0FBQyxTQUFTLEdBQUcsSUFBSSxTQUFTLENBQUMsR0FBRyxDQUFDLENBQUM7UUFDcEMsSUFBSSxDQUFDLFdBQVcsR0FBRyxJQUFJLENBQUMsYUFBYSxDQUFDO1FBQ3RDLElBQUksQ0FBQyxJQUFJLEdBQUcsSUFBSSxJQUFJLENBQUMsSUFBSSxDQUFDLFdBQVcsQ0FBQyxDQUFDO1FBQ3ZDLElBQUksQ0FBQyxZQUFZLEdBQUcsSUFBSSxNQUFNLENBQUMsU0FBUyxDQUFDLFlBQVksQ0FBQyxJQUFJ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