"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Polymesh = void 0;
const api_1 = require("@polkadot/api");
const apollo_cache_inmemory_1 = require("apollo-cache-inmemory");
const apollo_client_1 = require("apollo-client");
const apollo_link_1 = require("apollo-link");
const apollo_link_context_1 = require("apollo-link-context");
const apollo_link_http_1 = require("apollo-link-http");
const cross_fetch_1 = __importDefault(require("cross-fetch"));
const schema_1 = __importDefault(require("./polkadot/schema"));
const semver_1 = require("semver");
const websocket_1 = require("websocket");
const websocket_as_promised_1 = __importDefault(require("websocket-as-promised"));
const internal_1 = require("./internal");
const queries_1 = require("./middleware/queries");
const Settlements_1 = require("./Settlements");
const types_1 = require("./types");
const conversion_1 = require("./utils/conversion");
const internal_2 = require("./utils/internal");
const Claims_1 = require("./Claims");
const CurrentIdentity_1 = require("./CurrentIdentity");
// import { Governance } from './Governance';
const Middleware_1 = require("./Middleware");
const constants_1 = require("./utils/constants");
/**
 * Main entry point of the Polymesh SDK
 */
class Polymesh {
    /**
     * @hidden
     */
    constructor(context) {
        this.context = {};
        this.context = context;
        // NOTE uncomment in Governance v2 upgrade
        // this.governance = new Governance(context);
        this.claims = new Claims_1.Claims(context);
        this.middleware = new Middleware_1.Middleware(context);
        this.settlements = new Settlements_1.Settlements(context);
        this.currentIdentity = new CurrentIdentity_1.CurrentIdentity(context);
        this.transferPolyx = internal_2.createProcedureMethod({ getProcedureAndArgs: args => [internal_1.transferPolyx, args] }, context);
        this.registerIdentity = internal_2.createProcedureMethod({ getProcedureAndArgs: args => [internal_1.registerIdentity, args] }, context);
        this.claimClassicTicker = internal_2.createProcedureMethod({
            getProcedureAndArgs: args => [internal_1.claimClassicTicker, args],
        }, context);
    }
    // eslint-disable-next-line require-jsdoc
    static connect(params) {
        return __awaiter(this, void 0, void 0, function* () {
            const { nodeUrl, accountSeed, keyring, accountUri, accountMnemonic, signer, middleware, } = params;
            let context;
            /* istanbul ignore next: part of configuration, doesn't need to be tested */
            const wsp = new websocket_as_promised_1.default(nodeUrl, {
                createWebSocket: url => new websocket_1.w3cwebsocket(url),
                packMessage: data => JSON.stringify(data),
                unpackMessage: data => JSON.parse(data.toString()),
                attachRequestId: (data, requestId) => Object.assign({ id: requestId }, data),
                extractRequestId: data => data && data.id,
            });
            yield wsp.open();
            const { result: version } = yield wsp.sendRequest(constants_1.SYSTEM_VERSION_RPC_CALL);
            if (!semver_1.satisfies(version, constants_1.SUPPORTED_VERSION_RANGE)) {
                throw new internal_1.PolymeshError({
                    code: types_1.ErrorCode.FatalError,
                    message: 'Unsupported Polymesh version. Please upgrade the SDK',
                    data: {
                        polymeshVersion: version,
                        supportedVersionRange: constants_1.SUPPORTED_VERSION_RANGE,
                    },
                });
            }
            yield wsp.close();
            try {
                const { types, rpc } = schema_1.default;
                const polymeshApi = yield api_1.ApiPromise.create({
                    provider: new api_1.WsProvider(nodeUrl),
                    types,
                    rpc,
                });
                let middlewareApi = null;
                if (middleware) {
                    middlewareApi = new apollo_client_1.ApolloClient({
                        link: apollo_link_context_1.setContext((_, { headers }) => {
                            return {
                                headers: Object.assign(Object.assign({}, headers), { 'x-api-key': middleware.key }),
                            };
                        }).concat(apollo_link_1.ApolloLink.from([
                            new apollo_link_http_1.HttpLink({
                                uri: middleware.link,
                                fetch: cross_fetch_1.default,
                            }),
                        ])),
                        cache: new apollo_cache_inmemory_1.InMemoryCache(),
                        defaultOptions: {
                            watchQuery: {
                                fetchPolicy: 'no-cache',
                            },
                            query: {
                                fetchPolicy: 'no-cache',
                            },
                        },
                    });
                }
                if (signer) {
                    polymeshApi.setSigner(signer);
                }
                context = yield internal_1.Context.create({
                    polymeshApi,
                    middlewareApi,
                    accountSeed,
                    accountUri,
                    accountMnemonic,
                    keyring,
                });
            }
            catch (err) {
                const { message, code } = err;
                throw new internal_1.PolymeshError({
                    code,
                    message: `Error while connecting to "${nodeUrl}": "${message || 'The node couldn’t be reached'}"`,
                });
            }
            if (middleware) {
                try {
                    yield context.queryMiddleware(queries_1.heartbeat());
                }
                catch (err) {
                    if (err.message.indexOf('Forbidden') > -1 ||
                        err.message.indexOf('Missing Authentication Token') > -1) {
                        throw new internal_1.PolymeshError({
                            code: types_1.ErrorCode.FatalError,
                            message: 'Incorrect middleware URL or API key',
                        });
                    }
                }
            }
            return new Polymesh(context);
        });
    }
    // eslint-disable-next-line require-jsdoc
    getAccountBalance(args, callback) {
        const { context } = this;
        let account;
        let cb = callback;
        switch (typeof args) {
            case 'undefined': {
                break;
            }
            case 'function': {
                cb = args;
                break;
            }
            default: {
                ({ account } = args);
                break;
            }
        }
        if (!account) {
            account = context.getCurrentAccount();
        }
        else if (typeof account === 'string') {
            account = new internal_1.Account({ address: account }, context);
        }
        if (cb) {
            return account.getBalance(cb);
        }
        return account.getBalance();
    }
    // eslint-disable-next-line require-jsdoc
    isTickerAvailable(args, callback) {
        return __awaiter(this, void 0, void 0, function* () {
            const reservation = new internal_1.TickerReservation(args, this.context);
            if (callback) {
                return reservation.details(({ status: reservationStatus }) => {
                    // eslint-disable-next-line standard/no-callback-literal
                    callback(reservationStatus === types_1.TickerReservationStatus.Free);
                });
            }
            const { status } = yield reservation.details();
            return status === types_1.TickerReservationStatus.Free;
        });
    }
    /**
     * Retrieve all the ticker reservations currently owned by an Identity. This doesn't include tokens that
     *   have already been launched
     *
     * @param args.owner - defaults to the current Identity
     *
     * @note reservations with unreadable characters in their tickers will be left out
     */
    getTickerReservations(args) {
        return __awaiter(this, void 0, void 0, function* () {
            const { context: { polymeshApi: { query }, }, context, } = this;
            const did = yield internal_2.getDid(args === null || args === void 0 ? void 0 : args.owner, context);
            const entries = yield query.asset.assetOwnershipRelations.entries(conversion_1.stringToIdentityId(did, context));
            return entries.reduce((result, [key, relation]) => {
                if (relation.isTickerOwned) {
                    const ticker = conversion_1.tickerToString(key.args[1]);
                    if (internal_2.isPrintableAscii(ticker)) {
                        return [...result, new internal_1.TickerReservation({ ticker }, context)];
                    }
                }
                return result;
            }, []);
        });
    }
    /**
     * Retrieve a Ticker Reservation
     *
     * @param args.ticker - Security Token ticker
     */
    getTickerReservation(args) {
        return __awaiter(this, void 0, void 0, function* () {
            const { ticker } = args;
            const { context: { polymeshApi: { query: { asset }, }, }, context, } = this;
            const { owner, expiry } = yield asset.tickers(conversion_1.stringToTicker(ticker, context));
            if (!owner.isEmpty) {
                if (!expiry.isNone) {
                    return new internal_1.TickerReservation({ ticker }, context);
                }
                throw new internal_1.PolymeshError({
                    code: types_1.ErrorCode.UnmetPrerequisite,
                    message: `${ticker} token has been created`,
                });
            }
            throw new internal_1.PolymeshError({
                code: types_1.ErrorCode.UnmetPrerequisite,
                message: `There is no reservation for ${ticker} ticker`,
            });
        });
    }
    /**
     * Create an Identity instance from a DID
     *
     * @throws if there is no Identity with the passed DID
     */
    getIdentity(args) {
        return __awaiter(this, void 0, void 0, function* () {
            const identity = new internal_1.Identity(args, this.context);
            const exists = yield identity.exists();
            if (!exists) {
                throw new internal_1.PolymeshError({
                    code: types_1.ErrorCode.DataUnavailable,
                    message: 'The Identity does not exist',
                });
            }
            return identity;
        });
    }
    /**
     * Retrieve the Identity associated to the current Account (null if there is none)
     */
    getCurrentIdentity() {
        return this.context.getCurrentAccount().getIdentity();
    }
    /**
     * Create an Account instance from an address. If no address is passed, the current Account is returned
     */
    getAccount(args) {
        const { context } = this;
        if (args) {
            return new internal_1.Account(args, context);
        }
        return context.getCurrentAccount();
    }
    /**
     * Return a list that contains all the signing Accounts associated to the SDK instance
     *
     * @throws — if there is no current Account associated to the SDK instance
     */
    getAccounts() {
        return this.context.getAccounts();
    }
    /**
     * Return whether the supplied Identity/DID exists
     */
    isIdentityValid(args) {
        return __awaiter(this, void 0, void 0, function* () {
            const { identity: did } = args;
            const identity = did instanceof internal_1.Identity ? did : new internal_1.Identity({ did }, this.context);
            return identity.exists();
        });
    }
    /**
     * Retrieve the protocol fees associated with running a specific transaction
     *
     * @param args.tag - transaction tag (i.e. TxTags.asset.CreateAsset or "asset.createAsset")
     */
    getTransactionFees(args) {
        return this.context.getTransactionFees(args.tag);
    }
    /**
     * Get the treasury wallet address
     */
    getTreasuryAccount() {
        const { context } = this;
        return new internal_1.Account({ address: conversion_1.moduleAddressToString(constants_1.TREASURY_MODULE_ADDRESS, context) }, context);
    }
    /**
     * Handle connection errors
     *
     * @returns an unsubscribe callback
     */
    onConnectionError(callback) {
        const { context: { polymeshApi }, } = this;
        polymeshApi.on('error', callback);
        return () => {
            polymeshApi.off('error', callback);
        };
    }
    /**
     * Handle disconnection
     *
     * @returns an unsubscribe callback
     */
    onDisconnect(callback) {
        const { context: { polymeshApi }, } = this;
        polymeshApi.on('disconnected', callback);
        return () => {
            polymeshApi.off('disconnected', callback);
        };
    }
    /**
     * Retrieve all the Security Tokens owned by an Identity
     *
     * @param args.owner - identity representation or Identity ID as stored in the blockchain
     *
     * @note tokens with unreadable characters in their tickers will be left out
     */
    getSecurityTokens(args) {
        return __awaiter(this, void 0, void 0, function* () {
            const { context: { polymeshApi: { query }, }, context, } = this;
            const did = yield internal_2.getDid(args === null || args === void 0 ? void 0 : args.owner, context);
            const entries = yield query.asset.assetOwnershipRelations.entries(conversion_1.stringToIdentityId(did, context));
            return entries.reduce((result, [key, relation]) => {
                if (relation.isAssetOwned) {
                    const ticker = conversion_1.tickerToString(key.args[1]);
                    if (internal_2.isPrintableAscii(ticker)) {
                        return [...result, new internal_1.SecurityToken({ ticker }, context)];
                    }
                }
                return result;
            }, []);
        });
    }
    /**
     * Retrieve a Security Token
     *
     * @param args.ticker - Security Token ticker
     */
    getSecurityToken(args) {
        return __awaiter(this, void 0, void 0, function* () {
            const { ticker } = args;
            const token = new internal_1.SecurityToken({ ticker }, this.context);
            const exists = yield token.exists();
            if (!exists) {
                throw new internal_1.PolymeshError({
                    code: types_1.ErrorCode.DataUnavailable,
                    message: `There is no Security Token with ticker "${ticker}"`,
                });
            }
            return token;
        });
    }
    /**
     * Retrieve information for the current network
     */
    getNetworkProperties() {
        return __awaiter(this, void 0, void 0, function* () {
            const { context: { polymeshApi: { runtimeVersion: { specVersion }, rpc: { system: { chain }, }, }, }, } = this;
            const name = yield chain();
            return {
                name: conversion_1.textToString(name),
                version: conversion_1.u32ToBigNumber(specVersion).toNumber(),
            };
        });
    }
    // eslint-disable-next-line require-jsdoc
    getTreasuryBalance(callback) {
        return __awaiter(this, void 0, void 0, function* () {
            const account = this.getTreasuryAccount();
            if (callback) {
                return account.getBalance(({ free: freeBalance }) => {
                    callback(freeBalance);
                });
            }
            const { free } = yield account.getBalance();
            return free;
        });
    }
    /**
     * Retrieve the number of the latest block in the chain
     */
    getLatestBlock() {
        return this.context.getLatestBlock();
    }
    /**
     * Disconnect the client and close all open connections and subscriptions
     *
     * @note the SDK will become unusable after this operation. It will throw an error when attempting to
     *   access any chain or middleware data. If you wish to continue using the SDK, you must
     *   create a new instance by calling [[connect]]
     */
    disconnect() {
        return this.context.disconnect();
    }
    // eslint-disable-next-line require-jsdoc
    addSigner(params) {
        const { context } = this;
        const { address } = this.context.addPair(params);
        return new internal_1.Account({ address }, context);
    }
    /**
     * Set the SDK's current signing key to the provided address
     *
     * @note the key must have been previously added via [[addSigner]]
     */
    setSigner(signer) {
        this.context.setPair(conversion_1.signerToString(signer));
    }
    /**
     * Fetch the current network version (i.e. 3.1.0)
     */
    getNetworkVersion() {
        return __awaiter(this, void 0, void 0, function* () {
            return this.context.getNetworkVersion();
        });
    }
    // TODO @monitz87: remove when the dApp team no longer needs it
    /* eslint-disable @typescript-eslint/naming-convention */
    /* istanbul ignore next: only for testing purposes */
    /**
     * Polkadot client
     */
    get _polkadotApi() {
        return this.context.polymeshApi;
    }
    /* istanbul ignore next: only for testing purposes */
    /**
     * Middleware client
     */
    get _middlewareApi() {
        return this.context.middlewareApi;
    }
}
exports.Polymesh = Polymesh;
//# sourceMappingURL=Polymesh.js.map