"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.Setup = void 0;
const knex_1 = require("knex");
const sdk_1 = require("@bsv/sdk");
const utilityHelpers_1 = require("./utility/utilityHelpers");
const WERR_errors_1 = require("./sdk/WERR_errors");
const WalletStorageManager_1 = require("./storage/WalletStorageManager");
const Services_1 = require("./services/Services");
const Monitor_1 = require("./monitor/Monitor");
const PrivilegedKeyManager_1 = require("./sdk/PrivilegedKeyManager");
const Wallet_1 = require("./Wallet");
const StorageClient_1 = require("./storage/remoting/StorageClient");
const StorageKnex_1 = require("./storage/StorageKnex");
// To rely on your own headers service, uncomment the following line:
// import { BHServiceClient } from './services/chaintracker'
const dotenv = __importStar(require("dotenv"));
dotenv.config();
/**
 * The 'Setup` class provides static setup functions to construct BRC-100 compatible
 * wallets in a variety of configurations.
 *
 * It serves as a starting point for experimentation and customization.
 */
class Setup {
    /**
     * @param chain
     * @returns true if .env is not valid for chain
     */
    static noEnv(chain) {
        try {
            Setup.getEnv(chain);
            return false;
        }
        catch (_a) {
            return true;
        }
    }
    /**
     * Creates content for .env file with some private keys, identity keys, sample API keys, and sample MySQL connection string.
     *
     * Two new, random private keys are generated each time, with their associated public identity keys.
     *
     * Loading secrets from a .env file is intended only for experimentation and getting started.
     * Private keys should never be included directly in your source code.
     *
     * @publicbody
     */
    static makeEnv() {
        const testPrivKey1 = sdk_1.PrivateKey.fromRandom();
        const testIdentityKey1 = testPrivKey1.toPublicKey().toString();
        const testPrivKey2 = sdk_1.PrivateKey.fromRandom();
        const testIdentityKey2 = testPrivKey2.toPublicKey().toString();
        const mainPrivKey1 = sdk_1.PrivateKey.fromRandom();
        const mainIdentityKey1 = mainPrivKey1.toPublicKey().toString();
        const mainPrivKey2 = sdk_1.PrivateKey.fromRandom();
        const mainIdentityKey2 = mainPrivKey2.toPublicKey().toString();
        const log = `
# .env file template for working with wallet-toolbox Setup functions.
MY_TEST_IDENTITY = '${testIdentityKey1}'
MY_TEST_IDENTITY2 = '${testIdentityKey2}'
MY_MAIN_IDENTITY = '${mainIdentityKey1}'
MY_MAIN_IDENTITY2 = '${mainIdentityKey2}'
MAIN_TAAL_API_KEY='mainnet_9596de07e92300c6287e4393594ae39c'
TEST_TAAL_API_KEY='testnet_0e6cf72133b43ea2d7861da2a38684e3'
MYSQL_CONNECTION='{"port":3306,"host":"127.0.0.1","user":"root","password":"your_password","database":"your_database", "timezone": "Z"}'
DEV_KEYS = '{
    "${testIdentityKey1}": "${testPrivKey1.toString()}",
    "${testIdentityKey2}": "${testPrivKey2.toString()}",
    "${mainIdentityKey1}": "${mainPrivKey1.toString()}",
    "${mainIdentityKey2}": "${mainPrivKey2.toString()}"
}'
`;
        console.log(log);
        return log;
    }
    /**
     * Reads a .env file of the format created by `makeEnv`.
     *
     * Returns values for designated `chain`.
     *
     * Access private keys through the `devKeys` object: `devKeys[identityKey]`
     *
     * @param chain Which chain to use: 'test' or 'main'
     * @returns {SetupEnv} with configuration environment secrets used by `Setup` functions.
     *
     * @publicbody
     */
    static getEnv(chain) {
        // Identity keys of the lead maintainer of this repo...
        const identityKey = chain === 'main' ? process.env.MY_MAIN_IDENTITY : process.env.MY_TEST_IDENTITY;
        const identityKey2 = chain === 'main' ? process.env.MY_MAIN_IDENTITY2 : process.env.MY_TEST_IDENTITY2;
        const filePath = chain === 'main' ? process.env.MY_MAIN_FILEPATH : process.env.MY_TEST_FILEPATH;
        const DEV_KEYS = process.env.DEV_KEYS || '{}';
        const mySQLConnection = process.env.MYSQL_CONNECTION || '{}';
        const taalApiKey = (0, utilityHelpers_1.verifyTruthy)(chain === 'main' ? process.env.MAIN_TAAL_API_KEY : process.env.TEST_TAAL_API_KEY, `.env value for '${chain.toUpperCase()}_TAAL_API_KEY' is required.`);
        if (!identityKey || !identityKey2)
            throw new WERR_errors_1.WERR_INVALID_OPERATION('.env is not a valid SetupEnv configuration.');
        return {
            chain,
            identityKey,
            identityKey2,
            filePath,
            taalApiKey,
            devKeys: JSON.parse(DEV_KEYS),
            mySQLConnection
        };
    }
    /**
     * Create a `Wallet`. Storage can optionally be provided or configured later.
     *
     * The following components are configured: KeyDeriver, WalletStorageManager, WalletService, WalletStorage.
     * Optionally, PrivilegedKeyManager is also configured.
     *
     * @publicbody
     */
    static async createWallet(args) {
        const chain = args.env.chain;
        args.rootKeyHex || (args.rootKeyHex = args.env.devKeys[args.env.identityKey]);
        const rootKey = sdk_1.PrivateKey.fromHex(args.rootKeyHex);
        const identityKey = rootKey.toPublicKey().toString();
        const keyDeriver = new sdk_1.CachedKeyDeriver(rootKey);
        const storage = new WalletStorageManager_1.WalletStorageManager(identityKey, args.active, args.backups);
        if (storage.canMakeAvailable())
            await storage.makeAvailable();
        const serviceOptions = Services_1.Services.createDefaultOptions(chain);
        serviceOptions.taalApiKey = args.env.taalApiKey;
        // To rely on your own headers service, uncomment the following line, updating the url and apiKey to your own values.
        // serviceOptions.chaintracks = new BHServiceClient('main', 'https://headers.spv.money', 'fC42F069YJs30FaWBAgikfDFEfIW1j4q')
        const services = new Services_1.Services(serviceOptions);
        const monopts = Monitor_1.Monitor.createDefaultWalletMonitorOptions(chain, storage, services);
        const monitor = new Monitor_1.Monitor(monopts);
        monitor.addDefaultTasks();
        const privilegedKeyManager = args.privilegedKeyGetter
            ? new PrivilegedKeyManager_1.PrivilegedKeyManager(args.privilegedKeyGetter)
            : undefined;
        const wallet = new Wallet_1.Wallet({
            chain,
            keyDeriver,
            storage,
            services,
            monitor,
            privilegedKeyManager
        });
        const r = {
            rootKey,
            identityKey,
            keyDeriver,
            chain,
            storage,
            services,
            monitor,
            wallet
        };
        return r;
    }
    /**
     * Setup a new `Wallet` without requiring a .env file.
     *
     * @param args.chain - 'main' or 'test'
     * @param args.rootKeyHex  - Root private key for wallet's key deriver.
     * @param args.storageUrl - Optional. `StorageClient` and `chain` compatible endpoint URL.
     * @param args.privilegedKeyGetter - Optional. Method that will return the privileged `PrivateKey`, on demand.
     */
    static async createWalletClientNoEnv(args) {
        const chain = args.chain;
        const endpointUrl = args.storageUrl || `https://${args.chain !== 'main' ? 'staging-' : ''}storage.babbage.systems`;
        const rootKey = sdk_1.PrivateKey.fromHex(args.rootKeyHex);
        const keyDeriver = new sdk_1.CachedKeyDeriver(rootKey);
        const storage = new WalletStorageManager_1.WalletStorageManager(keyDeriver.identityKey);
        const services = new Services_1.Services(chain);
        const privilegedKeyManager = args.privilegedKeyGetter
            ? new PrivilegedKeyManager_1.PrivilegedKeyManager(args.privilegedKeyGetter)
            : undefined;
        const wallet = new Wallet_1.Wallet({
            chain,
            keyDeriver,
            storage,
            services,
            privilegedKeyManager
        });
        const client = new StorageClient_1.StorageClient(wallet, endpointUrl);
        await storage.addWalletStorageProvider(client);
        await storage.makeAvailable();
        return wallet;
    }
    /**
     * @publicbody
     */
    static async createWalletClient(args) {
        const wo = await Setup.createWallet(args);
        const endpointUrl = args.endpointUrl || `https://${args.env.chain !== 'main' ? 'staging-' : ''}storage.babbage.systems`;
        const client = new StorageClient_1.StorageClient(wo.wallet, endpointUrl);
        await wo.storage.addWalletStorageProvider(client);
        await wo.storage.makeAvailable();
        return {
            ...wo,
            endpointUrl
        };
    }
    /**
     * @publicbody
     */
    static getKeyPair(priv) {
        if (priv === undefined)
            priv = sdk_1.PrivateKey.fromRandom();
        else if (typeof priv === 'string')
            priv = new sdk_1.PrivateKey(priv, 'hex');
        const pub = sdk_1.PublicKey.fromPrivateKey(priv);
        const address = pub.toAddress();
        return { privateKey: priv, publicKey: pub, address };
    }
    /**
     * @publicbody
     */
    static getLockP2PKH(address) {
        const p2pkh = new sdk_1.P2PKH();
        const lock = p2pkh.lock(address);
        return lock;
    }
    /**
     * @publicbody
     */
    static getUnlockP2PKH(priv, satoshis) {
        const p2pkh = new sdk_1.P2PKH();
        const lock = Setup.getLockP2PKH(Setup.getKeyPair(priv).address);
        // Prepare to pay with SIGHASH_ALL and without ANYONE_CAN_PAY.
        // In otherwords:
        // - all outputs must remain in the current order, amount and locking scripts.
        // - all inputs must remain from the current outpoints and sequence numbers.
        // (unlock scripts are never signed)
        const unlock = p2pkh.unlock(priv, 'all', false, satoshis, lock);
        return unlock;
    }
    /**
     * @publicbody
     */
    static createP2PKHOutputs(outputs) {
        const os = [];
        const count = outputs.length;
        for (let i = 0; i < count; i++) {
            const o = outputs[i];
            os.push({
                basket: o.basket,
                tags: o.tags,
                satoshis: o.satoshis,
                lockingScript: Setup.getLockP2PKH(o.address).toHex(),
                outputDescription: o.outputDescription || `p2pkh ${i}`
            });
        }
        return os;
    }
    /**
     * @publicbody
     */
    static async createP2PKHOutputsAction(wallet, outputs, options) {
        const os = Setup.createP2PKHOutputs(outputs);
        const createArgs = {
            description: `createP2PKHOutputs`,
            outputs: os,
            options: {
                ...options,
                // Don't randomize so we can simplify outpoint creation
                randomizeOutputs: false
            }
        };
        const cr = await wallet.createAction(createArgs);
        let outpoints;
        if (cr.txid) {
            outpoints = os.map((o, i) => `${cr.txid}.${i}`);
        }
        return { cr, outpoints };
    }
    /**
     * @publicbody
     */
    static async fundWalletFromP2PKHOutpoints(wallet, outpoints, p2pkhKey, inputBEEF) {
        // TODO
    }
    /**
     * Adds `Knex` based storage to a `Wallet` configured by `Setup.createWalletOnly`
     *
     * @param args.knex `Knex` object configured for either MySQL or SQLite database access.
     * Schema will be created and migrated as needed.
     * For MySQL, a schema corresponding to databaseName must exist with full access permissions.
     * @param args.databaseName Name for this storage. For MySQL, the schema name within the MySQL instance.
     * @param args.chain Which chain this wallet is on: 'main' or 'test'. Defaults to 'test'.
     * @param args.rootKeyHex
     *
     * @publicbody
     */
    static async createWalletKnex(args) {
        const wo = await Setup.createWallet(args);
        const activeStorage = await Setup.createStorageKnex(args);
        await wo.storage.addWalletStorageProvider(activeStorage);
        const { user, isNew } = await activeStorage.findOrInsertUser(wo.identityKey);
        const userId = user.userId;
        const r = {
            ...wo,
            activeStorage,
            userId
        };
        return r;
    }
    /**
     * @returns {StorageKnex} - `Knex` based storage provider for a wallet. May be used for either active storage or backup storage.
     */
    static async createStorageKnex(args) {
        // Create a temporary wallet setup to consistently resolve optional args.
        const wo = await Setup.createWallet(args);
        const storage = new StorageKnex_1.StorageKnex({
            chain: wo.chain,
            knex: args.knex,
            commissionSatoshis: 0,
            commissionPubKeyHex: undefined,
            feeModel: { model: 'sat/kb', value: 1 }
        });
        await storage.migrate(args.databaseName, randomBytesHex(33));
        await storage.makeAvailable();
        await wo.wallet.destroy();
        return storage;
    }
    /**
     * @publicbody
     */
    static createSQLiteKnex(filename) {
        const config = {
            client: 'better-sqlite3',
            connection: { filename },
            useNullAsDefault: true
        };
        const knex = (0, knex_1.knex)(config);
        return knex;
    }
    /**
     * @publicbody
     */
    static createMySQLKnex(connection, database) {
        const c = JSON.parse(connection);
        if (database) {
            c.database = database;
        }
        const config = {
            client: 'mysql2',
            connection: c,
            useNullAsDefault: true,
            pool: { min: 0, max: 7, idleTimeoutMillis: 15000 }
        };
        const knex = (0, knex_1.knex)(config);
        return knex;
    }
    /**
     * @publicbody
     */
    static async createWalletMySQL(args) {
        return await this.createWalletKnex({
            ...args,
            knex: Setup.createMySQLKnex(args.env.mySQLConnection, args.databaseName)
        });
    }
    /**
     * @publicbody
     */
    static async createWalletSQLite(args) {
        return await this.createWalletKnex({
            ...args,
            knex: Setup.createSQLiteKnex(args.filePath)
        });
    }
}
exports.Setup = Setup;
function randomBytesHex(arg0) {
    throw new Error('Function not implemented.');
}
//# sourceMappingURL=Setup.js.map