@okxweb3/coin-stellar
Version:
@okxweb3/coin-stellar is a Stellar SDK for building Web3 wallets and applications. It supports Stellar and PI blockchains, enabling private key management, address generation, transaction signing, trustline creation, and asset transfers
336 lines • 15.3 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.isValidDate = exports.TransactionBuilder = exports.TimeoutInfinite = exports.BASE_FEE = void 0;
const js_xdr_1 = require("@stellar/js-xdr");
const bignumber_1 = __importDefault(require("./util/bignumber"));
const xdr_1 = __importDefault(require("./xdr"));
const account_1 = require("./account");
const muxed_account_1 = require("./muxed_account");
const decode_encode_muxed_account_1 = require("./util/decode_encode_muxed_account");
const transaction_1 = require("./transaction");
const fee_bump_transaction_1 = require("./fee_bump_transaction");
const sorobandata_builder_1 = require("./sorobandata_builder");
const strkey_1 = require("./strkey");
const signerkey_1 = require("./signerkey");
const memo_1 = require("./memo");
exports.BASE_FEE = '100';
exports.TimeoutInfinite = 0;
class TransactionBuilder {
constructor(sourceAccount, opts = {}) {
if (!sourceAccount) {
throw new Error('must specify source account for the transaction');
}
if (opts.fee === undefined) {
throw new Error('must specify fee for the transaction (in stroops)');
}
this.source = sourceAccount;
this.operations = [];
this.baseFee = opts.fee;
this.timebounds = opts.timebounds ? Object.assign({}, opts.timebounds) : null;
this.ledgerbounds = opts.ledgerbounds ? Object.assign({}, opts.ledgerbounds) : null;
this.minAccountSequence = opts.minAccountSequence || null;
this.minAccountSequenceAge = opts.minAccountSequenceAge || null;
this.minAccountSequenceLedgerGap = opts.minAccountSequenceLedgerGap || null;
this.extraSigners = opts.extraSigners ? [...opts.extraSigners] : null;
this.memo = opts.memo || memo_1.Memo.none();
this.networkPassphrase = opts.networkPassphrase || null;
this.sorobanData = opts.sorobanData
? new sorobandata_builder_1.SorobanDataBuilder(opts.sorobanData).build()
: null;
}
static cloneFrom(tx, opts = {}) {
if (!(tx instanceof transaction_1.Transaction)) {
throw new TypeError(`expected a 'Transaction', got: ${tx}`);
}
const sequenceNum = (BigInt(tx.sequence) - 1n).toString();
let source;
if (strkey_1.StrKey.isValidMed25519PublicKey(tx.source)) {
source = muxed_account_1.MuxedAccount.fromAddress(tx.source, sequenceNum);
}
else if (strkey_1.StrKey.isValidEd25519PublicKey(tx.source)) {
source = new account_1.Account(tx.source, sequenceNum);
}
else {
throw new TypeError(`unsupported tx source account: ${tx.source}`);
}
const unscaledFee = parseInt(tx.fee, 10) / tx.operations.length;
const builder = new TransactionBuilder(source, Object.assign({ fee: (unscaledFee || exports.BASE_FEE).toString(), memo: tx.memo, networkPassphrase: tx.networkPassphrase, timebounds: tx.timeBounds, ledgerbounds: tx.ledgerBounds, minAccountSequence: tx.minAccountSequence, minAccountSequenceAge: tx.minAccountSequenceAge, minAccountSequenceLedgerGap: tx.minAccountSequenceLedgerGap, extraSigners: tx.extraSigners }, opts));
tx._tx.operations().forEach((op) => builder.addOperation(op));
return builder;
}
addOperation(operation) {
this.operations.push(operation);
return this;
}
addOperationAt(operation, index) {
this.operations.splice(index, 0, operation);
return this;
}
clearOperations() {
this.operations = [];
return this;
}
clearOperationAt(index) {
this.operations.splice(index, 1);
return this;
}
addMemo(memo) {
this.memo = memo;
return this;
}
setTimeout(timeoutSeconds) {
if (this.timebounds !== null && this.timebounds.maxTime > 0) {
throw new Error('TimeBounds.max_time has been already set - setting timeout would overwrite it.');
}
if (timeoutSeconds < 0) {
throw new Error('timeout cannot be negative');
}
if (timeoutSeconds > 0) {
const timeoutTimestamp = Math.floor(Date.now() / 1000) + timeoutSeconds;
if (this.timebounds === null) {
this.timebounds = { minTime: 0, maxTime: timeoutTimestamp };
}
else {
this.timebounds = {
minTime: this.timebounds.minTime,
maxTime: timeoutTimestamp
};
}
}
else {
this.timebounds = {
minTime: 0,
maxTime: 0
};
}
return this;
}
setTimebounds(minEpochOrDate, maxEpochOrDate) {
if (typeof minEpochOrDate === 'number') {
minEpochOrDate = new Date(minEpochOrDate * 1000);
}
if (typeof maxEpochOrDate === 'number') {
maxEpochOrDate = new Date(maxEpochOrDate * 1000);
}
if (this.timebounds !== null) {
throw new Error('TimeBounds has been already set - setting timebounds would overwrite it.');
}
const minTime = Math.floor(minEpochOrDate.valueOf() / 1000);
const maxTime = Math.floor(maxEpochOrDate.valueOf() / 1000);
if (minTime < 0) {
throw new Error('min_time cannot be negative');
}
if (maxTime < 0) {
throw new Error('max_time cannot be negative');
}
if (maxTime > 0 && minTime > maxTime) {
throw new Error('min_time cannot be greater than max_time');
}
this.timebounds = { minTime, maxTime };
return this;
}
setLedgerbounds(minLedger, maxLedger) {
if (this.ledgerbounds !== null) {
throw new Error('LedgerBounds has been already set - setting ledgerbounds would overwrite it.');
}
if (minLedger < 0) {
throw new Error('min_ledger cannot be negative');
}
if (maxLedger < 0) {
throw new Error('max_ledger cannot be negative');
}
if (maxLedger > 0 && minLedger > maxLedger) {
throw new Error('min_ledger cannot be greater than max_ledger');
}
this.ledgerbounds = { minLedger, maxLedger };
return this;
}
setMinAccountSequence(minAccountSequence) {
if (this.minAccountSequence !== null) {
throw new Error('min_account_sequence has been already set - setting min_account_sequence would overwrite it.');
}
this.minAccountSequence = minAccountSequence;
return this;
}
setMinAccountSequenceAge(durationInSeconds) {
if (typeof durationInSeconds !== 'number') {
throw new Error('min_account_sequence_age must be a number');
}
if (this.minAccountSequenceAge !== null) {
throw new Error('min_account_sequence_age has been already set - setting min_account_sequence_age would overwrite it.');
}
if (durationInSeconds < 0) {
throw new Error('min_account_sequence_age cannot be negative');
}
this.minAccountSequenceAge = durationInSeconds;
return this;
}
setMinAccountSequenceLedgerGap(gap) {
if (this.minAccountSequenceLedgerGap !== null) {
throw new Error('min_account_sequence_ledger_gap has been already set - setting min_account_sequence_ledger_gap would overwrite it.');
}
if (gap < 0) {
throw new Error('min_account_sequence_ledger_gap cannot be negative');
}
this.minAccountSequenceLedgerGap = gap;
return this;
}
setExtraSigners(extraSigners) {
if (!Array.isArray(extraSigners)) {
throw new Error('extra_signers must be an array of strings.');
}
if (this.extraSigners !== null) {
throw new Error('extra_signers has been already set - setting extra_signers would overwrite it.');
}
if (extraSigners.length > 2) {
throw new Error('extra_signers cannot be longer than 2 elements.');
}
this.extraSigners = [...extraSigners];
return this;
}
setNetworkPassphrase(networkPassphrase) {
this.networkPassphrase = networkPassphrase;
return this;
}
setSorobanData(sorobanData) {
this.sorobanData = new sorobandata_builder_1.SorobanDataBuilder(sorobanData).build();
return this;
}
build() {
const sequenceNumber = new bignumber_1.default(this.source.sequenceNumber()).plus(1);
const fee = new bignumber_1.default(this.baseFee)
.times(this.operations.length)
.toNumber();
const attrs = {
fee,
seqNum: xdr_1.default.SequenceNumber.fromString(sequenceNumber.toString()),
memo: this.memo ? this.memo.toXDRObject() : null
};
if (this.timebounds === null ||
typeof this.timebounds.minTime === 'undefined' ||
typeof this.timebounds.maxTime === 'undefined') {
throw new Error('TimeBounds has to be set or you must call setTimeout(TimeoutInfinite).');
}
if (isValidDate(this.timebounds.minTime)) {
this.timebounds.minTime = this.timebounds.minTime.getTime() / 1000;
}
if (isValidDate(this.timebounds.maxTime)) {
this.timebounds.maxTime = this.timebounds.maxTime.getTime() / 1000;
}
this.timebounds.minTime = js_xdr_1.UnsignedHyper.fromString(this.timebounds.minTime.toString());
this.timebounds.maxTime = js_xdr_1.UnsignedHyper.fromString(this.timebounds.maxTime.toString());
const timeBounds = new xdr_1.default.TimeBounds(this.timebounds);
if (this.hasV2Preconditions()) {
let ledgerBounds = null;
if (this.ledgerbounds !== null) {
ledgerBounds = new xdr_1.default.LedgerBounds(this.ledgerbounds);
}
let minSeqNum = this.minAccountSequence || '0';
minSeqNum = xdr_1.default.SequenceNumber.fromString(minSeqNum);
const minSeqAge = js_xdr_1.UnsignedHyper.fromString(this.minAccountSequenceAge !== null
? this.minAccountSequenceAge.toString()
: '0');
const minSeqLedgerGap = this.minAccountSequenceLedgerGap || 0;
const extraSigners = this.extraSigners !== null
? this.extraSigners.map(signerkey_1.SignerKey.decodeAddress)
: [];
attrs.cond = xdr_1.default.Preconditions.precondV2(new xdr_1.default.PreconditionsV2({
timeBounds,
ledgerBounds,
minSeqNum,
minSeqAge,
minSeqLedgerGap,
extraSigners
}));
}
else {
attrs.cond = xdr_1.default.Preconditions.precondTime(timeBounds);
}
attrs.sourceAccount = (0, decode_encode_muxed_account_1.decodeAddressToMuxedAccount)(this.source.accountId());
if (this.sorobanData) {
attrs.ext = new xdr_1.default.TransactionExt(1, this.sorobanData);
}
else {
attrs.ext = new xdr_1.default.TransactionExt(0, xdr_1.default.Void);
}
const xtx = new xdr_1.default.Transaction(attrs);
xtx.operations(this.operations);
const txEnvelope = new xdr_1.default.TransactionEnvelope.envelopeTypeTx(new xdr_1.default.TransactionV1Envelope({ tx: xtx }));
const tx = new transaction_1.Transaction(txEnvelope, this.networkPassphrase);
this.source.incrementSequenceNumber();
return tx;
}
hasV2Preconditions() {
return (this.ledgerbounds !== null ||
this.minAccountSequence !== null ||
this.minAccountSequenceAge !== null ||
this.minAccountSequenceLedgerGap !== null ||
(this.extraSigners !== null && this.extraSigners.length > 0));
}
static buildFeeBumpTransaction(feeSource, baseFee, innerTx, networkPassphrase) {
const innerOps = innerTx.operations.length;
const innerBaseFeeRate = new bignumber_1.default(innerTx.fee).div(innerOps);
const base = new bignumber_1.default(baseFee);
if (base.lt(innerBaseFeeRate)) {
throw new Error(`Invalid baseFee, it should be at least ${innerBaseFeeRate} stroops.`);
}
const minBaseFee = new bignumber_1.default(exports.BASE_FEE);
if (base.lt(minBaseFee)) {
throw new Error(`Invalid baseFee, it should be at least ${minBaseFee} stroops.`);
}
let innerTxEnvelope = innerTx.toEnvelope();
if (innerTxEnvelope.switch() === xdr_1.default.EnvelopeType.envelopeTypeTxV0()) {
const v0Tx = innerTxEnvelope.v0().tx();
const v1Tx = new xdr_1.default.Transaction({
sourceAccount: new xdr_1.default.MuxedAccount.keyTypeEd25519(v0Tx.sourceAccountEd25519()),
fee: v0Tx.fee(),
seqNum: v0Tx.seqNum(),
cond: xdr_1.default.Preconditions.precondTime(v0Tx.timeBounds()),
memo: v0Tx.memo(),
operations: v0Tx.operations(),
ext: new xdr_1.default.TransactionExt(0)
});
innerTxEnvelope = new xdr_1.default.TransactionEnvelope.envelopeTypeTx(new xdr_1.default.TransactionV1Envelope({
tx: v1Tx,
signatures: innerTxEnvelope.v0().signatures()
}));
}
let feeSourceAccount;
if (typeof feeSource === 'string') {
feeSourceAccount = (0, decode_encode_muxed_account_1.decodeAddressToMuxedAccount)(feeSource);
}
else {
feeSourceAccount = feeSource.xdrMuxedAccount();
}
const tx = new xdr_1.default.FeeBumpTransaction({
feeSource: feeSourceAccount,
fee: xdr_1.default.Int64.fromString(base.times(innerOps + 1).toString()),
innerTx: xdr_1.default.FeeBumpTransactionInnerTx.envelopeTypeTx(innerTxEnvelope.v1()),
ext: new xdr_1.default.FeeBumpTransactionExt(0)
});
const feeBumpTxEnvelope = new xdr_1.default.FeeBumpTransactionEnvelope({
tx,
signatures: []
});
const envelope = new xdr_1.default.TransactionEnvelope.envelopeTypeTxFeeBump(feeBumpTxEnvelope);
return new fee_bump_transaction_1.FeeBumpTransaction(envelope, networkPassphrase);
}
static fromXDR(envelope, networkPassphrase) {
if (typeof envelope === 'string') {
envelope = xdr_1.default.TransactionEnvelope.fromXDR(envelope, 'base64');
}
if (envelope.switch() === xdr_1.default.EnvelopeType.envelopeTypeTxFeeBump()) {
return new fee_bump_transaction_1.FeeBumpTransaction(envelope, networkPassphrase);
}
return new transaction_1.Transaction(envelope, networkPassphrase);
}
}
exports.TransactionBuilder = TransactionBuilder;
function isValidDate(d) {
return d instanceof Date && !isNaN(d);
}
exports.isValidDate = isValidDate;
//# sourceMappingURL=transaction_builder.js.map