UNPKG

js-oip

Version:

A Javascript Library to fully interact with the Open Index Protocol

382 lines (296 loc) 13.3 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = void 0; require("core-js/modules/es6.regexp.to-string"); var _bitcoinjsLib = require("bitcoinjs-lib"); var _oipdApi = require("../oipd-api"); var _modules = require("../../modules"); var _records = require("../../modules/records"); var _edit = require("../../modules/records/edit"); var _wallets = require("../../modules/wallets"); var _FLOTransaction = require("../../modules/flo/FLOTransaction"); var _config = require("../../config"); /** * Class to publish, register, edit, transfer, and deactivate OIP Records */ class OIP { /** * ##### Example * ```javascript * import {OIP} from 'js-oip' * * let wif = "cRVa9rNx5N1YKBw8PhavegJPFCiYCfC4n8cYmdc3X1Y6TyFZGG4B" * let oip = new OIP(wif, "testnet") * ``` * @param {String} wif - private key in Wallet Import Format (WIF) see: {@link https://en.bitcoin.it/wiki/Wallet_import_format} * @param {String} [network="mainnet"] - Use "testnet" for mainnet * @param {Object} [options] - Options to for the OIP class * @param {Object} [options.publicAddress] - Explicitly define a public address for the passed WIF * @param {Object} [options.oipdURL] - The OIP daemon API url to use when looking up the Latest Record in oip.edit() * @param {Object} [options.rpc] - By default, OIP uses a connection to a web explorer to publish Records, you can however use a connection to an RPC wallet instead by passing an object into this option * @param {Object} [options.rpc.host] - The Hostname for the RPC wallet connection * @param {Object} [options.rpc.port] - The Port for the RPC wallet connection * @param {Object} [options.rpc.username] - The Username for the RPC wallet connection * @param {Object} [options.rpc.password] - The Password for the RPC wallet connection */ constructor(wif, network, options) { this.options = options || {}; this.options.wif = wif; this.options.network = network; // If public address is not defined, calculate it using bitcoin-js (used by RPC-Wallet) if (!this.options.publicAddress) { let tmpNetwork = _config.floMainnet; if (network === 'testnet') { tmpNetwork = _config.floTestnet; } if (network === 'regtest') { tmpNetwork = _config.floRegtest; } let myECPair = _bitcoinjsLib.ECPair.fromWIF(this.options.wif, tmpNetwork.network); this.options.publicAddress = _bitcoinjsLib.payments.p2pkh({ pubkey: myECPair.publicKey, network: tmpNetwork.network }).address; } if (this.options.rpc) { this.wallet = new _wallets.RPCWallet(this.options); this.walletInitialized = false; } else { this.wallet = new _wallets.ExplorerWallet(this.options); this.walletInitialized = true; } this.oipdAPI = new _oipdApi.DaemonApi(this.options.oipdURL); } /** * Sign an OIP Record (if unsigned), and verify it's signature * @param {OIPRecord} record - The record you want to make sure is signed */ async signRecord(record) { // Check if a signature exists if (!record.getSignature() || record.getSignature() === '') { // Set the publisher address record.setPubAddress(this.options.publicAddress); // Check if a timestamp is set, and if not, set it to the current date (in ms time) if (!record.getTimestamp()) { record.setTimestamp(Date.now()); } // Attempt the signing of the Record let _ref = await record.signSelf(this.wallet.signMessage.bind(this.wallet)), success = _ref.success, error = _ref.error; if (!success) { // If there was an error, log the stack, and then return the error. console.log(error.stack); throw new Error(`Failed to sign record: ${error}`); } } // Check if the record has a valid signature if (!record.hasValidSignature()) { throw new Error(`Invalid signature`); } } /** * Broadcast an OIP Record * @param {OIPRecord} record - Any Object whos class extends OIPRecord (Artifact, Publisher, Platform, Retailer, Influencer, EditRecord, etc) * @param {String} methodType - The method you are wanting to perform, i.e. `publish`, `edit`, `deactivate`, `transfer` etc * @param {Object} options - Broadcast options * @param {function} options.onConfirmation - A function to run once the transaction recieves a confirmation that it was included in a block. * @param {String} options.onConfirmationRef - A reference string that should be returned in the onConfirmation function (useful for ID's). * @return {Promise<Object>} response - An object that contains a var for `success`, the `record` that was published, and the `editRecord` if it is an edit * let oip = new OIP(wif, "testnet") * let artifact = new Artifact() * let result = await oip.broadcastRecord(artifact, 'publish') */ async broadcastRecord(record, methodType, options) { // Verify that we are generally an OIPRecord (aka, we have the required signature and serialization functions) if (!(record instanceof _records.OIPRecord)) { throw new Error(`Record must be an instanceof OIPRecord`); } // Make sure the wallet has initialized and is ready for use if (!this.walletInitialized) { await this.wallet.initialize(); this.walletInitialized = true; } // Make sure the record is signed, and if not, sign it. try { await this.signRecord(record); } catch (error) { return { success: false, error: `Error while Signing Record: ${error}` }; } // Make sure the Record is valid let _record$isValid = record.isValid(), success = _record$isValid.success, error = _record$isValid.error; if (!success) { return { success: false, error: `Invalid record: ${error}` }; } // Create the data we are broadcasting to the chain let broadcastString = record.serialize(methodType); // Array to store txids let txids = []; // Check if we need to publish it using Multiparts, or if it will fit into a single transaction if (broadcastString.length > _FLOTransaction.FLODATA_MAX_LEN) { try { // Split the broadcast string up and publish the multiparts for it txids = await this.publishMultiparts(broadcastString, options); } catch (err) { return { success: false, error: `Failed to publish multiparts: ${err}` }; } } else { try { // Broadcast it in a single transaction :) let txid = await this.wallet.sendDataToChain(broadcastString); txids = [txid]; } catch (err) { return { success: false, error: `Failed to broadcast message: ${err}` }; } } // Set the txid to the Record record.setTXID(txids[0]); // Grab the data we need and bundle it for returning let response = { success: true, txids, record // If we are an edit record, also return the edit record :) }; if (record instanceof _edit.EditRecord) { // Grab the patched record to return let patchedRecord = record.getPatchedRecord(); // Set the edit version to the EditRecord txid patchedRecord.setEditVersion(txids[0]); // Move EditRecord and set patchedRecord response.record = patchedRecord; response.editRecord = record; } // Subscribe to the onConfirmation function to get a callback when all transactions have been confirmed this.wallet.onConfirmation(response, options); // Return our built response return response; } /** * Publish OIP Records * @param {OIPRecord} record - an Artifact, Publisher, Platform, Retailer, or Influencer * @param {Object} options - Publishing options * @param {function} options.onConfirmation - A function to run once the transaction recieves a confirmation that it was included in a block. * @param {String} options.onConfirmationRef - A reference string that should be returned in the onConfirmation function (useful for ID's). * @return {Promise<Object>} response - An object that contains a var for `success`, the `record` that was published * let oip = new OIP(wif, "testnet") * let artifact = new Artifact() * let result = await oip.publish(artifact) */ async publish(record, options) { // Forward the publish directly on to the broadcastRecord method let res = await this.broadcastRecord(record, 'publish', options); return res; } // async register(record) { // } //ToDo /** * Publish an Edit for a Record * @param {OIPRecord} editedRecord - The new version of the Record * @param {Object} options - Publishing options * @param {function} options.onConfirmation - A function to run once the transaction recieves a confirmation that it was included in a block. * @param {String} options.onConfirmationRef - A reference string that should be returned in the onConfirmation function (useful for ID's). * @return {Promise<Object>} response - An object that contains a var for `success`, the `record` that was published, and the `editRecord` * @Example * let oip = new OIP(wif, "testnet") * let record = new Artifact(previousArtifactJSON) * record.setTitle('new title') * let result = await oip.edit(record) */ async edit(editedRecord, options) { // Lookup the currently latest version of the Record let original; try { let _ref2 = await this.oipdAPI.getRecord(editedRecord.getOriginalTXID()), success = _ref2.success, record = _ref2.record, error = _ref2.error; // If OIPd reported an error, then throw the error if (success) { original = record; } else { return { success: false, error: `Unable to load Original Record from OIP daemon: ${error}` }; } } catch (e) { // Throw an error if the API request failed return { success: false, error: `Error while requesting Original Record from OIP daemon: ${e}` }; } // Throw an Error if record does not exist if (!original) { return { success: false, error: `A Record with the txid ${editedRecord.getOriginalTXID()} was not found in OIP daemon! Please make sure you have set 'options.oipdURL' to your OIP daemon server!` }; } // Set the Publisher Address before we sign editedRecord.setPubAddress(this.options.publicAddress); // Check if a timestamp is set, and if not, set it to the current date (in ms time) if (!editedRecord.getTimestamp()) { editedRecord.setTimestamp(Date.now()); } // Create an Edit Record from the Original and Edited let edit = new _edit.EditRecord(undefined, original, editedRecord); // Publish to chain let res = await this.broadcastRecord(edit, 'edit', options); return res; } // async transfer(record) { // } //ToDO // async deactivate(record) { // } //ToDo /** * Publish data that exceeds the maximum floData length in multiple parts * @param {string} data - The data you wish to publish * @return {Promise<Array.<String>>} txids - An array of transaction IDs * @example * let oip = new OIP(wif, "testnet") * let txArray = await oip.publishMultiparts(superLongStringData) * //For multipart publishing, use oip.publish() instead. Will auto redirect to this function */ async publishMultiparts(data) { if (typeof data !== 'string') { throw new Error(`Data must be of type string. Got: ${typeof data}`); } // Make sure the wallet has had time to initialize if (!this.walletInitialized) { await this.wallet.initialize(); this.walletInitialized = true; } let mpx = new _modules.MultipartX(data); let mps = mpx.getMultiparts(); let txids = []; for (let mp of mps) { // set reference, addr, and sign mp.setAddress(this.options.publicAddress); if (txids.length > 0) { mp.setReference(txids[0]); } let _ref3 = await mp.signSelf(this.wallet.signMessage.bind(this.wallet)), error = _ref3.error; if (error) { throw new Error(`Failed to sign multipart: ${error}`); } // not going to be valid yet or will it if (!mp.isValid().success) { console.log(mp); throw new Error(`Invalid multipart: ${mp.isValid().error}`); } let txid; try { // console.log(mp.toString()) // console.log(mp.toString().length) // throw new Error('STOP') txid = await this.wallet.sendDataToChain(mp.toString()); } catch (err) { console.log(err.stack); throw new Error(`Failed to broadcast multipart: ${err}`); } // console.log(txid) txids.push(txid); } return txids; } /** * Wait for all Confirmation Subscriptions to complete, then resolves the Promise * @return {Promise} Returns a promise that resolves once all subscribed Confirmation callbacks have completed */ waitForConfirmations() { return this.wallet.waitForConfirmations(); } } var _default = OIP; exports.default = _default;