@renproject/ren
Version:
Official Ren JavaScript SDK for bridging crypto assets cross-chain.
215 lines • 10.8 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.GatewayTransaction = void 0;
const utils_1 = require("@renproject/utils");
const bignumber_js_1 = __importDefault(require("bignumber.js"));
const renVMTxSubmitter_1 = require("./renVMTxSubmitter");
const config_1 = require("./utils/config");
const inputAndOutputTypes_1 = require("./utils/inputAndOutputTypes");
/**
* A GatewayTransaction handles a specific bridging transaction through RenVM.
* It includes an input chain transaction that should have already been
* submitted, a RenVM transaction, and an output chain transaction.
* Additionally, there may be some setup transactions required to be submitted
* before the output transaction.
*/
class GatewayTransaction {
/** @hidden */
constructor(renVM, fromChain, toChain, params, fromTxWaiter, config) {
this.outSetup = {};
/** @hidden */
this.initialize = async () => {
const { inputType, outputType, selector } = await (0, inputAndOutputTypes_1.getInputAndOutputTypes)({
asset: this.params.asset,
fromChain: this.fromChain,
toChain: this.toChain,
});
this.inputType = inputType;
this.outputType = outputType;
this.selector = selector;
const { asset, nonce, to, fromTx } = this.params;
let payload = await this.toChain.getOutputPayload(asset, this.inputType, this.outputType, to);
if (fromTx.toRecipient) {
try {
const fromTxPayload = {
to: fromTx.toRecipient,
toBytes: this.toChain.addressToBytes(fromTx.toRecipient),
payload: fromTx.toPayload
? utils_1.utils.fromBase64(fromTx.toPayload)
: new Uint8Array([]),
};
if (payload) {
if (payload.to !== fromTxPayload.to) {
this._config.logger.warn(`Expected recipient to be ${fromTxPayload.to}, instead got ${payload.to}.`);
}
if (utils_1.utils.toBase64(payload.payload) !==
utils_1.utils.toBase64(fromTxPayload.payload)) {
this._config.logger.warn(`Expected payload to be ${utils_1.utils.toBase64(fromTxPayload.payload)}, instead got ${utils_1.utils.toBase64(payload.payload)}.`);
}
if (utils_1.utils.toBase64(payload.toBytes) !==
utils_1.utils.toBase64(fromTxPayload.toBytes)) {
this._config.logger.warn(`Expected decoded recipient to be ${utils_1.utils.toBase64(fromTxPayload.toBytes)}, instead got ${utils_1.utils.toBase64(payload.toBytes)}.`);
}
}
else {
payload = fromTxPayload;
}
}
catch (error) {
if (!payload) {
throw utils_1.ErrorWithCode.updateError(error, utils_1.RenJSError.PARAMETER_ERROR, `No target payload provided.`);
}
}
}
if (!payload) {
throw new utils_1.ErrorWithCode(`No target payload provided.`, utils_1.RenJSError.PARAMETER_ERROR);
}
const sHash = (0, utils_1.generateSHash)(`${this.params.asset}/to${this.params.to.chain}`);
this.pHash = (0, utils_1.generatePHash)(payload.payload);
const nonceBytes = typeof nonce === "string"
? utils_1.utils.fromBase64(nonce)
: utils_1.utils.toNBytes(nonce || 0, 32);
this.gHash = (0, utils_1.generateGHash)(this.pHash, sHash, payload.toBytes, nonceBytes);
const gPubKey = this.params.shard
? utils_1.utils.fromBase64(this.params.shard.gPubKey)
: new Uint8Array();
if (!this.in) {
this.in = new utils_1.DefaultTxWaiter({
chainTransaction: this.params.fromTx,
chain: this.fromChain,
target: await this.provider.getConfirmationTarget(this.fromChain.chain),
});
}
const onSignatureReady = async (txWithStatus) => {
this.queryTxResult = txWithStatus;
const { tx } = txWithStatus;
if (tx.out && tx.out.revert && tx.out.revert !== "") {
throw new utils_1.ErrorWithCode(`RenVM transaction reverted: ${tx.out.revert}`, utils_1.RenJSError.RENVM_TRANSACTION_REVERTED);
}
if (tx.out &&
tx.out.txid &&
tx.out.txid.length > 0 &&
(0, utils_1.isEmptySignature)(tx.out.sig)) {
// The transaction has already been submitted.
const txid = utils_1.utils.toURLBase64(tx.out.txid);
const txindex = tx.out.txindex.toFixed();
const txHash = this.toChain.txHashFromBytes(utils_1.utils.fromBase64(txid));
await this.out.setTransaction({
chain: this.toChain.chain,
txid,
txindex,
txHash,
explorerLink: this.toChain.transactionExplorerLink({
txHash,
txindex,
txid,
}) || "",
});
}
else if ((0, utils_1.isDepositChain)(this.toChain) &&
(await this.toChain.isDepositAsset(this.params.asset))) {
throw new utils_1.ErrorWithCode(`Expected release transaction details in RenVM response.`, utils_1.RenJSError.INTERNAL_ERROR);
}
};
const network = await this.provider.getNetwork();
this.renVM = new renVMTxSubmitter_1.RenVMCrossChainTxSubmitter(this.provider, this.selector, {
txid: utils_1.utils.fromBase64(this.params.fromTx.txid),
txindex: new bignumber_js_1.default(this.params.fromTx.txindex),
amount: new bignumber_js_1.default(this.params.fromTx.amount),
payload: payload.payload,
phash: this.pHash,
to: payload.to,
nonce: nonceBytes,
nhash: this.nHash,
gpubkey: gPubKey,
ghash: this.gHash,
}, onSignatureReady, this._config, network);
this._hash = this.renVM.tx.hash;
this.renVM.eventEmitter.on("progress", (status) => {
// Check if status.response's txStatus is outdated.
if (this.queryTxResult &&
this.queryTxResult.txStatus === utils_1.TxStatus.TxStatusDone &&
status.response &&
status.response.txStatus !== utils_1.TxStatus.TxStatusDone) {
console.warn("RenVM transaction emitted outdated progress event.", this.queryTxResult, status.response);
return;
}
this.queryTxResult = status.response;
});
try {
await this.renVM.query();
}
catch (error) {
// Ignore error, possible submitted too soon before RenVM's nodes
// have seen the transaction.
}
const getOutputParams = () => ({
amount: this.queryTxResult && this.queryTxResult.tx.out
? this.queryTxResult.tx.out.amount
: undefined,
nHash: this.nHash,
sHash: utils_1.utils.keccak256(utils_1.utils.fromUTF8String(this.selector)),
pHash: this.pHash,
sigHash: this.queryTxResult && this.queryTxResult.tx.out
? this.queryTxResult.tx.out.sighash
: undefined,
signature: this.queryTxResult && this.queryTxResult.tx.out
? this.queryTxResult.tx.out.sig
: undefined,
});
if ((0, utils_1.isContractChain)(this.toChain) && this.toChain.getOutSetup) {
this.outSetup = Object.assign(Object.assign({}, this.outSetup), (await this.toChain.getOutSetup(asset, this.inputType, this.outputType, to, getOutputParams)));
}
if ((0, utils_1.isDepositChain)(this.toChain) &&
(await this.toChain.isDepositAsset(this.params.asset))) {
this.out = new utils_1.DefaultTxWaiter({
chain: this.toChain,
target: 0,
});
}
else if ((0, utils_1.isContractChain)(this.toChain)) {
this.out = await this.toChain.getOutputTx(this.inputType, this.outputType, this.params.asset, this.params.to, getOutputParams, 1);
}
else {
throw new Error(`Error setting 'out' transaction submitter.`);
}
return this;
};
/** PRIVATE METHODS */
this._defaultGetter = (name) => {
if (this[name] === undefined) {
throw new Error(`Must call 'initialize' before accessing '${name}'.`);
}
return this[name];
};
this.provider = renVM;
this.params = Object.assign({}, params);
this._config = Object.assign(Object.assign({}, config_1.defaultRenJSConfig), config);
this.fromChain = fromChain;
this.toChain = toChain;
const nonce = typeof params.nonce === "string"
? utils_1.utils.fromBase64(params.nonce)
: utils_1.utils.toNBytes(params.nonce || 0, 32);
this.nHash = (0, utils_1.generateNHash)(nonce, utils_1.utils.fromBase64(params.fromTx.txid), params.fromTx.txindex);
if (fromTxWaiter) {
this.in = new utils_1.TxWaiterProxy(fromTxWaiter, params.fromTx);
}
else {
this.in = undefined;
}
// TODO: Throw error if this.out is accessed before this.signed().
this.selector = undefined;
this.out = undefined;
this.pHash = undefined;
this.gHash = undefined;
this.renVM = undefined;
}
get hash() {
return this._defaultGetter("_hash") || this._hash;
}
}
exports.GatewayTransaction = GatewayTransaction;
//# sourceMappingURL=gatewayTransaction.js.map