UNPKG

@bsv/wallet-toolbox-client

Version:
408 lines 15.9 kB
"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: '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