Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: implement sendAndAwaitStatus subscription #3541

Open
wants to merge 17 commits into
base: master
Choose a base branch
from

Conversation

danielbate
Copy link
Member

@danielbate danielbate commented Jan 3, 2025

Release notes

In this release, we:

  • Added a frictionless subscription endpoint for submitting transactions and obtaining it's status

Summary

Added a new method on the provider which wraps the sendAndAwaitStatus subscription. This is a blocking call for consumers that want to frictionlessly submit a transaction and retrieve it's updated status. The response may be altered, once #3536 has come to a conclusion.

Usage:

const txRequest = new ScriptTransactionRequest();
txRequest.addResources(resources);
const { transactionId, status, receipts } = await provider.sendTransactionAndAwaitStatus(txRequest);

Checklist

  • All changes are covered by tests (or not applicable)
  • All changes are documented (or not applicable)
  • I reviewed the entire PR myself (preferably, on GH UI)
  • I described all Breaking Changes (or there's none)

@danielbate danielbate added the feat Issue is a feature label Jan 3, 2025
@danielbate danielbate self-assigned this Jan 3, 2025
Copy link

vercel bot commented Jan 3, 2025

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
fuels-template ✅ Ready (Inspect) Visit Preview 💬 Add feedback Jan 5, 2025 0:28am
ts-docs 🛑 Canceled (Inspect) Jan 5, 2025 0:28am
ts-docs-api 🛑 Canceled (Inspect) Jan 5, 2025 0:28am

@Dhaiwat10
Copy link
Member

This is awesome. Thanks @danielbate


When submitting transactions via the SDK, usually this is done by calling a method in a [script](../scripts/running-scripts.md) or [contract](../contracts/methods.md#call), or sending a transaction via a [predicate](../predicates/methods.md#sendtransaction) or [wallet](../wallets/index.md). These methods submit the transaction and then returns a [TransactionResponse](./transaction-response.md) that allows you to view the result of a transaction at your convenience, leaving the rest of your app processing unblocked.

However, if you want to send a transaction and continue subscribing to it's result, the SDK also exposes the `sendTransactionAndAwaitStatus` available on a [Provider](../provider/index.md). This method takes a [TransactionRequest](./transaction-request.md) and returns a minimal transaction result that contains the ID, status and receipts.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
However, if you want to send a transaction and continue subscribing to it's result, the SDK also exposes the `sendTransactionAndAwaitStatus` available on a [Provider](../provider/index.md). This method takes a [TransactionRequest](./transaction-request.md) and returns a minimal transaction result that contains the ID, status and receipts.
However, if you want to send a transaction and wait until it's processed, the SDK also exposes the `sendTransactionAndAwaitStatus` available on a [Provider](../provider/index.md). This method takes a [TransactionRequest](./transaction-request.md) and returns a minimal transaction result that contains the ID, status and receipts once the node has processed the transaction.

Comment on lines +2286 to +2290
using launched = await setupTestProviderAndWallets({
nodeOptions: {
args: ['--poa-instant', 'false', '--poa-interval-period', '1s'],
},
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
using launched = await setupTestProviderAndWallets({
nodeOptions: {
args: ['--poa-instant', 'false', '--poa-interval-period', '1s'],
},
});
using launched = await setupTestProviderAndWallets();

These args aren't necessary if you won't be testing for SqueezedOutStatus.

Comment on lines +2309 to +2313
using launched = await setupTestProviderAndWallets({
nodeOptions: {
args: ['--poa-instant', 'false', '--poa-interval-period', '1s'],
},
});
Copy link
Contributor

@nedsalk nedsalk Jan 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
using launched = await setupTestProviderAndWallets({
nodeOptions: {
args: ['--poa-instant', 'false', '--poa-interval-period', '1s'],
},
});
using launched = await setupTestProviderAndWallets();

as mentioned in #3541 (comment).

Comment on lines +2330 to +2334
using launched = await setupTestProviderAndWallets({
nodeOptions: {
args: ['--poa-instant', 'false', '--poa-interval-period', '1s'],
},
});
Copy link
Contributor

@nedsalk nedsalk Jan 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
using launched = await setupTestProviderAndWallets({
nodeOptions: {
args: ['--poa-instant', 'false', '--poa-interval-period', '1s'],
},
});
using launched = await setupTestProviderAndWallets();

as mentioned in #3541 (comment).

Comment on lines +933 to +973
async sendTransactionAndAwaitStatus(
transactionRequestLike: TransactionRequestLike,
{ estimateTxDependencies = true }: ProviderSendTxParams = {}
): Promise<SendAndAwaitStatusResponse> {
const transactionRequest = transactionRequestify(transactionRequestLike);
if (estimateTxDependencies) {
await this.estimateTxDependencies(transactionRequest);
}

this.validateTransaction(transactionRequest);

const encodedTransaction = hexlify(transactionRequest.toTransactionBytes());

const transactionId = transactionRequest.getTransactionId(this.getChainId());
this.#cacheInputs(transactionRequest.inputs, transactionId);

const subscription = (await this.operations.submitAndAwaitStatus({
encodedTransaction,
})) as AsyncIterable<GqlSubmitAndAwaitStatusSubscription>;

for await (const sub of subscription) {
const statusChange = sub.submitAndAwaitStatus;
if (statusChange.type === 'SqueezedOutStatus') {
this.#uncacheInputs(transactionId);
throw new FuelError(
ErrorCode.TRANSACTION_SQUEEZED_OUT,
`Transaction Squeezed Out with reason: ${statusChange.reason}`
);
}
if (statusChange.type !== 'SubmittedStatus') {
return {
transactionId,
status: statusChange.type,
receipts: statusChange.receipts.map(processGqlReceipt),
};
}
}

return { transactionId, status: 'unknown', receipts: [] };
}

Copy link
Contributor

@nedsalk nedsalk Jan 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
async sendTransactionAndAwaitStatus(
transactionRequestLike: TransactionRequestLike,
{ estimateTxDependencies = true }: ProviderSendTxParams = {}
): Promise<SendAndAwaitStatusResponse> {
const transactionRequest = transactionRequestify(transactionRequestLike);
if (estimateTxDependencies) {
await this.estimateTxDependencies(transactionRequest);
}
this.validateTransaction(transactionRequest);
const encodedTransaction = hexlify(transactionRequest.toTransactionBytes());
const transactionId = transactionRequest.getTransactionId(this.getChainId());
this.#cacheInputs(transactionRequest.inputs, transactionId);
const subscription = (await this.operations.submitAndAwaitStatus({
encodedTransaction,
})) as AsyncIterable<GqlSubmitAndAwaitStatusSubscription>;
for await (const sub of subscription) {
const statusChange = sub.submitAndAwaitStatus;
if (statusChange.type === 'SqueezedOutStatus') {
this.#uncacheInputs(transactionId);
throw new FuelError(
ErrorCode.TRANSACTION_SQUEEZED_OUT,
`Transaction Squeezed Out with reason: ${statusChange.reason}`
);
}
if (statusChange.type !== 'SubmittedStatus') {
return {
transactionId,
status: statusChange.type,
receipts: statusChange.receipts.map(processGqlReceipt),
};
}
}
return { transactionId, status: 'unknown', receipts: [] };
}
async sendTransactionAndAwaitStatus(
transactionRequestLike: TransactionRequestLike,
providerSendTxParams: ProviderSendTxParams = {}
): Promise<SendAndAwaitStatusResponse> {
const transactionResponse = await this.sendTransaction(transactionRequestLike, providerSendTxParams);
const result = await transactionResponse.waitForResult();
return {
transactionId: result.id,
status: result.status,
receipts: result.receipts,
};
}

It seems to me that we could do it as I suggested here and wouldn't lose anything while not duplicating the tx preparation and subscription parsing logic and having to maintain it in both places. Am I missing something? By doing what I mentioned in #3536 (comment) it wouldn't incur any additional overhead with regards to network calls.

Copy link
Contributor

github-actions bot commented Jan 5, 2025

Coverage Report:

Lines Branches Functions Statements
77.77%(-0.02%) 70.45%(-0.01%) 75.39%(+0.01%) 77.73%(-0.02%)
Changed Files:
Ok File (✨=New File) Lines Branches Functions Statements
🔴 packages/account/src/account.ts 78.82%
(+0%)
64.86%
(+1.35%)
80%
(+0%)
78.61%
(+0%)
🔴 packages/account/src/providers/provider.ts 68.99%
(-0.03%)
57.76%
(+0.62%)
71.27%
(+0.31%)
68.72%
(-0.01%)
🔴 packages/account/src/providers/transaction-request/transaction-request.ts 88.57%
(+0%)
76.71%
(-1.37%)
84%
(+0%)
88.81%
(+0%)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feat Issue is a feature
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants