Ethers.js: ABI Encoding for offline method calls

Created on 15 May 2020  Â·  7Comments  Â·  Source: ethers-io/ethers.js

Hi !
I'm using ethers in combination with web3 in order to achieve the following:

  • have the user import his json wallet/restore from mnemonic
  • connect to the smart contract
  • create a transaction that calls a non-constant method of the SC (ethers)
  • sign the transaction offline or client side
  • send the transaction to any node
    I need this so that the user can be the only one with access to the wallet

It seems fine with simple methods, like those that pass integers. But I got issues with more complex data. The code I use is this:

                    w1 =  new ethers.Wallet(priv, provider);
                    iface = new ethers.utils.Interface(contract_abi);
                    txdata = iface.functions[method].encode(args);
                    signonce = await w1.getTransactionCount();
                    tx = {
                        "from" : addr,
                        "to" : contract_address,
                        "gasLimit" : 1500000,
                        "gasPrice" : 2,
                        "nonce" : signonce,
                        "data" : txdata,
                        "value" : 0,
                    };
                    web3 = new Web3( new Web3.providers.HttpProvider(rpcEndpoint));
                    account = await  web3.eth.accounts.privateKeyToAccount(priv);
                    stx1 = await web3.eth.accounts.signTransaction(tx, priv);
                    web3.eth.sendSignedTransaction(stx1.rawTransaction)

This part works well. The problem is creating agrs to be encoded.
If a prameter is an int or a string, no problem.

var args = {"i":32};

But how do I encode bytes? arrays? addresses?
I get errors about the format/type/length of the parameter

Any help/advice/feedback would be greatly appreciated.

TIA

discussion

Most helpful comment

@xzbnm

// This line returns the transaction object, which is already signed and sent
let unsignedTx = await contract.functions.populateTransaction(toAddress,numberOfTokens,transaction)

// Just replace that line with bellow one, it should probably work in your code
let unsignedTx = await contract.populateTransaction.populateTransaction(toAddress,numberOfTokens,transaction)

Edit: Hey, I got a feeling that you probably don't have a populateTransaction in a smart contract and you actually meant to create a populated tx for transfer method? If that's the case:

let unsignedTx = await contract.populateTransaction.transfer(toAddress,numberOfTokens,transaction)

All 7 comments

Here some extra attempts that are failing

args["_itemId"]
1.0857805267182605e+77
iface
Object { functions: {…}, events: {…}, abi: (57) […], deployFunction: {…}, … }

method
"transferEnable"
args
Object { _itemId: 1.0857805267182605e+77 }

iface.functions[method]
Object { inputs: (1) […], outputs: [], gas: undefined, payable: false, type: "transaction", signature: "transferEnable(bytes32)", sighash: "0x0eb8ed07", … }

iface.functions[method].encode({"_itemId": 1})
Error: invalid input argument (arg=undefined, reason="types/values length mismatch", value={"types":[{"name":"_itemId","type":"bytes32"}],"values":{"_itemId":1}}, version=4.0.11) ethers-v4.min.js:1:16093
Source map error: request failed with status 404
Resource URL: http://localhost/dist/ethers-v4.min.js
Source Map URL: ethers.min.js.map

iface.functions.transferEnable.encode({"_itemId": 1})
Error: invalid input argument (arg=undefined, reason="types/values length mismatch", value={"types":[{"name":"_itemId","type":"bytes32"}],"values":{"_itemId":1}}, version=4.0.11) ethers-v4.min.js:1:16093
iface.functions.transferEnable.encode({"_itemId": 0x1})
Error: invalid input argument (arg=undefined, reason="types/values length mismatch", value={"types":[{"name":"_itemId","type":"bytes32"}],"values":{"_itemId":1}}, version=4.0.11) ethers-v4.min.js:1:16093
iface.functions.transferEnable.encode({"_itemId": 0xf00d000000000000cabba51af28578cf7f88a2966f64759b7f6a0ed7e000664a})
Error: invalid input argument (arg=undefined, reason="types/values length mismatch", value={"types":[{"name":"_itemId","type":"bytes32"}],"values":{"_itemId":1.0857805267182605e+77}}, version=4.0.11) ethers-v4.min.js:1:16093
iface.functions.transferEnable.encode({"_itemId": '0xf00d000000000000cabba51af28578cf7f88a2966f64759b7f6a0ed7e000664a'})
Error: invalid input argument (arg=undefined, reason="types/values length mismatch", value={"types":[{"name":"_itemId","type":"bytes32"}],"values":{"_itemId":"0xf00d000000000000cabba51af28578cf7f88a2966f64759b7f6a0ed7e000664a"}}, version=4.0.11) ethers-v4.min.js:1:16093
iface.functions.transferEnable.encode({"_itemId": 'f00d000000000000cabba51af28578cf7f88a2966f64759b7f6a0ed7e000664a'})
Error: invalid input argument (arg=undefined, reason="types/values length mismatch", value={"types":[{"name":"_itemId","type":"bytes32"}],"values":{"_itemId":"f00d000000000000cabba51af28578cf7f88a2966f64759b7f6a0ed7e000664a"}}, version=4.0.11)

