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

How to get transaction hash immediately for transaction call? #511

Closed
cellog opened this issue May 10, 2019 · 28 comments
Closed

How to get transaction hash immediately for transaction call? #511

cellog opened this issue May 10, 2019 · 28 comments
Labels
discussion Questions, feedback and general information. duplicate Duplicate of another issue.

Comments

@cellog
Copy link

cellog commented May 10, 2019

Thank you again for ethers. Recently migrated all of our stuff from web3 to ethers, and it's been great.

There is one subtle but important difference between the two, however. When one calls web3.eth.sendTransaction, the promiEvent emits a transactionHash event as soon as it knows what the hash will be.

Ethers, on the other hand, does not return anything at the moment it receives the transaction hash, but instead polls getTransaction until we get our first mined block. Our UI is designed to show a "pending" state in between the submission of the transaction and either its first mined block or its failure.

Thus, instead we have a multi-second delay where nothing is shown except the modal we display to ask the user to confirm the transaction.

So, my question is whether there is a built-in way to get a poke of some kind when the transaction hash is available? If not, I suppose I could take the custom provider I made for using fetch instead of fetchJson and extend it to return a custom signer that returns a different form.

However, this is a significant UX problem, I suspect I will not be the first trying to migrate over from web3 to mention this.

Could we discuss the flow of what is returned? For example, our app does the following:

  • display a "pending" state in between the transaction hash retrieval and the first mined block (or failure to mine)
  • display a "confirming" state, showing how many confirmations there are, polling for new blocks since the transaction was mined
  • display a "confirmed" state when the minimum number of blocks have been mined since the first mined block

With web3, we had to implement our own polling. Ethers actually has polling built in (nice) but there are just a couple of places we need information that ethers does not yet provide.

  • between the transaction hash and the first successful mining, we need the hash
  • at every block mined, prior to confirmations, we need to see the current status of the transaction

The other aspect of how we do this that is a bit unusual is that we use completely separate providers for our transaction sending, and for our transaction polling.

In any case, would love your advice on how to do this without changing ethers (if possible), and your thoughts about these use cases.

@ricmoo
Copy link
Member

ricmoo commented May 10, 2019

Please see this solution, that should handle everything you need. :)

In v5, the UncheckedJsonRpcProvider is built-in, and can either be accessed directly from the jsonRpcProvider.getUncheckedSigner(addressOrIndex) or from a jsonRpcProvider.connectUnchecked().

The returned TransactionResponses will not have any data populated, but the tx.wait(), and similar functions can be used to further wait until the transaction is ready.

I'm also talking to several people regarding eth2.0 to solve this problem at the core, which is the signed transaction should be returned, not the transaction hash. It is east to compute the transaction hash from a signed transaction, it is impossible to compute the transaction from a transaction hash. Both are available, the API service call just drops it before returning it, which I think was to match the Bitcoin API more closely.

Let me know if this helps. :)

@ricmoo ricmoo added discussion Questions, feedback and general information. duplicate Duplicate of another issue. labels May 10, 2019
@cellog
Copy link
Author

cellog commented May 10, 2019

yes! thank you.

@cellog
Copy link
Author

cellog commented May 10, 2019

Unfortunately, that solution does not quite provide parity. I built our tests using nock, to verify that the HTTP requests sent to the server match, and after making this change, they hang infinitely. May need some help later.

@mrwillis
Copy link

@ricmoo Is there a solution for if you are using a Wallet instead? Can I subclass Wallet?

@ricmoo
Copy link
Member

ricmoo commented May 11, 2019

You can subclass Wallet. What functionality are you trying to add?

@mrwillis
Copy link

mrwillis commented May 11, 2019 via email

@mrwillis
Copy link

mrwillis commented May 11, 2019 via email

@ricmoo
Copy link
Member

ricmoo commented May 11, 2019

Oh, a Wallet will always return immediately after sending, since it knows what was signed. This is only a problem for JSON-RPC. The LedgerSigner, Wallet, and any other Signer just work. The problem is the JSON-RPC API, which other methods of sending transactions do not need to deal with. :)

@ricmoo
Copy link
Member

ricmoo commented May 11, 2019

@cellog Weird... Sounds like an event handler is still pending. Keep in mind the ethers does not unref() any timer by default. Maybe you are using contract.on instead of contract.once? You can check whether a provider is polling using console.log(provider.polling), and to help debug those issues, console.log(provider._events) to see what type of events are remaining schedule in the event loop.

@cellog
Copy link
Author

cellog commented May 14, 2019

Sorry for the delay - I ended up getting it sorted using this source: https://github.com/unlock-protocol/unlock/blob/master/unlock-js/src/FastJsonRpcSigner.js which provides 1-to-1 parity with the old way, just adds an intermediary wait() call to the transaction result.

@ricmoo
Copy link
Member

ricmoo commented May 24, 2019

One of the issues with your FastJsonRpcSigner is the reason we have to be sucky at JSON-RPC signing in the first place, many things may not be specified. For example, a user could use:

signer.sendTransaction({
    to: address,
    value: parseEther("1.0")
});

Notice that gasPrice, nonce, et al. are not specified. Those are populated by the node, so in your code gasLimit: utils.bigNumberify(utils.hexStripZeros(utils.hexlify(transaction.gasLimit))) and company will fail. :s

You can check out the UncheckedJsonRpcSigner in v5 for, and note that even though the values are unpopulated, the .wait() will return the populated receipt for you. :)

