@akashicpay/sdk
Version:
SDK to interact with the Akashic ecosystem
438 lines • 21.7 kB
JavaScript
"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