Sorry, my bad
Found the issue. Args must be in the form

 args = Object.values({...object...});

Heya!

I also recommend you try out v5, which has a. simpler API for dealing with Interface and Contracts for offline signing.

w1 =  new ethers.Wallet(priv, provider);
contract = new ethers.Contract(contract_address, contract_abi, w1);
overrides = {
  nonce: w1.getTransactionCount(),
  gasLimit: 1500000,
  gasPrice: utils.parseUnits("2.0", "gwei"),
  value: 0
};
unsignedTx = await contract.populateTransaction[method](...args, overrides);

signedTx = w1.signTransaction(unsignedTx);

provider.sendTransaction(signedTx);

This is all doable in v4 too, but v5 specifically added some API to make offline-signing easier. :)

Closing this now, but feel free to re-open if you feel it hasn't been addressed.

Thanks! :)

hi ,I'm trying to create rawHex of trasnction (offline mod)

let value = 0     // for test
let toAddress = '0x663b58B1620C545217d3df30122fa7ED1346fFf0'
let contractAddress = '0xF6fF95D53E08c9660dC7820fD5A775484f77183A' //  token contract address
var numberOfDecimals = 8
var numberOfTokens = ethers.utils.parseUnits(value.toString(), numberOfDecimals)

let provider = ethers.getDefaultProvider('ropsten')
let wallet = new ethers.Wallet(this.privatekeyETH,provider)
const abi = [ "function transfer(address,uint)",
                "function populateTransaction(address,uint)"]

const contract = new ethers.Contract(contractAddress, abi, wallet)
let transaction = {
    gasLimit: Number(this.gasLimit),
    gasPrice: ethers.BigNumber.from(this.gasPrice*1000000000), //1Gwei =1000,000,000
    // nonce: this.Nonces
    // to: toAddress,
    // value: numberOfTokens,

}
let unsignedTx = await contract.functions.populateTransaction(toAddress,numberOfTokens,transaction)

await wallet.signTransaction(unsignedTx).then((tx) => {
                console.log(tx)
                // this.showLoading = ''
            }).catch((e) => {
                // this.showLoading = ''
                console.log("errror = "+e);
            })

The error I got is ###
```json

Error: invalid object key - v (argument="transaction:v", value=
{"nonce":45,
"gasPrice":{"_hex":"0xb2d05e00","_isBigNumber":true},
"gasLimit":{"_hex":"0x0493e0","_isBigNumber":true},
"to":"0xF6fF95D53E08c9660dC7820fD5A775484f77183A",
"value":{"_hex":"0x00","_isBigNumber":true},

"data":"0x2b1ad867000000000000000000000000663b58b1620c545217d3df30122fa7ed1346fff00000000000000000000000000000000000000000000000000000000000000000",
"chainId":3,
"v":42,
"r":"0xf2bf576f42430baa39df8d63d01b3308c875ae38e2eb66a64f42347ff6743c91",
"s":"0x5be3f94b6ed21d93f65c86d1f1fa8bb9a7ed69a4c78ec9879a70ab2684ab7372",
"hash":"0xac08e5f9d7c8796317ff96290effbf41e846eef077cbca3527c652bcc5b65ce9"
},
code=INVALID_ARGUMENT, version=properties/5.0.1)

@xzbnm

// This line returns the transaction object, which is already signed and sent
let unsignedTx = await contract.functions.populateTransaction(toAddress,numberOfTokens,transaction)

// Just replace that line with bellow one, it should probably work in your code
let unsignedTx = await contract.populateTransaction.populateTransaction(toAddress,numberOfTokens,transaction)

Edit: Hey, I got a feeling that you probably don't have a populateTransaction in a smart contract and you actually meant to create a populated tx for transfer method? If that's the case:

let unsignedTx = await contract.populateTransaction.transfer(toAddress,numberOfTokens,transaction)

```javascript

let unsignedTx = await contract.populateTransaction.transfer(toAddress,numberOfTokens,transaction)
````
This resolved my issue! Thanks!

Was this page helpful?
0 / 5 - 0 ratings

Related issues

lastmjs picture lastmjs  Â·  31Comments

elenadimitrova picture elenadimitrova  Â·  28Comments

MicahZoltu picture MicahZoltu  Â·  24Comments

bpierre picture bpierre  Â·  43Comments

subramanianv picture subramanianv  Â·  34Comments