myria-core-sdk
Version:
Latest version SDK
428 lines (413 loc) • 33.1 kB
JavaScript
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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