diff --git a/src/contract/assembled_transaction.ts b/src/contract/assembled_transaction.ts index 1f46824b9..1ebb74535 100644 --- a/src/contract/assembled_transaction.ts +++ b/src/contract/assembled_transaction.ts @@ -300,6 +300,11 @@ export class AssembledTransaction { */ private server: Server; + /** + * The signed transaction. + */ + private signed?: Tx; + /** * A list of the most important errors that various AssembledTransaction * methods can throw. Feel free to catch specific errors in your application @@ -506,11 +511,9 @@ export class AssembledTransaction { /** * Sign the transaction with the `wallet`, included previously. If you did * not previously include one, you need to include one now that at least - * includes the `signTransaction` method. After signing, this method will - * send the transaction to the network and return a `SentTransaction` that - * keeps track of all the attempts to fetch the transaction. + * includes the `signTransaction` method. */ - signAndSend = async ({ + sign = async ({ force = false, signTransaction = this.options.signTransaction, }: { @@ -522,7 +525,7 @@ export class AssembledTransaction { * You must provide this here if you did not provide one before */ signTransaction?: ClientOptions["signTransaction"]; - } = {}): Promise> => { + } = {}): Promise => { if (!this.built) { throw new Error("Transaction has not yet been simulated"); } @@ -548,9 +551,66 @@ export class AssembledTransaction { ); } + const timeoutInSeconds = + this.options.timeoutInSeconds ?? DEFAULT_TIMEOUT; + this.built = TransactionBuilder.cloneFrom(this.built!, { + fee: this.built!.fee, + timebounds: undefined, + sorobanData: this.simulationData.transactionData, + }) + .setTimeout(timeoutInSeconds) + .build(); + + const signature = await signTransaction( + this.built.toXDR(), + { + networkPassphrase: this.options.networkPassphrase, + }, + ); + + this.signed = TransactionBuilder.fromXDR( + signature, + this.options.networkPassphrase, + ) as Tx; + }; + + /** + * Sends the transaction to the network to return a `SentTransaction` that + * keeps track of all the attempts to fetch the transaction. + */ + async send(){ + if(!this.signed){ + throw new Error("The transaction has not yet been signed. Run `sign` first."); + } const typeChecked: AssembledTransaction = this; - const sent = await SentTransaction.init(signTransaction, typeChecked); + const sent = await SentTransaction.init(typeChecked, this.signed); return sent; + } + + /** + * Sign the transaction with the `wallet`, included previously. If you did + * not previously include one, you need to include one now that at least + * includes the `signTransaction` method. After signing, this method will + * send the transaction to the network and return a `SentTransaction` that + * keeps track of all the attempts to fetch the transaction. + */ + signAndSend = async ({ + force = false, + signTransaction = this.options.signTransaction, + }: { + /** + * If `true`, sign and send the transaction even if it is a read call + */ + force?: boolean; + /** + * You must provide this here if you did not provide one before + */ + signTransaction?: ClientOptions["signTransaction"]; + } = {}): Promise> => { + if(!this.signed){ + await this.sign({ force, signTransaction }); + } + return this.send(); }; private getStorageExpiration = async () => { diff --git a/src/contract/sent_transaction.ts b/src/contract/sent_transaction.ts index d6f275d78..960f842f7 100644 --- a/src/contract/sent_transaction.ts +++ b/src/contract/sent_transaction.ts @@ -1,7 +1,6 @@ /* disable max-classes rule, because extending error shouldn't count! */ /* eslint max-classes-per-file: 0 */ -import { SorobanDataBuilder, TransactionBuilder } from "@stellar/stellar-base"; -import type { ClientOptions, MethodOptions, Tx } from "./types"; +import type { MethodOptions, Tx } from "./types"; import { Server } from "../rpc/server" import { Api } from "../rpc/api" import { DEFAULT_TIMEOUT, withExponentialBackoff } from "./utils"; @@ -24,8 +23,6 @@ import type { AssembledTransaction } from "./assembled_transaction"; export class SentTransaction { public server: Server; - public signed?: Tx; - /** * The result of calling `sendTransaction` to broadcast the transaction to the * network. @@ -53,14 +50,10 @@ export class SentTransaction { }; constructor( - public signTransaction: ClientOptions["signTransaction"], public assembled: AssembledTransaction, + + public signed: Tx, ) { - if (!signTransaction) { - throw new Error( - "You must provide a `signTransaction` function to send a transaction", - ); - } this.server = new Server(this.assembled.options.rpcUrl, { allowHttp: this.assembled.options.allowHttp ?? false, }); @@ -68,46 +61,21 @@ export class SentTransaction { /** * Initialize a `SentTransaction` from an existing `AssembledTransaction` and - * a `signTransaction` function. This will also send the transaction to the + * a `signed` AssembledTransaction. This will also send the transaction to the * network. */ static init = async ( - /** More info in {@link MethodOptions} */ - signTransaction: ClientOptions["signTransaction"], /** {@link AssembledTransaction} from which this SentTransaction was initialized */ assembled: AssembledTransaction, + /** The signed transaction to send to the network */ + signed: Tx, ): Promise> => { - const tx = new SentTransaction(signTransaction, assembled); + const tx = new SentTransaction(assembled, signed); const sent = await tx.send(); return sent; }; private send = async (): Promise => { - const timeoutInSeconds = - this.assembled.options.timeoutInSeconds ?? DEFAULT_TIMEOUT; - this.assembled.built = TransactionBuilder.cloneFrom(this.assembled.built!, { - fee: this.assembled.built!.fee, - timebounds: undefined, - sorobanData: new SorobanDataBuilder( - this.assembled.simulationData.transactionData.toXDR(), - ).build(), - }) - .setTimeout(timeoutInSeconds) - .build(); - - const signature = await this.signTransaction!( - // `signAndSend` checks for `this.built` before calling `SentTransaction.init` - this.assembled.built!.toXDR(), - { - networkPassphrase: this.assembled.options.networkPassphrase, - }, - ); - - this.signed = TransactionBuilder.fromXDR( - signature, - this.assembled.options.networkPassphrase, - ) as Tx; - this.sendTransactionResponse = await this.server.sendTransaction( this.signed, ); @@ -124,6 +92,8 @@ export class SentTransaction { const { hash } = this.sendTransactionResponse; + const timeoutInSeconds = + this.assembled.options.timeoutInSeconds ?? DEFAULT_TIMEOUT; this.getTransactionResponseAll = await withExponentialBackoff( () => this.server.getTransaction(hash), (resp) => resp.status === Api.GetTransactionStatus.NOT_FOUND,