diff --git a/cmd/crates/soroban-spec-typescript/src/project_template/src/assembled-tx.ts b/cmd/crates/soroban-spec-typescript/src/project_template/src/assembled-tx.ts index 79ba90f5f..dd2e91b44 100644 --- a/cmd/crates/soroban-spec-typescript/src/project_template/src/assembled-tx.ts +++ b/cmd/crates/soroban-spec-typescript/src/project_template/src/assembled-tx.ts @@ -23,6 +23,8 @@ export class NotImplementedError extends Error { } export class ExpiredStateError extends Error { } export class NeedsMoreSignaturesError extends Error { } export class WalletDisconnectedError extends Error { } +export class SendResultOnlyError extends Error { } +export class SendFailedError extends Error { } type SendTx = SorobanRpc.SendTransactionResponse; type GetTx = SorobanRpc.GetTransactionResponse; @@ -51,8 +53,7 @@ export class AssembledTransaction { static async fromSimulation(options: AssembledTransactionClassOptions): Promise> { const tx = new AssembledTransaction(options) - await tx.simulate() - return tx + return await tx.simulate() } constructor(public options: AssembledTransactionClassOptions) { @@ -61,7 +62,7 @@ export class AssembledTransaction { }); } - simulate = async () => { + simulate = async (): Promise => { const contract = new Contract(this.options.contractId); this.txUnsigned = new TransactionBuilder(await this.getAccount(), { @@ -79,6 +80,8 @@ export class AssembledTransaction { this.options.networkPassphrase, this.simulation ).build() + + return this } getWallet = async (): Promise => { @@ -126,8 +129,8 @@ export class AssembledTransaction { /** * Sign a transaction with the injected `wallet` (default to Freighter) and - * add the fully-reconstructed to this object as `txSigned`. It can then be - * sent with {@link send}. + * add the fully-reconstructed transaction to this object as `txSigned`. It + * can then be sent with {@link send}. */ sign = async (): Promise => { if (!this.txUnsigned) { @@ -143,6 +146,9 @@ export class AssembledTransaction { throw new WalletDisconnectedError('Wallet is not connected') } + // throw errors if transaction needs restore first or if it's otherwise not an expected simulation type + this.ensureSuccessfulHostFunctionSimulation() + const wallet = await this.getWallet() const signed = await wallet.signTransaction(this.txUnsigned.toXDR(), { networkPassphrase: this.options.networkPassphrase, @@ -256,8 +262,18 @@ export class AssembledTransaction { // 2. otherwise, maybe it was merely sent with `sendTransaction` if (this.sendTransactionResponse) { - // if it didn't await, it returned the result of `sendTransaction` - return this.options.parseResultXdr(this.sendTransactionResponse.errorResultXdr!) + const errorResultXdr = this.sendTransactionResponse.errorResultXdr && + xdr.TransactionResult.fromXDR( + this.sendTransactionResponse.errorResultXdr, "base64" + ).result().results().toString() + if (errorResultXdr) { + throw new SendFailedError( + `Transaction simulation looked correct, but attempting to send the transaction failed. Check \`simulation\` and \`sendTransactionResponseAll\` to troubleshoot. Decoded \`sendTransactionResponse.errorResultXdr\`: ${errorResultXdr}` + ) + } + throw new SendResultOnlyError( + `Transaction was sent to the network, but not yet awaited. No result to show. Await transaction completion with \`getTransaction(sendTransactionResponse.hash)\`` + ) } // 3. finally, if neither of those are present, then parse the simulation result