@stellar/stellar-sdk
Version:
A library for working with the Stellar network, including communication with the Horizon and Soroban RPC servers.
290 lines (287 loc) • 9.54 kB
JavaScript
import { Buffer } from 'buffer';
import types from './generated/curr_generated.js';
import { hash } from './hashing.js';
import { StrKey } from './strkey.js';
import { Operation } from './operation.js';
import { Memo } from './memo.js';
import { TransactionBase } from './transaction_base.js';
import { encodeMuxedAccountToAddress, extractBaseAddress } from './util/decode_encode_muxed_account.js';
class Transaction extends TransactionBase {
_envelopeType;
_source = "";
_memo;
_sequence;
_operations;
_timeBounds;
_ledgerBounds;
_minAccountSequence;
_minAccountSequenceAge;
_minAccountSequenceLedgerGap;
_extraSigners;
/**
* @param envelope - transaction envelope object or base64 encoded string
* @param networkPassphrase - passphrase of the target stellar network
* (e.g. "Public Global Stellar Network ; September 2015")
*/
constructor(envelope, networkPassphrase) {
if (typeof envelope === "string") {
const buffer = Buffer.from(envelope, "base64");
envelope = types.TransactionEnvelope.fromXDR(buffer);
}
const envelopeType = envelope.switch();
if (!(envelopeType === types.EnvelopeType.envelopeTypeTxV0() || envelopeType === types.EnvelopeType.envelopeTypeTx())) {
throw new Error(
`Invalid TransactionEnvelope: expected an envelopeTypeTxV0 or envelopeTypeTx but received an ${envelopeType.name}.`
);
}
const txEnvelope = envelope.value();
const tx = txEnvelope.tx();
const fee = tx.fee().toString();
const signatures = (txEnvelope.signatures() || []).slice();
super(tx, signatures, fee, networkPassphrase);
this._envelopeType = envelopeType;
this._memo = tx.memo();
this._sequence = tx.seqNum().toString();
switch (this._envelopeType) {
case types.EnvelopeType.envelopeTypeTxV0():
this._source = StrKey.encodeEd25519PublicKey(
tx.sourceAccountEd25519()
);
break;
default:
this._source = encodeMuxedAccountToAddress(
tx.sourceAccount()
);
break;
}
let cond = null;
let timeBounds = null;
switch (this._envelopeType) {
case types.EnvelopeType.envelopeTypeTxV0():
timeBounds = tx.timeBounds();
break;
case types.EnvelopeType.envelopeTypeTx():
switch (tx.cond().switch()) {
case types.PreconditionType.precondTime():
timeBounds = tx.cond().timeBounds();
break;
case types.PreconditionType.precondV2():
cond = tx.cond().v2();
timeBounds = cond.timeBounds();
break;
}
break;
}
if (timeBounds) {
this._timeBounds = {
minTime: timeBounds.minTime().toString(),
maxTime: timeBounds.maxTime().toString()
};
}
if (cond) {
const ledgerBounds = cond.ledgerBounds();
if (ledgerBounds) {
this._ledgerBounds = {
minLedger: ledgerBounds.minLedger(),
maxLedger: ledgerBounds.maxLedger()
};
}
const minSeq = cond.minSeqNum();
if (minSeq) {
this._minAccountSequence = minSeq.toString();
}
this._minAccountSequenceAge = cond.minSeqAge().toBigInt();
this._minAccountSequenceLedgerGap = cond.minSeqLedgerGap();
this._extraSigners = cond.extraSigners();
}
const operations = tx.operations() || [];
this._operations = operations.map((op) => Operation.fromXDRObject(op));
}
/**
* The time bounds for this transaction, with `minTime` and `maxTime` as
* 64-bit unix timestamps (strings).
*/
get timeBounds() {
return this._timeBounds;
}
set timeBounds(_value) {
throw new Error("Transaction is immutable");
}
/**
* The ledger bounds for this transaction, with `minLedger` (uint32) and
* `maxLedger` (uint32, or 0 for no upper bound).
*/
get ledgerBounds() {
return this._ledgerBounds;
}
set ledgerBounds(_value) {
throw new Error("Transaction is immutable");
}
/** The minimum account sequence (64-bit, as a string). */
get minAccountSequence() {
return this._minAccountSequence;
}
set minAccountSequence(_value) {
throw new Error("Transaction is immutable");
}
/** The minimum account sequence age (64-bit number of seconds). */
get minAccountSequenceAge() {
return this._minAccountSequenceAge;
}
set minAccountSequenceAge(_value) {
throw new Error("Transaction is immutable");
}
/** The minimum account sequence ledger gap (32-bit number of ledgers). */
get minAccountSequenceLedgerGap() {
return this._minAccountSequenceLedgerGap;
}
set minAccountSequenceLedgerGap(_value) {
throw new Error("Transaction is immutable");
}
/**
* Array of extra signers as XDR objects; use {@link SignerKey.encodeSignerKey}
* to convert to StrKey strings.
*/
get extraSigners() {
return this._extraSigners;
}
set extraSigners(_value) {
throw new Error("Transaction is immutable");
}
/** The sequence number for this transaction. */
get sequence() {
return this._sequence;
}
set sequence(_value) {
throw new Error("Transaction is immutable");
}
/** The source account for this transaction. */
get source() {
return this._source;
}
set source(_value) {
throw new Error("Transaction is immutable");
}
/** The list of operations in this transaction. */
get operations() {
return this._operations;
}
set operations(_value) {
throw new Error("Transaction is immutable");
}
/** The memo attached to this transaction. */
get memo() {
return Memo.fromXDRObject(this._memo);
}
set memo(_value) {
throw new Error("Transaction is immutable");
}
/**
* Returns the "signature base" of this transaction, which is the value
* that, when hashed, should be signed to create a signature that
* validators on the Stellar Network will accept.
*
* It is composed of a 4 prefix bytes followed by the xdr-encoded form
* of this transaction.
*/
signatureBase() {
let tx = this.tx;
if (this._envelopeType === types.EnvelopeType.envelopeTypeTxV0()) {
tx = types.Transaction.fromXDR(
Buffer.concat([
// TransactionV0 is a transaction with the AccountID discriminant
// stripped off, we need to put it back to build a valid transaction
// which we can use to build a TransactionSignaturePayloadTaggedTransaction
Buffer.alloc(4),
// AccountID discriminant: publicKeyTypeEd25519 = 0
tx.toXDR()
])
);
}
const taggedTransaction = types.TransactionSignaturePayloadTaggedTransaction.envelopeTypeTx(
tx
);
const txSignature = new types.TransactionSignaturePayload({
networkId: types.Hash.fromXDR(hash(this.networkPassphrase)),
taggedTransaction
});
return txSignature.toXDR();
}
/**
* To envelope returns a xdr.TransactionEnvelope which can be submitted to the network.
*/
toEnvelope() {
const rawTx = this.tx.toXDR();
const signatures = this.signatures.slice();
let envelope;
switch (this._envelopeType) {
case types.EnvelopeType.envelopeTypeTxV0():
envelope = types.TransactionEnvelope.envelopeTypeTxV0(
new types.TransactionV0Envelope({
tx: types.TransactionV0.fromXDR(rawTx),
// make a copy of tx
signatures
})
);
break;
case types.EnvelopeType.envelopeTypeTx():
envelope = types.TransactionEnvelope.envelopeTypeTx(
new types.TransactionV1Envelope({
tx: types.Transaction.fromXDR(rawTx),
// make a copy of tx
signatures
})
);
break;
default:
throw new Error(
`Invalid TransactionEnvelope: expected an envelopeTypeTxV0 or envelopeTypeTx but received an ${this._envelopeType.name}.`
);
}
return envelope;
}
/**
* Calculate the claimable balance ID for an operation within the transaction.
*
* @param opIndex - the index of the CreateClaimableBalance op
*
* @throws for invalid `opIndex` value, if op at `opIndex` is not
* `CreateClaimableBalance`, or for general XDR un/marshalling failures
*
* @see https://github.com/stellar/go/blob/d712346e61e288d450b0c08038c158f8848cc3e4/txnbuild/transaction.go#L392-L435
*
*/
getClaimableBalanceId(opIndex) {
if (!Number.isInteger(opIndex) || opIndex < 0 || opIndex >= this.operations.length) {
throw new RangeError("invalid operation index");
}
const op = this.operations[opIndex];
if (op === void 0) {
throw new RangeError("invalid operation index");
}
try {
Operation.createClaimableBalance(
op
);
} catch (err) {
throw new TypeError(
`expected createClaimableBalance, got ${op.type}: ${String(err)}`
);
}
const account = StrKey.decodeEd25519PublicKey(
extractBaseAddress(this.source)
);
const operationId = types.HashIdPreimage.envelopeTypeOpId(
new types.HashIdPreimageOperationId({
sourceAccount: types.PublicKey.publicKeyTypeEd25519(account),
seqNum: types.Int64.fromString(this.sequence),
opNum: opIndex
})
);
const opIdHash = hash(operationId.toXDR("raw"));
const balanceId = types.ClaimableBalanceId.claimableBalanceIdTypeV0(opIdHash);
return balanceId.toXDR("hex");
}
}
export { Transaction };
//# sourceMappingURL=transaction.js.map