Skip to content

Commit

Permalink
separate sign and send
Browse files Browse the repository at this point in the history
  • Loading branch information
BlaineHeffron committed Jun 10, 2024
1 parent e5e49dc commit 57b5c4c
Show file tree
Hide file tree
Showing 2 changed files with 75 additions and 45 deletions.
72 changes: 66 additions & 6 deletions src/contract/assembled_transaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,11 @@ export class AssembledTransaction<T> {
*/
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
Expand Down Expand Up @@ -506,11 +511,9 @@ export class AssembledTransaction<T> {
/**
* 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,
}: {
Expand All @@ -522,7 +525,7 @@ export class AssembledTransaction<T> {
* You must provide this here if you did not provide one before
*/
signTransaction?: ClientOptions["signTransaction"];
} = {}): Promise<SentTransaction<T>> => {
} = {}): Promise<void> => {
if (!this.built) {
throw new Error("Transaction has not yet been simulated");
}
Expand All @@ -548,9 +551,66 @@ export class AssembledTransaction<T> {
);
}

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<T> = 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<SentTransaction<T>> => {
if(!this.signed){
await this.sign({ force, signTransaction });
}
return this.send();
};

private getStorageExpiration = async () => {
Expand Down
48 changes: 9 additions & 39 deletions src/contract/sent_transaction.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -24,8 +23,6 @@ import type { AssembledTransaction } from "./assembled_transaction";
export class SentTransaction<T> {
public server: Server;

public signed?: Tx;

/**
* The result of calling `sendTransaction` to broadcast the transaction to the
* network.
Expand Down Expand Up @@ -53,61 +50,32 @@ export class SentTransaction<T> {
};

constructor(
public signTransaction: ClientOptions["signTransaction"],
public assembled: AssembledTransaction<T>,

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,
});
}

/**
* 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 <U>(
/** More info in {@link MethodOptions} */
signTransaction: ClientOptions["signTransaction"],
/** {@link AssembledTransaction} from which this SentTransaction was initialized */
assembled: AssembledTransaction<U>,
/** The signed transaction to send to the network */
signed: Tx,
): Promise<SentTransaction<U>> => {
const tx = new SentTransaction(signTransaction, assembled);
const tx = new SentTransaction(assembled, signed);
const sent = await tx.send();
return sent;
};

private send = async (): Promise<this> => {
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,
);
Expand All @@ -124,6 +92,8 @@ export class SentTransaction<T> {

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,
Expand Down

0 comments on commit 57b5c4c

Please sign in to comment.