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

Improve SDK and Wallet Communication #3669

Open
danielbate opened this issue Feb 6, 2025 · 6 comments · May be fixed by #3735
Open

Improve SDK and Wallet Communication #3669

danielbate opened this issue Feb 6, 2025 · 6 comments · May be fixed by #3735
Assignees
Labels
feat Issue is a feature

Comments

@danielbate
Copy link
Member

danielbate commented Feb 6, 2025

Problem

When a transaction is passed from an application to the wallet, the wallet performs estimation and funding network requests that have already been performed by the SDK. This can be visualised in the following diagram:

Image

This is because the wallet has no knowledge of the state of a transaction, so attempts to account for anything that could be missing.

Solution

We should improve handoff from the SDK to connectors/wallets so that they can act accordingly, and not introduce any redundancy in the transaction flow.

Currently, the context that the SDK provides to the wallet includes:

interface SendTransactionJsonRpc {
  address: string,
  transaction: TransactionRequest,
  provider: {
    url: string,
  },
}

This should be extended to:

  interface SendTransactionJsonRpc {
    address: string,
    transaction: TransactionRequest,
    provider: {
      url: string,
      maxInputs: number, // required for estimatiom
      estimatedGasPrice: number, // required for estimation, so the wallet does not need to re-fetch this value
    },
    isEstimated: boolean, // mitigate any redundant dry-runs
    isFunded: boolean, // mitigate any redundant 
    isSigned: boolean, // tx should not be augmented, invalidating any signatures present i.e. gasless paymaster
    receipts: Receipt[] // reciepts to build out the final tx summary on the approval screen
  }

With the following additional information, the wallet can act accordingly and should not perform any redundant network requests.

Note

The specific work that needs to be done in the SDK, Connectors and Wallet is still being worked out but will be added to this issue

@danielbate
Copy link
Member Author

danielbate commented Feb 13, 2025

Have discussed further with @petertonysmith94 and we have come up with the following phased approach:

Phase 1 - Estimation and Funding

Phase 1 aims to give more context to the wallet as to whether it is needs to perform the estimation and funding stages.

This includes the addition of the isEstimated , isFunded and isSigned flags to the Json RPC object:

 interface SendTransactionJsonRpc {
    address: string,
    transaction: TransactionRequest,
    provider: {
      url: string,
    },
    isEstimated: boolean,
    isFunded: boolean,
    isSigned: boolean,
  }

For the implementation, we will pass an object throughout the transaction preparation flow:

{ isEstimated: false, isFunded: false, isSigned: false }

When an operation is performed that sets one of these flags, we will store the transaction hash against the flag:

