@renproject/ren
Version:
Official Ren JavaScript SDK for bridging crypto assets cross-chain.
217 lines • 11 kB
JavaScript
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
import { DefaultTxWaiter, ErrorWithCode, generateGHash, generateNHash, generatePHash, generateSHash, isContractChain, isDepositChain, isEmptySignature, RenJSError, TxStatus, TxWaiterProxy, utils, } from "@renproject/utils";
import BigNumber from "bignumber.js";
import { RenVMCrossChainTxSubmitter } from "./renVMTxSubmitter";
import { defaultRenJSConfig } from "./utils/config";
import { getInputAndOutputTypes } from "./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.
*/
export class GatewayTransaction {
/** @hidden */
constructor(renVM, fromChain, toChain, params, fromTxWaiter, config) {
this.outSetup = {};
/** @hidden */
this.initialize = () => __awaiter(this, void 0, void 0, function* () {
const { inputType, outputType, selector } = yield 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 = yield 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.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.toBase64(payload.payload) !==
utils.toBase64(fromTxPayload.payload)) {
this._config.logger.warn(`Expected payload to be ${utils.toBase64(fromTxPayload.payload)}, instead got ${utils.toBase64(payload.payload)}.`);
}
if (utils.toBase64(payload.toBytes) !==
utils.toBase64(fromTxPayload.toBytes)) {
this._config.logger.warn(`Expected decoded recipient to be ${utils.toBase64(fromTxPayload.toBytes)}, instead got ${utils.toBase64(payload.toBytes)}.`);
}
}
else {
payload = fromTxPayload;
}
}
catch (error) {
if (!payload) {
throw ErrorWithCode.updateError(error, RenJSError.PARAMETER_ERROR, `No target payload provided.`);
}
}
}
if (!payload) {
throw new ErrorWithCode(`No target payload provided.`, RenJSError.PARAMETER_ERROR);
}
const sHash = generateSHash(`${this.params.asset}/to${this.params.to.chain}`);
this.pHash = generatePHash(payload.payload);
const nonceBytes = typeof nonce === "string"
? utils.fromBase64(nonce)
: utils.toNBytes(nonce || 0, 32);
this.gHash = generateGHash(this.pHash, sHash, payload.toBytes, nonceBytes);
const gPubKey = this.params.shard
? utils.fromBase64(this.params.shard.gPubKey)
: new Uint8Array();
if (!this.in) {
this.in = new DefaultTxWaiter({
chainTransaction: this.params.fromTx,
chain: this.fromChain,
target: yield this.provider.getConfirmationTarget(this.fromChain.chain),
});
}
const onSignatureReady = (txWithStatus) => __awaiter(this, void 0, void 0, function* () {
this.queryTxResult = txWithStatus;
const { tx } = txWithStatus;
if (tx.out && tx.out.revert && tx.out.revert !== "") {
throw new ErrorWithCode(`RenVM transaction reverted: ${tx.out.revert}`, RenJSError.RENVM_TRANSACTION_REVERTED);
}
if (tx.out &&
tx.out.txid &&
tx.out.txid.length > 0 &&
isEmptySignature(tx.out.sig)) {
// The transaction has already been submitted.
const txid = utils.toURLBase64(tx.out.txid);
const txindex = tx.out.txindex.toFixed();
const txHash = this.toChain.txHashFromBytes(utils.fromBase64(txid));
yield this.out.setTransaction({
chain: this.toChain.chain,
txid,
txindex,
txHash,
explorerLink: this.toChain.transactionExplorerLink({
txHash,
txindex,
txid,
}) || "",
});
}
else if (isDepositChain(this.toChain) &&
(yield this.toChain.isDepositAsset(this.params.asset))) {
throw new ErrorWithCode(`Expected release transaction details in RenVM response.`, RenJSError.INTERNAL_ERROR);
}
});
const network = yield this.provider.getNetwork();
this.renVM = new RenVMCrossChainTxSubmitter(this.provider, this.selector, {
txid: utils.fromBase64(this.params.fromTx.txid),
txindex: new BigNumber(this.params.fromTx.txindex),
amount: new BigNumber(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 === TxStatus.TxStatusDone &&
status.response &&
status.response.txStatus !== TxStatus.TxStatusDone) {
console.warn("RenVM transaction emitted outdated progress event.", this.queryTxResult, status.response);
return;
}
this.queryTxResult = status.response;
});
try {
yield 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.keccak256(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 (isContractChain(this.toChain) && this.toChain.getOutSetup) {
this.outSetup = Object.assign(Object.assign({}, this.outSetup), (yield this.toChain.getOutSetup(asset, this.inputType, this.outputType, to, getOutputParams)));
}
if (isDepositChain(this.toChain) &&
(yield this.toChain.isDepositAsset(this.params.asset))) {
this.out = new DefaultTxWaiter({
chain: this.toChain,
target: 0,
});
}
else if (isContractChain(this.toChain)) {
this.out = yield 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({}, defaultRenJSConfig), config);
this.fromChain = fromChain;
this.toChain = toChain;
const nonce = typeof params.nonce === "string"
? utils.fromBase64(params.nonce)
: utils.toNBytes(params.nonce || 0, 32);
this.nHash = generateNHash(nonce, utils.fromBase64(params.fromTx.txid), params.fromTx.txindex);
if (fromTxWaiter) {
this.in = new 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;
}
}
//# sourceMappingURL=gatewayTransaction.js.map