UNPKG

@akashicpay/sdk

Version:

SDK to interact with the Akashic ecosystem

438 lines 21.7 kB
"use strict"; var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) { if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter"); if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it"); return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver); }; var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) { if (kind === "m") throw new TypeError("Private method is not writable"); if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter"); if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it"); return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value; }; var __rest = (this && this.__rest) || function (s, e) { var t = {}; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) t[p] = s[p]; if (s != null && typeof Object.getOwnPropertySymbols === "function") for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) t[p[i]] = s[p[i]]; } return t; }; var _AkashicPay_otk; Object.defineProperty(exports, "__esModule", { value: true }); exports.AkashicPay = void 0; const activecrypto_1 = require("@activeledger/activecrypto"); const winston_1 = require("winston"); const package_json_1 = require("../package.json"); const akashicChain_1 = require("./akashicChain"); const APACTypes_1 = require("./APACTypes"); const constants_1 = require("./constants"); const error_1 = require("./error"); const FetchHttpClient_1 = require("./FetchHttpClient"); const l1Network_1 = require("./l1Network"); const generate_otk_1 = require("./otk-reconstruction/generate-otk"); const reconstruct_1 = require("./otk-reconstruction/reconstruct"); const types_1 = require("./types"); const async_1 = require("./utils/async"); const currency_1 = require("./utils/currency"); const http_1 = require("./utils/http"); const prefix_1 = require("./utils/prefix"); class AkashicPay { static async build(args) { const { environment, targetNode } = args, initialisationArgs = __rest(args, ["environment", "targetNode"]); const ap = new AkashicPay({ environment, targetNode, }); await ap.init(initialisationArgs); return ap; } get keyBackup() { if (this.env === constants_1.Environment.Production) throw new error_1.AkashicError(error_1.AkashicErrorCode.AccessDenied); return { l2Address: __classPrivateFieldGet(this, _AkashicPay_otk, "f").identity, privateKey: __classPrivateFieldGet(this, _AkashicPay_otk, "f").key.prv.pkcs8pem, raw: __classPrivateFieldGet(this, _AkashicPay_otk, "f"), }; } async payout(recipientId, to, amount, network, token) { let toAddress = to; let initiatedToNonL2 = undefined; let isL2 = false; if (network === types_1.TronSymbol.Tron_Shasta && token === types_1.TokenSymbol.USDT) { token = types_1.TokenSymbol.TETHER; } const decimalAmount = (0, currency_1.convertToSmallestUnit)(amount, network, token); const { l2Address } = await this.lookForL2Address(to, network); if (RegExp(l1Network_1.NetworkDictionary[network].regex.address).exec(to)) { if (l2Address) { toAddress = l2Address; initiatedToNonL2 = to; isL2 = true; } else { } } else if (RegExp(akashicChain_1.L2Regex).exec(to)) { if (!l2Address) { return { error: error_1.AkashicErrorCode.L2AddressNotFound, }; } isL2 = true; } else { if (!l2Address) { return { error: error_1.AkashicErrorCode.L2AddressNotFound, }; } toAddress = l2Address; initiatedToNonL2 = to; isL2 = true; } if (isL2) { const signedL2Tx = await this.akashicChain.l2Transaction({ otk: __classPrivateFieldGet(this, _AkashicPay_otk, "f"), amount: decimalAmount, toAddress, coinSymbol: network, tokenSymbol: token, initiatedToNonL2, identifier: recipientId, }); let l2Tx = signedL2Tx; if (this.isFxBp) { const response = await this.prepareL2Transaction({ signedTx: signedL2Tx }); l2Tx = response.preparedTxn; } const acResponse = await this.post(this.targetNode.node, l2Tx); const chainError = (0, akashicChain_1.checkForAkashicChainError)(acResponse.data); if (chainError) return { error: chainError }; this.logger.info(`Paid out ${amount} ${token} to user ${recipientId} at ${to}`); return { l2Hash: (0, prefix_1.prefixWithAS)(acResponse.data.$umid), }; } else { const payload = { toAddress: to, coinSymbol: network, amount, tokenSymbol: token, identity: __classPrivateFieldGet(this, _AkashicPay_otk, "f").identity, identifier: recipientId, feeDelegationStrategy: APACTypes_1.FeeDelegationStrategy.Delegate, }; let preparedTxn; try { preparedTxn = (await this.post(this.akashicUrl + constants_1.AkashicEndpoints.PrepareTx, payload)).data.preparedTxn; } catch (error) { if (error instanceof Error && error.message.includes('exceeds total savings')) { return { error: error_1.AkashicErrorCode.SavingsExceeded, }; } return { error: error_1.AkashicErrorCode.UnknownError, }; } const signedTxn = await this.akashicChain.signPayoutTransaction(preparedTxn, __classPrivateFieldGet(this, _AkashicPay_otk, "f")); const acResponse = await this.post(this.targetNode.node, signedTxn); const chainError = (0, akashicChain_1.checkForAkashicChainError)(acResponse.data); if (chainError) return { error: chainError }; this.logger.info(`Paid out ${amount} ${token} to user ${recipientId} at $to`); return { l2Hash: (0, prefix_1.prefixWithAS)(acResponse.data.$umid), }; } } async getDepositAddress(network, identifier, referenceId) { var _a; this.logger.info('getDepositAddress called', { identifier }); const { address } = await this.getByOwnerAndIdentifier({ identifier, coinSymbol: network, }); if (address) { if (referenceId) { const payloadToSign = { identity: __classPrivateFieldGet(this, _AkashicPay_otk, "f").identity, expires: Date.now() + 60 * 1000, referenceId, identifier, toAddress: address, coinSymbol: network, }; await this.createDepositOrder(Object.assign(Object.assign({}, payloadToSign), { signature: this.sign(__classPrivateFieldGet(this, _AkashicPay_otk, "f").key.prv.pkcs8pem, payloadToSign) })); } return { address, identifier, }; } const tx = await this.akashicChain.keyCreateTransaction(network, __classPrivateFieldGet(this, _AkashicPay_otk, "f")); const response = await this.post(this.targetNode.node, tx); const newKey = (_a = response.data.$responses) === null || _a === void 0 ? void 0 : _a[0]; if (!newKey) { this.logger.warn(`Key creation on ${network} failed for identifier ${identifier}. Responses: ${response.data.$responses}`); throw new error_1.AkashicError(error_1.AkashicErrorCode.KeyCreationFailure); } const txBody = await this.akashicChain.differentialConsensusTransaction(__classPrivateFieldGet(this, _AkashicPay_otk, "f"), newKey, identifier); const diffResponse = (await this.post(this.targetNode.node, txBody)).data; if (diffResponse.$responses && diffResponse.$responses[0] !== 'confirmed') { this.logger.warn(`Key creation on ${network} failed at differential consensus for identifier ${identifier}. Unhealthy key: ${JSON.stringify(newKey)}`); throw new error_1.AkashicError(error_1.AkashicErrorCode.UnHealthyKey); } if (referenceId) { const payloadToSign = { identity: __classPrivateFieldGet(this, _AkashicPay_otk, "f").identity, expires: Date.now() + 60 * 1000, referenceId, identifier, toAddress: newKey.address, coinSymbol: network, }; await this.createDepositOrder(Object.assign(Object.assign({}, payloadToSign), { signature: this.sign(__classPrivateFieldGet(this, _AkashicPay_otk, "f").key.prv.pkcs8pem, payloadToSign) })); } return { address: newKey.address, identifier, }; } async getDepositUrl(identifier, referenceId) { const [keys, supportedCurrencies] = await Promise.all([ this.getKeysByOwnerAndIdentifier({ identifier, }), this.getSupportedCurrencies(), ]); for (const coinSymbol of new Set(Object.keys(supportedCurrencies))) { if (!new Set(keys.map((key) => key.coinSymbol)).has(coinSymbol)) { await this.getDepositAddress(coinSymbol, identifier); } } if (referenceId) { const payloadToSign = { identity: __classPrivateFieldGet(this, _AkashicPay_otk, "f").identity, expires: Date.now() + 60 * 1000, referenceId, identifier, }; await this.createDepositOrder(Object.assign(Object.assign({}, payloadToSign), { signature: this.sign(__classPrivateFieldGet(this, _AkashicPay_otk, "f").key.prv.pkcs8pem, payloadToSign) })); } return `${this.akashicPayUrl}/sdk/deposit?identity=${__classPrivateFieldGet(this, _AkashicPay_otk, "f").identity}&identifier=${identifier}`; } verifyDepositAddress() { throw new Error('Unimplemented'); } async lookForL2Address(aliasOrL1OrL2Address, network) { let url = `${this.akashicUrl}${constants_1.AkashicEndpoints.L2Lookup}?to=${aliasOrL1OrL2Address}`; if (network) { url += `&coinSymbol=${network}`; } return (await this.get(url)).data; } async getTransfers(getTransactionParams) { const queryParameters = Object.assign(Object.assign({}, getTransactionParams), { identity: __classPrivateFieldGet(this, _AkashicPay_otk, "f").identity, withSigningErrors: true }); const query = (0, http_1.encodeURIObject)(queryParameters); return (await this.get(`${this.akashicUrl}${constants_1.AkashicEndpoints.OwnerTransaction}?${query}`)).data.transactions .map((t) => (Object.assign(Object.assign({}, t), { tokenSymbol: t.tokenSymbol === types_1.TokenSymbol.TETHER ? types_1.TokenSymbol.USDT : t.tokenSymbol }))); } async getBalance() { const response = (await this.get(`${this.akashicUrl}${constants_1.AkashicEndpoints.OwnerBalance}?address=${__classPrivateFieldGet(this, _AkashicPay_otk, "f").identity}`)).data; return response.totalBalances.map((bal) => ({ networkSymbol: bal.coinSymbol, tokenSymbol: bal.tokenSymbol === types_1.TokenSymbol.TETHER ? types_1.TokenSymbol.USDT : bal.tokenSymbol, balance: bal.balance, })); } async getTransactionDetails(l2TxHash) { const response = await this.get(`${this.akashicUrl}${constants_1.AkashicEndpoints.TransactionsDetails}?l2Hash=${l2TxHash}`); const transaction = response.data.transaction; if (transaction) { return Object.assign(Object.assign({}, transaction), { tokenSymbol: transaction.tokenSymbol === types_1.TokenSymbol.TETHER ? types_1.TokenSymbol.USDT : transaction.tokenSymbol }); } return undefined; } async getSupportedCurrencies() { return (await this.get(`${this.akashicUrl}${constants_1.AkashicEndpoints.SupportedCurrencies}`)).data; } async getByOwnerAndIdentifier(getByOwnerAndIdentifierParams) { const queryParameters = Object.assign(Object.assign({}, getByOwnerAndIdentifierParams), { identity: __classPrivateFieldGet(this, _AkashicPay_otk, "f").identity }); const query = (0, http_1.encodeURIObject)(queryParameters); return (await this.get(`${this.akashicUrl}${constants_1.AkashicEndpoints.IdentifierLookup}?${query}`)).data; } async createDepositOrder(createDepositOrderParams) { return (await this.post(`${this.akashicUrl}${constants_1.AkashicEndpoints.CreateDepositOrder}`, createDepositOrderParams)).data; } async getKeysByOwnerAndIdentifier(getKeysByOwnerAndIdentifierParams) { const queryParameters = Object.assign(Object.assign({}, getKeysByOwnerAndIdentifierParams), { identity: __classPrivateFieldGet(this, _AkashicPay_otk, "f").identity }); const query = (0, http_1.encodeURIObject)(queryParameters); return (await this.get(`${this.akashicUrl}${constants_1.AkashicEndpoints.AllKeysOfIdentifier}?${query}`)).data; } constructor(args) { var _a; this.isFxBp = false; _AkashicPay_otk.set(this, void 0); this.env = (_a = args.environment) !== null && _a !== void 0 ? _a : constants_1.Environment.Production; this.akashicChain = new akashicChain_1.AkashicChain(this.env); const httpTransportOptions = { host: 'http-intake.logs.datadoghq.com', path: `/api/v2/logs?dd-api-key=${AkashicPay.DatadogApiKey}&ddsource=nodejs&service=js-sdk&ddtags=env:${this.env},version:${package_json_1.version}`, ssl: true }; const logger = (0, winston_1.createLogger)({ level: 'info', exitOnError: false, format: winston_1.format.json(), transports: [ new winston_1.transports.Http(httpTransportOptions), new winston_1.transports.Console({ format: winston_1.format.simple() }) ], }); this.logger = logger; this.httpClient = new FetchHttpClient_1.FetchHttpClient(this.logger); this.akashicUrl = (this.env === constants_1.Environment.Development ? constants_1.AkashicBaseUrlDev : constants_1.AkashicBaseUrl); this.akashicPayUrl = (this.env === constants_1.Environment.Development ? constants_1.AkashicPayBaseUrlDev : constants_1.AkashicPayBaseUrl); if (args.targetNode) this.targetNode = args.targetNode; } async init(args) { this.logger.info('Initialising AkashicPay instance'); if (!this.targetNode) { this.targetNode = await this.chooseBestACNode(); } if (!args.l2Address) { await this.setNewOTK(); } else { if (this.env === constants_1.Environment.Production) { const { data: { isBp, isFxBp }, } = await this.get(`${this.akashicUrl}${constants_1.AkashicEndpoints.IsBp}?address=${args.l2Address}`); if (!isBp) { throw new error_1.AkashicError(error_1.AkashicErrorCode.IsNotBp); } this.isFxBp = isFxBp; } if ('privateKey' in args && args.privateKey) { this.setOtkFromKeyPair(args.privateKey, args.l2Address); } else if ('recoveryPhrase' in args && args.recoveryPhrase) { await this.setOtkFromRecoveryPhrase(args.recoveryPhrase, args.l2Address); } else { throw new error_1.AkashicError(error_1.AkashicErrorCode.IncorrectPrivateKeyFormat); } } this.logger = this.logger.child({ identity: args.l2Address }); this.logger.info('AkashicPay instance initialised'); } async chooseBestACNode() { const nodes = this.env === constants_1.Environment.Production ? constants_1.ACNodes : constants_1.ACDevNodes; try { const fastestFullHealthNode = await (0, async_1.promiseAny)(Object.values(nodes).map(async (node) => { const response = (await this.httpClient.get(`${node.node}a/status`)).data; if (response.status !== 4) throw new ACHealthError(node, response.status); return node; })); this.logger.info(`Set healthy target node as ${JSON.stringify(fastestFullHealthNode)} by testing for fastest`); return fastestFullHealthNode; } catch (errors) { if (!(errors instanceof Array)) throw errors; const healthErrors = errors .filter((e) => e instanceof ACHealthError) .sort((e1, e2) => e2.status - e1.status); if (!healthErrors.length) throw errors; const { node, status } = healthErrors[0]; this.logger.warn(`Set least unhealthy target node ${JSON.stringify(node)} (health: ${status})`); return node; } } async setNewOTK() { var _a, _b; this.logger.info('Generating new OTK for development environment. Access it via `this.otk().`'); const otk = await (0, generate_otk_1.generateOTK)(); const onboardTx = await this.akashicChain.onboardOtkTransaction(otk); const alResponse = await this.post(this.targetNode.node, onboardTx); const identity = (_b = (_a = alResponse.data.$streams.new) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.id; if (!identity) { throw new error_1.AkashicError(error_1.AkashicErrorCode.TestNetOtkOnboardingFailed); } __classPrivateFieldSet(this, _AkashicPay_otk, Object.assign(Object.assign({}, otk), { identity: 'AS' + identity }), "f"); this.logger = this.logger.child({ identity: __classPrivateFieldGet(this, _AkashicPay_otk, "f").identity }); this.logger.info(`New OTK generated and onboarded with identity: ${__classPrivateFieldGet(this, _AkashicPay_otk, "f").identity}`); } setOtkFromKeyPair(privateKey, l2Address) { if (!AkashicPay.ACPrivateKeyRegex.test(privateKey)) throw new error_1.AkashicError(error_1.AkashicErrorCode.IncorrectPrivateKeyFormat); __classPrivateFieldSet(this, _AkashicPay_otk, Object.assign(Object.assign({}, (0, reconstruct_1.restoreOtkFromKeypair)(privateKey)), { identity: l2Address }), "f"); this.logger.debug('otk set from private key'); } async setOtkFromRecoveryPhrase(recoveryPhrase, l2Address) { __classPrivateFieldSet(this, _AkashicPay_otk, Object.assign(Object.assign({}, (await (0, reconstruct_1.restoreOtkFromPhrase)(recoveryPhrase))), { identity: l2Address }), "f"); this.logger.debug('otk set from recovery phrase'); } async post(url, payload) { this.logger.info(JSON.stringify(payload), `POSTing to url: ${url}`); return await this.httpClient.post(url, payload); } async get(url) { return await this.httpClient.get(url); } async prepareL2Transaction(transactionData) { const response = await this.post(`${this.akashicUrl}${constants_1.AkashicEndpoints.PrepareL2Txn}`, transactionData); return response.data; } sign(otkPriv, data) { try { const pemPrivate = '-----BEGIN EC PRIVATE KEY-----\n' + `${otkPriv}\n` + '-----END EC PRIVATE KEY-----'; let kp; if (otkPriv.startsWith('0x')) { kp = new activecrypto_1.ActiveCrypto.KeyPair('secp256k1', otkPriv); } else { kp = new activecrypto_1.ActiveCrypto.KeyPair('secp256k1', pemPrivate); } return kp.sign(data); } catch (_a) { throw new Error('Invalid private key'); } } } exports.AkashicPay = AkashicPay; _AkashicPay_otk = new WeakMap(); AkashicPay.ACPrivateKeyRegex = /^0x[a-f\d]{64}$/; AkashicPay.DatadogApiKey = '10f3796eb5494075b36b7d89ae456a65'; class ACHealthError extends Error { constructor(node, status) { super(`Node ${node.node} is unhealthy with status ${status}`); this.node = node; this.status = status; } } //# sourceMappingURL=main.js.map