See: https://github.com/ethers-io/ethers.js/blob/ethers-v5-beta/packages/providers/src.ts/json-rpc-provider.ts#L187

@cellog
Copy link
Author

cellog commented May 25, 2019

Thanks for checking into this. Since our use case is so narrow (only sending specific transactions to 1 smart contract), we won't run into that issue.

I'm working up against a deadline for a project, so I won't take a look at v5 until after next week, but I'm looking forward

@ricmoo
Copy link
Member

ricmoo commented Jun 4, 2019

Cool cool. I think this issue is taken care of now? If not, please feel free to re-open.

Thanks! :)

@ricmoo ricmoo closed this as completed Jun 4, 2019
@hilmarx
Copy link

hilmarx commented Nov 6, 2019

I'm using "ethers": "^4.0.39", and when I send a transaction to a contract using ...

const dummyContract = new ethers.Contract(dummyAddress, DUMMY_ABI, signer)
let result = await dummyContract.increment()

... then the result won't be the txHash that I need to prompt the user with the respective etherscan link, but the full transaction object after being mined. Hence it does what I thought result.wait() would do.

Is there a way to actually receive the txHash when the user actually sends the tx? Thanks!

@kevin-wad
Copy link

Hi @hilmarx , I have the same issue ! Did you manage to find a solution ? It's the kind of pb that makes us waste time for nothing although it should be considered as one of the main features and explained properly but I can't find any information about this. Best regards

@ricmoo
Copy link
Member

ricmoo commented Nov 18, 2019

The tx object returned should have a .hash property that contains the hash. Or are you using an older version of MetaMask that does not populate the tx from its internal tx pool? For that, you can use the UncheckedSigner (typing on my phone, I will search the issues for that link and include it in the thread shortly). In v5, the UncheckedSigner is built-in, so you can use Provider.getUncheckedSigner().

This was addressed by MetaMask though, so recent versions should not suffer this problem anymore.

@ricmoo
Copy link
Member

ricmoo commented Nov 18, 2019

See this for the UncheckedSigner for v4.

@kevin-wad
Copy link

Hi @ricmoo thank you for reply. Yes the response contains the transaction hash, but the issue is from a UI point of view. As developpers we need to let user know that its transaction is being processed and pending. The pb is that the response only comes after transaction has been mined, thus we can't show a loader to let user wait for the transaction because we don't know if the transaction is being processed we just get the result of mined transaction. What we need is to get transaction hash immediatly after user confirmed transaction on metamask. And after we check for transaction status trough liseners, but the current way makes listeners useless because response comes back too late once everything has been done. Best regards

@ricmoo
Copy link
Member

ricmoo commented Nov 18, 2019

@kevin-wad exactly what the UncheckedSigner does; returns at tx with the hash immediately and leaves the rest of the TX empty (still with a wait). This is also fixed in the most recent MetaMask, it will now return the entire TX immediately, so the UncheckedSigner will become increasingly useless as more people upgrade. Sorry, I don’t know what pb stands for... :s

@kevin-wad
Copy link

Ok thank you very much @ricmoo ! pb means problem. Best regards

@kevin-wad
Copy link

Hi @ricmoo apparently the issue is still present on Metamask, I updated to 7.5.3 which is the current last version, but I still get this issue. Maybe I need to delete and re download ? But I think the issue is not gone

@ricmoo
Copy link
Member

ricmoo commented Nov 18, 2019

Let me double check with MM that they’ve merged the change into the version they published on the Chrome Store then. The latest version on GitHub should work, but that is not very useful for most humans. :)

@kevin-wad
Copy link

Hi @ricmoo, did you find what is going on ? Best regards

@kevin-wad
Copy link

So @ricmoo, do you have any update on this issue what is going on ? Thanks again and Best regards

@adrianmcli
Copy link

@ricmoo It seems like this is still a problem. @kevin-wad Can you confirm?

I am on MetaMask 7.7.8.

@tomarsachin2271
Copy link

tomarsachin2271 commented Dec 23, 2020

i solved it by replacing the signer object with signer.connectUnchecked()
so it'll be

let signer = ethersProvider.getSigner();
let contract = new ethers.Contract(address, abi, signer.connectUnchecked());
let tx = await contract.method();

// this will return immediately with tx.hash and tx.wait property

console.log("Transaction hash is ", tx.hash);
let receipt = await tx.wait();

console.log("Transaction is confirmed");

@iamRishvanth
Copy link

@tomarsachin2271 , it works fine for the smart contract function which doesn't return any value from it. what about the smart contract function which return value.

i need both return value and Txn. how can I do that?

@ricmoo
Copy link
Member

ricmoo commented Aug 22, 2023

@iamRishvanth The return value of a transaction is not generally available. You must pay for every byte permanently stored on the blockchain, and since there is no additional cost to return a value, it isn’t stored.

This is why cases where return values are required, you would generally use an event, which you do pay an additional 375 gas for (and additional beyond that each additional log entry, data, etc.).

If you have access to an archive node or a node with debug or trace APIs available, you can use those API to replay the tx at the blockchain state, which will give you the return value.

You can use the provider.getTransactionResult(hash) in those cases to get the return value.

Make sense?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
discussion Questions, feedback and general information. duplicate Duplicate of another issue.
Projects
None yet
Development

No branches or pull requests

8 participants