UNPKG

cosmic-lib

Version:

A JavaScript implementation of the CosmicLink protocol for Stellar

237 lines (211 loc) 7.19 kB
"use strict" /** * Contains the action methods for CosmicLink. * * @private * @exports action */ const action = exports const axios = require("@cosmic-plus/base/es5/axios") const env = require("@cosmic-plus/jsutils/es5/env") const misc = require("@cosmic-plus/jsutils/es5/misc") const convert = require("./convert") const format = env.isBrowser && require("./format") const resolve = require("./resolve") const signersUtils = require("./signers-utils") const status = require("./status") /** * Lock a CosmicLink to a network/source pair. If the cosmicLink was created * from a query/uri/tdesc/json, it will create the corresponding * transaction/xdr/sep7 formats. * * This operation must be performed by the wallet before signing & sending the * transaction. * * @example * cosmicLib.config.network = "test" * const cosmicLink = new CosmicLink("?setOptions") * console.log(cosmicLink.network) // => undefined * console.log(cosmicLink.xdr) // => undefined * await cosmicLink.lock() * console.log(cosmicLink.network) // => "test" * console.log(cosmicLink.xdr) // => "AAAA...AA==" * * * @alias CosmicLink#lock * @async * @param {Object} [options] * @param {string} options.network Local fallback network * @param {string} options.horizon Local fallback horizon (overwrited by global configuration) * @param {string} options.callback Local fallback callback * @param {string} options.source Local fallback source */ action.lock = async function (cosmicLink, options = {}) { if (cosmicLink.status) throw new Error(cosmicLink.status) if (cosmicLink.locker) throw new Error("CosmicLink is already locked.") try { await applyLock(cosmicLink, options) } catch (error) { if (!cosmicLink.errors) { console.error(error) status.error(cosmicLink, error.message) } status.fail(cosmicLink, "Transaction build failed", "throw") } updateSignersNode(cosmicLink) return cosmicLink } async function applyLock (cosmicLink, options) { /** * The locker property tells that a CosmicLink have been locked, and exposes * the network & source values to which it have been locked. * * @alias CosmicLink#locker */ cosmicLink.locker = { source: cosmicLink.tdesc.source || options.source || cosmicLink.config.source, network: cosmicLink.tdesc.network || options.network || cosmicLink.config.network, horizon: options.horizon || cosmicLink.horizon, callback: cosmicLink.tdesc.callback || options.callback || cosmicLink.config.callback } /// Preserve the underlying tdesc object. cosmicLink._tdesc = Object.assign({}, cosmicLink.tdesc, cosmicLink.locker) delete cosmicLink._query delete cosmicLink._json if (!cosmicLink._transaction) { // eslint-disable-next-line require-atomic-updates cosmicLink._transaction = await convert.tdescToTransaction( cosmicLink, cosmicLink.tdesc ) delete cosmicLink._tdesc } delete cosmicLink._transaction._cosmicplus await signersUtils.extends(cosmicLink, cosmicLink._transaction) } /** * Sign CosmicLink's Transaction with **keypairs_or_preimage** and update the * other formats accordingly. Only legit signers are allowed to sign, and a * CosmicLink have to be [locked]{@link CosmicLink#lock} before signing. * * @alias CosmicLink#sign * @param {...Keypair|Buffer|string} ...keypairs_or_preimage */ action.sign = async function (cosmicLink, ...keypairsOrPreimage) { if (!cosmicLink.locker) throw new Error("cosmicLink is not locked.") const transaction = cosmicLink.transaction let allFine = true if (typeof keypairsOrPreimage[0] !== "string") { for (let index in keypairsOrPreimage) { const keypair = keypairsOrPreimage[index] const publicKey = keypair.publicKey() if (!cosmicLink.transaction.hasSigner(publicKey)) { const short = misc.shorter(publicKey) status.error(cosmicLink, "Not a legit signer: " + short) allFine = false continue } if (cosmicLink.transaction.hasSigned(publicKey)) continue try { transaction.sign(keypair) } catch (error) { console.error(error) const short = misc.shorter(publicKey) status.error(cosmicLink, "Failed to sign with key: " + short) allFine = false continue } } } else { try { transaction.signHashX(keypairsOrPreimage[0]) } catch (error) { console.error(error) const short = misc.shorter(keypairsOrPreimage[0]) status.error( cosmicLink, "Failed to sign with preimage: " + short, "throw" ) } } /// Update other formats. ["_query", "_xdr", "_sep7"].forEach(format => delete cosmicLink[format]) updateSignersNode(cosmicLink) if (!allFine) throw new Error("Some signers where invalid") else return transaction } function updateSignersNode (cosmicLink) { if (cosmicLink._signersNode) { const signersNode = format.signatures(cosmicLink, cosmicLink._transaction) cosmicLink.htmlDescription.replaceChild( signersNode, cosmicLink._signersNode ) cosmicLink._signersNode = signersNode } } /** * Send CosmicLink's transaction to a blockchain validator, or to * [StellarGuard]{@link https://stellarguard.me} when relevant. A * CosmicLink have to be [locked]{@link CosmicLink#lock} before sending. * * Returns a promise that resolve/reject to the horizon server response. * * @example * cosmicLink.send() * .then(console.log) * .catch(console.error) * * @alias CosmicLink#send * @param {horizon} [horizon] An horizon node URL * @return {Object} The server response */ action.send = async function (cosmicLink, horizon = cosmicLink.horizon) { if (!cosmicLink.locker) throw new Error("cosmicLink is not locked.") try { if (cosmicLink.transaction.hasSigner(STELLARGUARD_PUBKEY)) { return await sendToStellarGuard(cosmicLink) } else if (cosmicLink.callback) { return await axios.post(cosmicLink.callback, { xdr: cosmicLink.xdr }) } else { return await sendToHorizon(cosmicLink, horizon) } } catch (error) { if (error.response) console.error(error.message, error.response) throw error } } async function sendToHorizon (cosmicLink, horizon) { const server = resolve.server(cosmicLink, horizon) // Keep connection alive until transaction gets validated or a non-504 error // is returned. 504 error means the transaction is still following the // validation process. // eslint-disable-next-line no-constant-condition while (true) { try { return await server.submitTransaction(cosmicLink.transaction) } catch (error) { if (error.response.status !== 504) throw error } } } function sendToStellarGuard (cosmicLink) { const url = cosmicLink.network === "test" ? "https://test.stellarguard.me/api/transactions" : "https://stellarguard.me/api/transactions" return axios .post(url, { xdr: cosmicLink.xdr, callback: cosmicLink.callback }) .then(result => result.data) } const STELLARGUARD_PUBKEY = "GCVHEKSRASJBD6O2Z532LWH4N2ZLCBVDLLTLKSYCSMBLOYTNMEEGUARD"