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

TypeScript error: ethers.Transaction.from won't accept ethers.TransactionRequest #4659

Closed
lastmjs opened this issue Mar 21, 2024 · 8 comments
Assignees
Labels
documentation Documentation related issue. fixed/complete This Bug is fixed or Enhancement is complete and published. v6 Issues regarding v6

Comments

@lastmjs
Copy link

lastmjs commented Mar 21, 2024

Ethers Version

6.11.1

Search Terms

No response

Describe the Problem

While implementing signTransaction within my own concrete implementation of ethers.AbstractSigner, I am trying to create a transaction using ethers.Transaction.from with an ethers.TransactionRequest as the argument. I would think that this could work, but unfortunately there is a TypeScript error. At runtime everything is working fine thus far.

Code Snippet

// implementing my own ethers.AbstractSigner
async signTransaction(
    txRequest: ethers.TransactionRequest
): Promise<string> {
    let tx = ethers.Transaction.from(txRequest);
    // Here I have custom signing logic doing threshold ECDSA
}

Contract ABI

No response

Errors

Argument of type 'TransactionRequest' is not assignable to parameter of type 'string | TransactionLike<string> | undefined'.
  Type 'TransactionRequest' is not assignable to type 'TransactionLike<string>'.
    Types of property 'to' are incompatible.
      Type 'AddressLike | null | undefined' is not assignable to type 'string | null | undefined'.
        Type 'Promise<string>' is not assignable to type 'string'.ts(2345)

Environment

node.js (v12 or newer)

Environment (Other)

VS Code

@lastmjs lastmjs added investigate Under investigation and may be a bug. v6 Issues regarding v6 labels Mar 21, 2024
@ricmoo
Copy link
Member

ricmoo commented Mar 21, 2024

The issue is that a Transaction’s operations are all synchronous, while a TransactionRequest allows the to and from to be specified as ENS names (which are strings, so not the typing issue you are having; but do require async access) or allows them to be an Addressable, such as a Signer or Contract, whose address is only accessible via async methods.

If you are certain your transaction has a synchronously accessible to and/or from (undefined is perfectly valid as a synchronously accessible value ;)) you can cast it.

Also, if you use the signer.populateTransaction, that should return a TransactionLike, which is safe to pass to Transaction.from.

Does that make sense?

@lastmjs
Copy link
Author

lastmjs commented Mar 22, 2024

This is what I'm trying to do, I am implementing signTransaction, so I have been given a TransactionRequest, I cannot change that signature. I need to construct a transaction as shown and do these custom operations on it, so how can I create a transaction from a txRequest?:

    async signTransaction(
        txRequest: ethers.TransactionRequest
    ): Promise<string> {
        // TODO Hopefully we can remove the explicit any after this is resolved: https://github.com/ethers-io/ethers.js/issues/4659
        let tx = ethers.Transaction.from(txRequest as any);

        const unsignedSerializedTx = tx.unsignedSerialized;
        const unsignedSerializedTxHash = ethers.keccak256(unsignedSerializedTx);

        const signedSerializedTxHash = await signWithEcdsa(
            this.thresholdKeyInfo,
            ethers.getBytes(unsignedSerializedTxHash)
        );

        if (this.provider === null) {
            throw new Error(`ThresholdWallet: provider must not be null`);
        }

        const network = await this.provider.getNetwork();
        const chainId = Number(network.chainId);

        const { r, s, v } = calculateRsvForTEcdsa(
            chainId,
            await this.getAddress(),
            unsignedSerializedTxHash,
            signedSerializedTxHash
        );

        tx.signature = {
            r,
            s,
            v
        };

        const rawTransaction = tx.serialized;

        return rawTransaction;
    }

MIT License

// Copyright (c) 2023 AZLE token holders (nlhft-2iaaa-aaaae-qaaua-cai)

// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:

// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.

// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

@lastmjs
Copy link
Author

lastmjs commented Mar 22, 2024

