@mizuwallet-sdk/core
Version:
563 lines (493 loc) • 12.8 kB
text/typescript
import { Network } from '@aptos-labs/ts-sdk';
import { Base64 } from '@mizuwallet-sdk/protocol';
import request from 'graphql-request';
import { decodeJWT } from '../helpers/JWTHelper';
import {
CheckUserIsExistQueryByTgId,
TGLoginMutation,
TGLoginWithShortIDMutation,
TGWidgetLoginMutation,
TGWidgetLoginWithShortIDMutation,
UserWalletAddressQuery,
bindGoogleQuery,
confirmOrderQuery,
createOrderMutation,
createOrderWithCodeMutation,
createSignMessageMutation,
createSignatureMutation,
fetchOrderListQuery,
fetchOrderQuery,
simulateOrderQuery,
} from '../query';
import { payloadRebuild } from '../utils';
import { ORDER_STATUS } from './config/enum';
export const GRAPHQL_URL: Record<'mainnet' | 'testnet', string> = {
testnet: 'https://hasura-wallet.mizu.one/v1/graphql',
mainnet: 'https://api.mz.xyz/v1/graphql/',
};
export type JWTToken = string;
interface PaginationSettings {
limit?: number;
offset?: number;
}
const KEYLESS_GOOGLE_SITE_URL = 'https://mizu.io/keyless_google';
/**
* MizuWallet SDK Core Client
*
*
*
*/
export class Mizu {
appId: string;
network: Network.MAINNET | Network.TESTNET;
graphqlEndPoint: string = '';
userId: string = '';
jwtToken: string = '';
initialized: boolean = false;
/**
* Initialize MizuWallet SDK Core Client
*
* @param args.appId - Application ID
* @param args.network - Network.MAINNET | Network.TESTNET
*/
constructor(args: { appId: string; network: Network.MAINNET | Network.TESTNET }) {
if (!args.appId) throw new Error('appId is required');
if (!args.network) throw new Error('network is required');
this.appId = args.appId;
this.network = args.network;
this.graphqlEndPoint = GRAPHQL_URL[this.network];
// after all
this.initialized = true;
}
/**
* Check if MizuWallet SDK Core Client is initialized
*/
private checkInitialized() {
if (!this.initialized) throw new Error('MizuWallet SDK Core Client not initialized');
}
/**
* Check if JWT Token is available
*/
private checkJWTToken() {
if (!this.jwtToken) throw new Error('JWT Token not found. Please login first.');
}
/**
* Decode JWT Token
*/
static decodeJWTToken(tokenStr: string) {
const [userId, jwt]: any = decodeJWT(tokenStr);
return [userId, jwt];
}
/**
* Clone MizuWallet SDK Core Client
*
* @param args.appId - Application ID
* @param args.network - Network.MAINNET | Network.TESTNET
* @param args.jwtToken - JWT Token
*/
static clone(args: {
appId: string;
network: Network.MAINNET | Network.TESTNET;
jwtToken: string;
}) {
if (!args.appId) throw new Error('appId is required');
if (!args.network) throw new Error('network is required');
if (!args.jwtToken) throw new Error('jwtToken is required');
const clone: Mizu = new Mizu({ appId: args.appId, network: args.network });
[clone.userId, clone.jwtToken] = Mizu.decodeJWTToken(args.jwtToken);
return clone;
}
/**
* Update network
* @param network - Network.MAINNET | Network.TESTNET
*/
updateNetwork(network: Network.MAINNET | Network.TESTNET) {
this.checkInitialized();
this.network = network;
this.graphqlEndPoint = GRAPHQL_URL[this.network];
}
/**
* Get network info
*/
get networkInfo() {
return {
name: this.network,
chainId: this.network === Network.MAINNET ? 1 : 2,
url:
this.network === Network.MAINNET
? 'https://fullnode.mainnet.aptoslabs.com/v1'
: 'https://fullnode.testnet.aptoslabs.com/v1',
};
}
/**
*
*
*/
login() {
this.checkInitialized();
}
/**
* Login in TG
*
* @param data - initial data of TG, or stringified widget user object
* @param opt.isWidget - is from login widget
*/
async loginInTG(data: string, opt?: { isWidget?: boolean; shortID?: string }) {
this.checkInitialized();
// if isWidget, then use TGWidgetLoginMutation
let tokenStr = '';
if (opt?.isWidget) {
const result: any = await request({
url: this.graphqlEndPoint,
document: opt?.shortID ? TGWidgetLoginWithShortIDMutation : TGWidgetLoginMutation,
variables: {
appId: this.appId,
authData: Base64.encode(data),
...(opt?.shortID ? { shortId: opt.shortID } : {}),
},
});
tokenStr = result.tgWidgetLogin;
} else {
const result: any = await request({
url: this.graphqlEndPoint,
document: opt?.shortID ? TGLoginWithShortIDMutation : TGLoginMutation,
variables: {
appId: this.appId,
initData: data,
...(opt?.shortID ? { shortId: opt.shortID } : {}),
},
});
tokenStr = result.tgLogin;
}
try {
[this.userId, this.jwtToken] = Mizu.decodeJWTToken(tokenStr);
} catch (e) {
this.logout();
}
}
/**
* Check if user exist by TG ID
*
* @param tgId
* @returns
*/
async isUserExistByTgID(tgId: string) {
this.checkInitialized();
if (!tgId) throw new Error('tgId is required');
const result: any = await request({
url: this.graphqlEndPoint,
document: CheckUserIsExistQueryByTgId,
variables: {},
requestHeaders: {
'x-hasura-tg-id': tgId,
},
});
return result?.telegramUser?.length > 0;
}
/**
* fetch user wallet address
*
* @returns
*/
async getUserWalletAddress() {
this.checkInitialized();
this.checkJWTToken();
const result: any = await request({
url: this.graphqlEndPoint,
document: UserWalletAddressQuery,
variables: {
id: this.userId,
},
requestHeaders: {
Authorization: `Bearer ${this.jwtToken}`,
},
});
return result.walletUserByPk.sub_wallets[0].address;
}
/**
* fetch user wallet address
*
* @returns
*/
async getUserInfo() {
this.checkInitialized();
this.checkJWTToken();
const result: any = await request({
url: this.graphqlEndPoint,
document: UserWalletAddressQuery,
variables: {
id: this.userId,
},
requestHeaders: {
Authorization: `Bearer ${this.jwtToken}`,
},
});
return result.walletUserByPk;
}
/**
* Logout
*/
logout() {
this.userId = '';
this.jwtToken = '';
}
/**
*
* @param args.redirect_uri
*/
async startBindGoogle(args: { redirect_uri: string }) {
this.checkInitialized();
this.checkJWTToken();
const urlSearchParams = new URLSearchParams({
token: this.jwtToken,
appId: this.appId,
...args,
});
window.open(`${KEYLESS_GOOGLE_SITE_URL}?${urlSearchParams.toString()}`, '_blank');
}
/**
*
* @param args.address keyless address
* @param args.idToken google jwt
* @returns
*/
async bindGoogleAccount(args: { address: string; idToken: string }) {
this.checkInitialized();
this.checkJWTToken();
const result: any = await request({
url: this.graphqlEndPoint,
document: bindGoogleQuery,
variables: {
...args,
},
requestHeaders: {
Authorization: `Bearer ${this.jwtToken}`,
},
});
return result;
}
/**
* Simulate Order
*
* @param args.payload TransactionPayload
*/
async simulateOrder(args: { payload: any }) {
this.checkInitialized();
this.checkJWTToken();
const result: any = await request({
url: this.graphqlEndPoint,
document: simulateOrderQuery,
variables: {
payload: Base64.encode(payloadRebuild(args.payload)),
},
requestHeaders: {
Authorization: `Bearer ${this.jwtToken}`,
},
});
return result?.simulateOrder;
}
/**
* Create Order
*
* @param args.payload TransactionPayload
* @returns
*/
async createOrder(args: { payload: any }) {
this.checkInitialized();
this.checkJWTToken();
const result: any = await request({
url: this.graphqlEndPoint,
document: createOrderMutation,
variables: {
appId: this.appId,
payload: Base64.encode(payloadRebuild(args.payload)),
},
requestHeaders: {
Authorization: `Bearer ${this.jwtToken}`,
},
});
return result?.createOrder;
}
/**
* Create Order with Code
*
* @param args.payload
* @param args.code
* @returns
*/
async createOrderWithCode(args: { payload: any; code: string }) {
this.checkInitialized();
const result: any = await request({
url: this.graphqlEndPoint,
document: createOrderWithCodeMutation,
variables: {
appId: this.appId,
authCode: args.code,
payload: Base64.encode(payloadRebuild(args.payload)),
},
requestHeaders: {},
});
return result?.createOrderWithCode;
}
/**
* Create Signature
*
* @param args.transactionHex AnyRawTransaction.bscToHex().toStringWithoutPrefix()
* @returns
*/
async createSignature(args: { transactionHex: any }) {
this.checkInitialized();
this.checkJWTToken();
const result: any = await request({
url: this.graphqlEndPoint,
document: createSignatureMutation,
variables: {
appId: this.appId,
transactionHex: args.transactionHex,
},
requestHeaders: {
Authorization: `Bearer ${this.jwtToken}`,
},
});
return result?.createSignature;
}
/**
* Sign Message
*
* @param args.message message to sign
* @param args.nonce nonce
* @returns
*/
async signMessage(args: { message: string; nonce: string }) {
this.checkInitialized();
this.checkJWTToken();
const result: any = await request({
url: this.graphqlEndPoint,
document: createSignMessageMutation,
variables: {
appId: this.appId,
...args,
},
requestHeaders: {
Authorization: `Bearer ${this.jwtToken}`,
},
});
return result?.createSignMessage;
}
/**
* User interactive
*
* @param args.orderId Order ID
* return bool
*/
async confirmOrder(args: { orderId: string }) {
this.checkInitialized();
this.checkJWTToken();
const result: any = await request({
url: this.graphqlEndPoint,
document: confirmOrderQuery,
variables: {
orderId: args.orderId,
},
requestHeaders: {
Authorization: `Bearer ${this.jwtToken}`,
},
});
return result?.confirmOrder;
}
/**
* Fetch Order By ID
*
* @param args.id order.id
* @returns
*/
async fetchOrder(args: { id: string }) {
this.checkInitialized();
this.checkJWTToken();
const result: any = await request({
url: this.graphqlEndPoint,
document: fetchOrderQuery,
variables: {
id: args.id,
},
requestHeaders: {
Authorization: `Bearer ${this.jwtToken}`,
},
});
if (!result.orderByPk) throw new Error('Order not found');
return {
...result.orderByPk,
payload: result.orderByPk.decodedPayload,
};
}
/**
* Wait for order
*
* @param args.orderId order.id
* @returns
*/
async waitForOrder(args: { orderId: string }) {
let order = await this.fetchOrder({
id: args.orderId,
});
let MAX_RETRY = 20;
while (
MAX_RETRY-- > 0 &&
![ORDER_STATUS.SUCCESS, ORDER_STATUS.FAIL, ORDER_STATUS.CANCELED].includes(order.status)
) {
await new Promise((resolve) => setTimeout(resolve, 5000));
order = await this.fetchOrder({
id: args.orderId,
});
}
if (order.status === ORDER_STATUS.FAIL) {
throw new Error('Order failed');
}
return order;
}
/**
* fetch order list
*
* @param args.walletUserId
* @param args.limit
* @param args.offset
*
* @returns
*/
async fetchOrderList(fetchParams?: PaginationSettings & { status: ORDER_STATUS[] }) {
this.checkInitialized();
this.checkJWTToken();
const { limit = 10, offset = 0 } = fetchParams || {};
const result: any = await request({
url: this.graphqlEndPoint,
document: fetchOrderListQuery,
variables: {
walletUserId: this.userId,
limit,
offset,
},
requestHeaders: {
Authorization: `Bearer ${this.jwtToken}`,
},
});
return {
data: result?.order.map((order: any) => {
let payloadStruct = {};
try {
payloadStruct = order.decodedPayload;
} catch (error) {
console.error(error);
}
return {
...order,
payload: payloadStruct,
};
}),
pagination: {
total: result?.orderAggregate.aggregate.count,
limit,
offset,
},
};
}
}