cosmic-lib
Version:
A JavaScript implementation of the CosmicLink protocol for Stellar
221 lines (177 loc) • 6.24 kB
JavaScript
;
/**
* Contains the methods to convert field values from CosmicLink's
* `transaction descriptor` format to Stellar transaction object format.
*
* @private
* @exports construct
*/
const construct = exports;
const Buffer = require("@cosmic-plus/base/es5/buffer");
const misc = require("@cosmic-plus/jsutils/es5/misc");
const StellarSdk = require("@cosmic-plus/base/es5/stellar-sdk");
const resolve = require("./resolve");
const specs = require("./specs");
const status = require("./status");
/**
* Returns the StellarSdk Transaction built from tdesc.
*
* @param {Object} tdesc
* @return {Transaction}
*/
construct.transaction = async function (conf, tdesc) {
if (conf.status) throw new Error(conf.status);
try {
const txBuilder = await makeTransactionBuilder(conf, tdesc);
for (let index in tdesc.operations) {
const odesc = tdesc.operations[index];
const operation = await construct.operation(conf, odesc);
txBuilder.addOperation(operation);
}
const tx = txBuilder.build(); // Fix timebounds related issues. (Trezor can't sign minTime:0+maxTime:0)
if (tx._timeBounds.minTime === "0" && tx._timeBounds.maxTime === "0") {
delete tx._timeBounds;
delete tx._tx._attributes.timeBounds;
}
return tx;
} catch (error) {
if (!conf.errors) {
console.error(error);
status.error(conf, error.message);
}
if (!conf.status) status.fail(conf, "Transaction build failed", "throw");else throw error;
}
};
/**
* Returns the StellarSdk Operation built from `odesc`.
*
* @param {Object} odesc
* @return {Operation}
*/
construct.operation = async function (conf, odesc) {
const operation = odesc.type;
delete odesc.type;
for (let field in odesc) {
// eslint-disable-next-line require-atomic-updates
odesc[field] = await construct.field(conf, field, odesc[field]);
}
return StellarSdk.Operation[operation](odesc);
};
/**
* Returns the TransactionBuilder for `tdesc`.
*/
async function makeTransactionBuilder(conf, tdesc) {
const server = resolve.server(conf);
const baseFee = await server.fetchBaseFee();
let txOpts = {};
txOpts.networkPassphrase = resolve.networkPassphrase(conf);
if (tdesc.fee) txOpts.fee = tdesc.fee;else txOpts.fee = tdesc.operations.length * baseFee;
if (tdesc.memo) txOpts.memo = construct.memo(conf, tdesc.memo);
txOpts.timebounds = {
minTime: 0,
maxTime: 0
};
if (tdesc.minTime) txOpts.timebounds.minTime = construct.date(conf, tdesc.minTime);
if (tdesc.maxTime) txOpts.timebounds.maxTime = construct.date(conf, tdesc.maxTime);
const sourceAccount = await resolve.txSourceAccount(conf, tdesc.source, tdesc.sequence);
const builder = new StellarSdk.TransactionBuilder(sourceAccount, txOpts); /// Check if memo is needed for destination account.
for (let index in tdesc.operations) {
const operation = tdesc.operations[index];
if (operation.destination) {
const destination = await resolve.address(conf, operation.destination);
if (destination.memo) {
const memoType = destination.memo_type;
const memoValue = destination.memo;
if (tdesc.memo && (tdesc.memo.type !== memoType || tdesc.memo.value !== memoValue)) {
const short = misc.shorter(operation.destination);
status.error(conf, "Memo conflict: ".concat(short, " requires to set a memo"), "throw");
} else {
// eslint-disable-next-line require-atomic-updates
tdesc.memo = {
type: memoType,
value: memoValue
};
builder.addMemo(new StellarSdk.Memo(memoType, memoValue));
}
}
}
}
return builder;
}
/******************************************************************************/
/**
* Prepare `value` accordingly to `field` type.
*
* @param {string} field
* @param {any} value
*/
construct.field = async function (conf, field, value) {
const type = specs.fieldType[field];
if (type) return construct.type(conf, type, value);else throw new Error("Invalid field: ".concat(field));
};
/**
* Prepare `value` using the preparing function for `type`.
*
* @param {string} type
* @param {any} value
*/
construct.type = async function (conf, type, value) {
return construct[type](conf, value);
};
/******************************************************************************/
construct.address = async function (conf, address) {
const account = await resolve.address(conf, address);
return account.account_id;
};
construct.asset = async function (conf, asset) {
if (asset.issuer) {
const publicKey = await construct.address(conf, asset.issuer);
return new StellarSdk.Asset(asset.code, publicKey);
} else {
return StellarSdk.Asset.native();
}
};
construct.assetsArray = async function (conf, assetsArray) {
let path = [];
for (let index in assetsArray) {
const string = assetsArray[index];
const constructedAsset = await construct.asset(conf, string);
path.push(constructedAsset);
}
return path;
};
construct.buffer = function (conf, object) {
if (object.type === "base64") {
return Buffer.from(object.value, "base64");
} else {
return object.value || null;
}
};
construct.date = function (conf, string) {
return Date.parse(string) / 1000;
};
construct.memo = function (conf, memo) {
if (memo.type === "base64") {
return new StellarSdk.Memo("text", Buffer.from(memo.value, "base64"));
} else {
return new StellarSdk.Memo(memo.type, memo.value);
}
};
construct.signer = async function (conf, signer) {
let sdkSigner = {
weight: +signer.weight
};
if (signer.type === "tx") sdkSigner.preAuthTx = signer.value;else if (signer.type === "hash") sdkSigner.sha256Hash = signer.value;else if (signer.type === "key") {
const publicKey = await construct.address(conf, signer.value);
sdkSigner.ed25519PublicKey = publicKey;
}
return sdkSigner;
};
/******************************************************************************/
/**
* Provide dummy aliases for every other type for convenience & backward
* compatibility.
*/
specs.types.forEach(type => {
if (!exports[type]) exports[type] = (conf, value) => value;
});