Ah, are you saying that I can do this?

    async signTransaction(
        txRequest: ethers.TransactionRequest
    ): Promise<string> {
        let txLike = await this.populateTransaction(txRequest);
        let tx = ethers.Transaction.from(txLike);

@lastmjs
Copy link
Author

lastmjs commented Mar 22, 2024

This isn't working unfortunately:

Error: unsigned transaction cannot define from (argument="tx", value={ "accessList": [  ], "chainId": 84532, "data": "0x", "from": "0xCf26D65Eaf326F3Ae2107E53E12c1C89067450fD", "gasLimit": 21000, "maxFeePerGas": 1000568, "maxPriorityFeePerGas": 1000000, "nonce": 0, "to": "0x5DE9a1C39b7CE15Df5908DF712C2a9053aB22533", "type": 2, "value": 7 }, code=INVALID_ARGUMENT, version=6.11.1)

I haven't specified from, this is failing on this transaction:

                const wallet = new ThresholdWallet(
                    {
                        derivationPath: [ic.id().toUint8Array()]
                    },
                    ethers.getDefaultProvider('https://sepolia.base.org')
                );

                const to = req.body.to;
                const value = ethers.parseEther(req.body.value);
                const gasLimit = 21_000n;

                const tx = await wallet.sendTransaction({
                    to,
                    value,
                    gasLimit
                });

@ricmoo
Copy link
Member

ricmoo commented Apr 10, 2024

Oh! That's because you are including a "from" address. If you remove that, it should work.

When you create a Transaction object, it verifies anything that needs verification (such as from and hash). If the signature is not present, these fields are invalid (as it cannot validate them).

I just tried your example deleting the from, and things work fine from that point on. :)

The property names "from" and "to" are confusing in error messages. I should probably wrap them in backticks or something to indicate it is the property name, not a preposition. :)

@ricmoo
Copy link
Member

ricmoo commented Apr 10, 2024

Quick aside: the reason you cannot "set" the .from is because on the Transaction object it is computed property (via a getter).

The easiest way to drop it is probably Transaction.from(Object.assign({ }, txInfo, { from: undefined })) which will just overwrite the value passed in with undefined.

@ricmoo
Copy link
Member

ricmoo commented Apr 17, 2024

The error message now includes quotes around the property name so it doesn't look like an incomplete error message randomly ending in a preposition that makes no sense. :)

Thanks! :)

@ricmoo ricmoo closed this as completed Apr 17, 2024
@ricmoo ricmoo added documentation Documentation related issue. fixed/complete This Bug is fixed or Enhancement is complete and published. and removed investigate Under investigation and may be a bug. labels Apr 17, 2024
@lastmjs
Copy link
Author

lastmjs commented Apr 18, 2024

This isn't working unfortunately:

Error: unsigned transaction cannot define from (argument="tx", value={ "accessList": [  ], "chainId": 84532, "data": "0x", "from": "0xCf26D65Eaf326F3Ae2107E53E12c1C89067450fD", "gasLimit": 21000, "maxFeePerGas": 1000568, "maxPriorityFeePerGas": 1000000, "nonce": 0, "to": "0x5DE9a1C39b7CE15Df5908DF712C2a9053aB22533", "type": 2, "value": 7 }, code=INVALID_ARGUMENT, version=6.11.1)

I haven't specified from, this is failing on this transaction:

                const wallet = new ThresholdWallet(
                    {
                        derivationPath: [ic.id().toUint8Array()]
                    },
                    ethers.getDefaultProvider('https://sepolia.base.org')
                );

                const to = req.body.to;
                const value = ethers.parseEther(req.body.value);
                const gasLimit = 21_000n;

                const tx = await wallet.sendTransaction({
                    to,
                    value,
                    gasLimit
                });

Sorry I'm confused at your last few comments. There is no from anywhere in my example here, so not sure what the disconnect is

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
documentation Documentation related issue. fixed/complete This Bug is fixed or Enhancement is complete and published. v6 Issues regarding v6
Projects
None yet
Development

No branches or pull requests

2 participants