"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"