{ isEstimated: '0x123..., isFunded: false, isSigned: false }

On submission, the final transaction hash is then checked against the flags.

If they are still valid, they are passed to the wallet.

Phase 2 - Summary Generation

Phase 2 will then provide more context around provider values used for estimation, that can also be used in transaction summary generation.

This includes the addition of the chain and gas properties to the JSON RPC provider object:

 interface SendTransactionJsonRpc {
    ...
    provider: {
      ...
      maxInputs: BN,
      estimatedGasPrice: BN,
      maxGasPerTx: BN,
      maxGasPerPredicate: BN,
      gasPriceFactor: BN,
      gasPerByte: BN,
      gasCosts: GasCosts,
    },
    ...
  }

For the implementation, on submission the chain parameters are grabbed from the cache and added to the transmission object.

estimatedGasPrice is still TBD but will likely involve some kind of cache mechanism.

Phase 3 - Summary on load

Phase 3 will provide a complete transaction summary and/or receipts to the wallet, that will allow it to synchronously generate a summary without fetching any other information.

This includes the addition of a complete transaction summary and receipts to the JSON RPC object:

 interface SendTransactionJsonRpc {
	 ...
   summary: TransactionSummary,
   receipts: Receipt[]
  }

Implementation is still TBD here.

@danielbate
Copy link
Member Author

Based on feedback in #3709 and some further investigation, we are going to update the above spec.

@petertonysmith94
Copy link
Contributor

petertonysmith94 commented Feb 20, 2025

Analysis of how eco-system projects are estimating and funding transactions:

Scenarios

Identifying use cases

React Hooks

This returns an Account instance

Mira

Pass through the gasCosts as part of the fund

import {useWallet} from "@fuels/react";

// wallet: Account
const {wallet} = useWallet();

// Fund + estimate
const gasCost = await wallet.getTransactionCost(txRequest);
const fundedTx = await wallet.fund(txRequest, gasCost);

// Send
const tx = await wallet.sendTransaction(fundedTx, {
  estimateTxDependencies: true,
});

Griffy

Uses both predicate estimates and setting of the gasLimit + maxFee

import { useWallet } from "@fuels/react";

const { wallet } = useWallet();

// Estimate
let txCost: TransactionCost = await predicate.getTransactionCost(
  transactionRequest
);

transactionRequest.updatePredicateGasUsed(txCost.estimatedPredicates);
transactionRequest.gasLimit = txCost.gasUsed;
transactionRequest.maxFee = txCost.maxFee;

// Fund
await predicate.fund(transactionRequest, txCost);

// Send
const tx = await wallet.sendTransaction(transactionRequest);

Uses the estimateTxGasAndFee to get the maxFee and then sets it on the request

const { maxFee } = await predicate.provider.estimateTxGasAndFee({
    transactionRequest: requestToReestimate,
    gasPrice,
});

request.maxFee = maxFee;

Swaylend

Setup the related contracts with the wallet

import { useWallet } from '@fuels/react';
const { wallet } = useWallet();

const pythContract = new PythContract(
  appConfig.markets[market].oracleAddress,
  walletOrProvider
);

const marketContract = new Market(
  appConfig.markets[market].marketAddress,
  walletOrProvider
);

updateContracts(market, pythContract, marketContract);

Perform calls on the contracts (using the above initialized contracts)

const { waitForResult } = await marketContract.functions
  .withdraw_base(amount.toFixed(0), priceUpdateData)
  .callParams({
    forward: {
      amount: priceUpdateData.update_fee,
      assetId: appConfig.baseAssetId,
    },
  })
  .addContracts([pythContract])
  .call();

@danielbate
Copy link
Member Author

danielbate commented Feb 20, 2025

State Flags Re-Spec

Following a conversation with @petertonysmith94 on the above topic, we are currently discussing the follow spec:

To describe the level of preparedness for a transaction, we shall pass a txState property to the wallet. It can have the following values:

  • funded - this is where the transaction has been augmented using a funding method (this is solely on Account.fund as that is the lowest funding level used by other abstractions in the SDK.
  • signed - this is a transaction that is already signed, so the only changes to the tx should be additional signatures (i.e. gasless paymaster).

Estimation is currently TBD as most estimation related methods return estimated values and do not augment the transaction request, this is done by the user.

This will be stored against the transaction request so that it is still the sole object that a user needs to pass throughout the transaction flow.

We will update the state property through the tracking of the transaction hash. Consider the following flow:

// State is currently `state: { value: undefined, txId: undefined }`
const txRequest = new ScriptTransactionRequest();
txRequest.addCoinOutput(sender.address, bn(1000), await provider.getBaseAssetId());

// State set to `state: { value: 'funded', txId: '0x123' }`
await txRequest.estimateAndFund(sender);

// txId is checked against `state.txId`, if it matches,
// then `state.value` is propagated to the wallet, otherwise
// `state.value` is set back to `undefined`
await sender.sendTransaction(txRequest);

The phase 1 interface now looks like so:

 interface SendTransactionJsonRpc {
    address: string,
    transaction: TransactionRequest,
    provider: {
      url: string,
    },
    state: 'funded' | 'immutable',
  }

@danielbate
Copy link
Member Author

danielbate commented Feb 20, 2025

Chain Info Re-Spec

Please see PR for specification.

Here we will now pass across anything that has already been fetched from the chain, include the chain configuration and an estimate/lastest gas price if they have been fetched.

 interface SendTransactionJsonRpc {
    address: string,
    transaction: TransactionRequest,
    provider: {
      ...,
      cache: {
        consensusParameterTimestamp: string,
        chainInfo: ChainInfo,
      }
    },
    data: {
       latestGasPrice: string,
       estimatedGasPrice: string,
    }
    ...
  }

To support this, we should also enable a way of initialising a provider and pass through a chain info cache, allowing it to be shared between the SDK and the wallet.

const provider = new Provider(sdkJsonRpc.provider.url, { chainInfo: sdkJsonRpc.provider.chainInfo });

@danielbate
Copy link
Member Author

danielbate commented Feb 21, 2025

Summary Re-Spec

interface SendTransactionJsonRpc {
    ...
   summary: TransactionSummary
}

For the summary, we need to only provide the summary as receipts will be nested inside of there also.

After some validation and conversations with @petertonysmith94 and @Torres-ssf, we can use the receipts from the initial dry run in getTransactionCost in our final transaction summary. As per @LuizAsFight request, we should use assembleTransactionSummary() to produce a full tx summary for the wallet.

So if state === 'funded' this summary can be used without any additional fetches.

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 a pull request may close this issue.

2 participants