diff --git a/.env.development b/.env.development index 860c27a..71a03e7 100644 --- a/.env.development +++ b/.env.development @@ -48,4 +48,9 @@ GAME_PAY_CB_URL=https://game2006api-test.kingsome.cn/webapp/index.php?c=Shop&a=b WALLET_CLIENT_SK='38d9baa24aaea6f87a1caa51f588b0c9578368a1cb00b1639eb9f450b6cada00' # 检查guest能否绑定平台账号 -GAME_CHECK_RELATION_URL='https://game2006api-test.kingsome.cn/webapp/index.php?c=AccountVerify&a=canBind' \ No newline at end of file +GAME_CHECK_RELATION_URL='https://game2006api-test.kingsome.cn/webapp/index.php?c=AccountVerify&a=canBind' + +OKX_API_KEY='5cda794d-b2af-479c-bd75-af1eb877d4ef' +OKX_PROJECT_ID='17c69bdda138a6342f9bece529030cbb' +OKX_PASS='7654321Cf_' +OKX_SECRET_KEY='AF7F4CEE2A10715F9709D38452CE0BFD' diff --git a/src/api.server.ts b/src/api.server.ts index 435a5fe..d715ebd 100644 --- a/src/api.server.ts +++ b/src/api.server.ts @@ -44,7 +44,7 @@ export class ApiServer { expiresIn: config.api.token_expiresIn, }) if (process.env.NODE_ENV !== 'production') { - mongoose.set('debug', true) + // mongoose.set('debug', true) this.server.register(require('@fastify/cors'), {}) } } diff --git a/src/controllers/okx.controller.ts b/src/controllers/okx.controller.ts new file mode 100644 index 0000000..4c6a0c9 --- /dev/null +++ b/src/controllers/okx.controller.ts @@ -0,0 +1,156 @@ +import BaseController, { ROLE_ANON } from 'common/base.controller' +import { ZError } from 'common/ZError' +import { role, router } from 'decorators/router' +import logger from 'logger/logger' +import { TranRecord } from 'modules/TranRecord' +import { Wallet } from 'modules/Wallet' +import { + addCoin, + createWallet, + ensureTxhash, + getAddresses, + getGasPrice, + getSignInfo, + queryCoin, + queryTranDetail, + queryTranHistory, + sendTran, +} from 'service/okx.svr' + +const DEFAULT_CHAINID = 42161 +class OkxController extends BaseController { + // @role(ROLE_ANON) + // @router('post /wallet/okx/gasprice') + async getPrice(req, res) { + let { chain } = req.params + chain = chain || DEFAULT_CHAINID + let { data } = await getGasPrice(chain) + if (data.code) { + throw new ZError(data.code, data.message) + } + if (!data.data || data.data.length === 0) { + throw new ZError(100, 'no data') + } + return data.data[0] + } + + // @role(ROLE_ANON) + // @router('post /wallet/okx/signinfo') + async signInfo(req, res) { + let { signData } = req.params + let { data } = await getSignInfo(signData) + return data + } + + // @role(ROLE_ANON) + // @router('post /wallet/okx/addcoin') + async addCustomerCoin(req, res) { + let { chainId, address } = req.params + let reqData = { + coins: [ + { + chainId: chainId, + tokenAddress: address, + }, + ], + } + let { data } = await addCoin(reqData) + return data + } + + // @router('post /wallet/okx/bindwallet') + async bindWallet(req, res) { + let user = req.user + let { chainId, address, walletId } = req.params + chainId = chainId || DEFAULT_CHAINID + let data = { + addresses: [ + { + chainId: chainId, + address: address, + }, + ], + walletId, + } + let rep = await createWallet(data) + return rep.data + } + + @router('post /wallet/okx/walletaddress') + async walletAddresses(req, res) { + let { walletId } = req.params + let result = await getAddresses(walletId) + return result.data + } + + @router('post /wallet/okx/walletassets') + async queryWalletCoin(req, res) { + let { coinIds, chainIds, walletId } = req.params + let reqData: any = { + walletId, + } + if (coinIds && coinIds.length > 0) { + reqData.coinIds = coinIds + } + chainIds = chainIds || [DEFAULT_CHAINID] + reqData.chainIds = chainIds + let result = await queryCoin(reqData) + return result.data + } + + @router('post /wallet/okx/transhistory') + async queryWalletTransHistory(req, res) { + let { chainIds, walletId } = req.params + let reqData: any = { + walletId, + limit: 10, + } + chainIds = chainIds || [DEFAULT_CHAINID] + reqData.chainIds = chainIds + let result = await queryTranHistory(reqData) + return result.data + } + + @router('post /wallet/okx/transinfo') + async queryWalletTransInfo(req, res) { + let { chainId, walletId, orderId } = req.params + chainId = chainId || DEFAULT_CHAINID + let result = await queryTranDetail({walletId, orderId, chainId}) + return result.data + } + + @router('post /wallet/okx/sendtran') + async sendWalletTran(req, res) { + let { data } = req.params + let user = req.user; + let record = await Wallet.findOne({ account: user.id }) + if (!record || !record.address) { + throw new ZError(11, 'no wallet found') + } + data.chainId = data.chainId || DEFAULT_CHAINID + data.walletId = record.id; + console.log('send trans: ' + JSON.stringify(data)); + let result = await sendTran(data) + if (result.status !== 200) { + throw new ZError(result.status, result.statusText) + } + if (result.data.code) { + throw new ZError(result.data.code, result.data.msg) + } + if (result.data?.data) { + setImmediate(async () => { + try { + await TranRecord.insertOrUpdate({ + transactionHash: data.txHash, + }, { + okxOrderId: result.data.data.orderId, + }) + logger.log(`success update log: ${data.txHash}`) + } catch (err) { + logger.log(`error update log: ${data.txHash} with error: ${err}`) + } + }); + } + return {txHash: data.txHash} + } +} diff --git a/src/controllers/wallet.controller.ts b/src/controllers/wallet.controller.ts index be3dfea..03c1897 100644 --- a/src/controllers/wallet.controller.ts +++ b/src/controllers/wallet.controller.ts @@ -7,10 +7,26 @@ import { Wallet } from 'modules/Wallet' import { WalletBackup } from 'modules/WalletBackup' import { WalletExt } from 'modules/WalletExt' import { customAlphabet } from 'nanoid' +import { createWallet } from 'service/okx.svr' import { genRandomString, sha3_256, sha512 } from 'utils/security.util' const nanoid = customAlphabet('1234567890abcdef', 10) +const DEFAULT_CHAINID = 42161 + +const bindOkx = async (record: any) => { + console.log('bindOkx: ', record.address) + let res = await createWallet({ + addresses: [{ chainId: DEFAULT_CHAINID, address: record.address}], + walletId: record.id + }) + console.log('bind result: '+ JSON.stringify(res.data)); + if (!res.data.code || res.data.code == 81102) { + record.toOkx = true + await record.save() + } +} + class WalletController extends BaseController { @router('get /wallet/info') async getWalletInfo(req, res) { @@ -24,6 +40,11 @@ class WalletController extends BaseController { record.nweRecord = false await record.save() } + if (!record.toOkx && record.address) { + setImmediate(async () => { + await bindOkx(record) + }); + } Object.assign(data, record.toJson()) return data } @@ -66,6 +87,13 @@ class WalletController extends BaseController { } record.address = address await record.save() + if (!record.toOkx) { + if (!record.toOkx && record.address) { + setImmediate(async () => { + await bindOkx(record) + }); + } + } return {} } diff --git a/src/modules/TranRecord.ts b/src/modules/TranRecord.ts index 5ad0d53..8808758 100644 --- a/src/modules/TranRecord.ts +++ b/src/modules/TranRecord.ts @@ -73,6 +73,9 @@ class TranRecordClass extends BaseModule { @prop() public confirmTime: number + @prop() + public okxOrderId?: string + public toJson() { return { transactionHash: this.transactionHash, diff --git a/src/modules/Wallet.ts b/src/modules/Wallet.ts index 2758b0b..3b2c638 100644 --- a/src/modules/Wallet.ts +++ b/src/modules/Wallet.ts @@ -28,6 +28,9 @@ class WalletClass extends BaseModule { @prop({ required: true, default: true }) public nweRecord: boolean + @prop({default: false}) + public toOkx: boolean + public static async findByAccount(this: ReturnModelType, account: string) { return this.findOne({ account }).exec() } diff --git a/src/service/okx.svr.ts b/src/service/okx.svr.ts new file mode 100644 index 0000000..aeeeee2 --- /dev/null +++ b/src/service/okx.svr.ts @@ -0,0 +1,304 @@ +import axios, { AxiosResponse } from 'axios' +import logger from 'logger/logger' +import { prepareOkxReqCfg } from 'utils/okx.utils'; + +const OKX_BASE = 'https://www.okx.com/api/v5/waas' + +export interface IOkxRes { + code: number + msg: string + data: any +} +// MARK: begin of tranactions +/** + * Get dynamic gas price + * https://www.okx.com/web3/build/docs/waas/api-transaction-get-gas-price + * @param chainId + * @returns + */ +export function getGasPrice(chainId: string|number) { + let config = { + method: 'get', + url: `${OKX_BASE}/transaction/get-gas-price?chainId=${chainId}` + }; + config = prepareOkxReqCfg(config); + return axios.request(config) +} + +/** + * Get data required for signature + * https://www.okx.com/web3/build/docs/waas/api-transaction-get-sign-info + * @param data + * @returns + */ +export function getSignInfo(data: any) { + if (typeof data === 'object') { + data = JSON.stringify(data) + } + let config = { + method: 'post', + url: `${OKX_BASE}/transaction/get-sign-info`, + data + }; + config = prepareOkxReqCfg(config); + return axios.request(config) +} + +/** + * Send transaction + * https://www.okx.com/web3/build/docs/waas/api-transaction-send-transaction + * @param data + * @returns + */ +export function sendTran(data: any): Promise> { + if (typeof data === 'object') { + data = JSON.stringify(data) + } + let config = { + method: 'post', + url: `${OKX_BASE}/transaction/send-transaction`, + data + }; + config = prepareOkxReqCfg(config); + return axios.request(config) +} + +/** + * Query transaction details + * https://www.okx.com/web3/build/docs/waas/api-transaction-get-transaction-detail + * @param data + * @returns + */ +export function queryTranDetail({walletId, orderId, chainId} + :{walletId: string, orderId: string, chainId: string}) { + + let config = { + method: 'get', + url: `${OKX_BASE}/transaction/get-transaction-detail?walletId=${walletId}&orderId=${orderId}&chainId=${chainId}` + }; + config = prepareOkxReqCfg(config); + return axios.request(config) + } + + export async function ensureTxhash({walletId, orderId, chainId} + :{walletId: string, orderId: string, chainId: string}) { + return new Promise(async (resolve, reject) => { + let interReq = setInterval(async () => { + try { + let res = await queryTranDetail({walletId, orderId, chainId}) + let {code, data} = res.data + if (code === 0) { + let {txHash, txStatus} = data + if (txHash && txStatus === 4) { + clearInterval(interReq) + resolve && resolve(txHash) + } else if (txStatus == 3) { + clearInterval(interReq) + reject && reject('trade error') + } + } + } catch (err) { + logger.log('ensureTxhash err', err) + } + }, 1000); + }); + } + + +/** + * Query transaction history + * https://www.okx.com/web3/build/docs/waas/api-transaction-get-transactions-history + * @param data + * @returns + */ + export function queryTranHistory(data: any) { + if (typeof data === 'object') { + data = JSON.stringify(data) + } + let config = { + method: 'post', + url: `${OKX_BASE}/transaction/get-transactions`, + data + }; + config = prepareOkxReqCfg(config); + return axios.request(config) + } + // end of tranactions + // begin of Wallet +/** + * Create wallet + * https://www.okx.com/api/v5/waas/wallet/create-wallet + * @param data + { + "addresses": [ + { + "chainId": "1", + "address": "0x238193be9e80e68eace3588b45d8cf4a7eae0fa3" + }, + { + "chainId": "56", + "address": "0x238193be9e80e68eace3588b45d8cf4a7eae0fa3" + }, + { + "chainId": "66", + "address": "0x238193be9e80e68eace3588b45d8cf4a7eae0fa3" + } + ], + "walletId": "13886e05-1265-4b79-8ac3-b7ab46211001" + } + * @returns + */ + export function createWallet(data: any) { + if (typeof data === 'object') { + data = JSON.stringify(data) + } + let config = { + method: 'post', + url: `${OKX_BASE}/wallet/create-wallet`, + data + }; + config = prepareOkxReqCfg(config); + return axios.request(config) + } + +/** + * Sync wallet other chain addresses + * https://www.okx.com/web3/build/docs/waas/api-wallet-synchronize-wallet-address + * @param data + { + "addresses": [ + { + "chainId": "1", + "address": "0x238193be9e80e68eace3588b45d8cf4a7eae0fa3" + }, + { + "chainId": "56", + "address": "0x238193be9e80e68eace3588b45d8cf4a7eae0fa3" + }, + { + "chainId": "66", + "address": "0x238193be9e80e68eace3588b45d8cf4a7eae0fa3" + }, + { + "chainId": "137", + "address": "0x238193be9e80e68eace3588b45d8cf4a7eae0fa3" + } + ], + "walletId": "13886e05-1265-4b79-8ac3-b7ab46211001" + } + * @returns + */ + export function syncAddress(data: any) { + if (typeof data === 'object') { + data = JSON.stringify(data) + } + let config = { + method: 'post', + url: `${OKX_BASE}/wallet/sync-address`, + data + }; + config = prepareOkxReqCfg(config); + return axios.request(config) + } +/** + * Query current wallet address + * https://www.okx.com/web3/build/docs/waas/api-wallet-get-wallet-address + * @returns + */ + export function getAddresses(walletId: string) { + let config = { + method: 'get', + url: `${OKX_BASE}/wallet/get-addresses?walletId=${walletId}` + }; + config = prepareOkxReqCfg(config); + return axios.request(config) + } + + // end of Wallet + // begin of Assets +/** + * Query all supported coin information + * https://www.okx.com/web3/build/docs/waas/api-asset-get-all-coins + * @param type 0: Platform coin, 1: Custom token + */ + export function getAllCoins(type: string) { + let config = { + method: 'get', + url: `${OKX_BASE}/asset/get-all-coins?type=${type}` + }; + config = prepareOkxReqCfg(config); + return axios.request(config) + } +/** + * Add custom coins + * https://www.okx.com/web3/build/docs/waas/api-asset-add-coin + * @param data : + { + "coins":[ + { + "chainId":1, + "tokenAddress":"0x4CEdA7906a5Ed2179785Cd3A40A69ee8bc99C466" + } + ] + } + * @returns + */ + export function addCoin(data: any) { + if (typeof data === 'object') { + data = JSON.stringify(data) + } + let config = { + method: 'post', + url: `${OKX_BASE}/asset/add-coin`, + data + }; + config = prepareOkxReqCfg(config); + return axios.request(config) + } + +/** + * Delete custom coin + * https://www.okx.com/web3/build/docs/waas/api-asset-delete-coin + * @param data + { + "chainId": 1, + "tokenAddress": "0x4CEdA7906a5Ed2179785Cd3A40A69ee8bc99C466" + } + * @returns + */ + export function removeCoin(data: any) { + if (typeof data === 'object') { + data = JSON.stringify(data) + } + let config = { + method: 'post', + url: `${OKX_BASE}/asset/del-coin`, + data + }; + config = prepareOkxReqCfg(config); + return axios.request(config) + } + +/** + * Query wallet token assets + * https://www.okx.com/web3/build/docs/waas/api-asset-get-wallet-asset + * @param data + { + "walletId": "13886e05-1265-4b79-8ac3-b7ab46211001", + "coinIds":["3"] + } + * @returns + */ + export function queryCoin(data: any) { + if (typeof data === 'object') { + data = JSON.stringify(data) + } + let config = { + method: 'post', + url: `${OKX_BASE}/asset/get-assets`, + data + }; + config = prepareOkxReqCfg(config); + return axios.request(config) + } + // end of assets diff --git a/src/utils/okx.utils.ts b/src/utils/okx.utils.ts new file mode 100644 index 0000000..4cbb685 --- /dev/null +++ b/src/utils/okx.utils.ts @@ -0,0 +1,31 @@ +import crypto from 'crypto' + +const apiKey = process.env.OKX_API_KEY; +const projectId = process.env.OKX_PROJECT_ID; +const pass = process.env.OKX_PASS; +const secretKey = process.env.OKX_SECRET_KEY; + + +export function prepareOkxReqCfg(config: any) { + let timestamp = new Date().toISOString(); + let url = new URL(config.url); + let method = config.method.toUpperCase(); + let signStr = timestamp + method + url.pathname; + if (method === 'GET') { + signStr += url.search + } else if (method === 'POST') { + let bodyStr = JSON.stringify(JSON.parse(config.data)) + signStr+=bodyStr; + } + const mac = crypto.createHmac('sha256', secretKey); + const sign = mac.update(signStr).digest('base64') + config.headers = { + 'OK-ACCESS-PROJECT': projectId, + 'OK-ACCESS-KEY': apiKey, + 'OK-ACCESS-SIGN': sign, + 'OK-ACCESS-TIMESTAMP': timestamp, + 'OK-ACCESS-PASSPHRASE': pass, + 'Content-Type': 'application/json' + } + return config; +} \ No newline at end of file