UNPKG

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