0

Some wallets, like Gnosis Safe multisig, do not have web3.js support or ABI encoder built-in. How one can create a transaction that calls a smart contract function and originates from such wallets?

2

For Gnosis MultiSigWallet, I use the following:

function submitTransaction(options, msWalletAddr, contractAddr, contractAbi, functionName, functionArgs, privateKey) {
    return sign(options, msWalletAddr, "submitTransaction", [contractAddr, "0", encode(contractAbi, functionName, functionArgs)], privateKey);
}

function confirmTransaction(options, msWalletAddr, transactionId, privateKey) {
    return sign(options, msWalletAddr, "confirmTransaction", [transactionId], privateKey);
}

function executeTransaction(options, msWalletAddr, transactionId, privateKey) {
    return sign(options, msWalletAddr, "executeTransaction", [transactionId], privateKey);
}

function revokeConfirmation(options, msWalletAddr, transactionId, privateKey) {
    return sign(options, msWalletAddr, "revokeConfirmation", [transactionId], privateKey);
}

The options parameter is an object containing the following fields:

  • price (gas price)
  • limit (gas limit)
  • nonce (transaction nonce)

The functions sign and encode are implemented as follows:

const EthereumTx = require("ethereumjs-tx");

function sign(options, msWalletAddr, actionName, actionArgs, privateKey) {
    const params = {
        value   : 0, 
        chainId : 1,
        gasPrice: Number(options.price), 
        gasLimit: Number(options.limit),
        nonce   : Number(options.nonce),
        to      : msWalletAddr, 
        data    : encode(MultiSigWalletAbi, actionName, actionArgs)
    };
    const ethereumTx = new EthereumTx(params);
    ethereumTx.sign(Buffer.from(privateKey.slice(2), "hex"));
    return ethereumTx.serialize().toString("hex");
}

function encode(contractAbi, functionName, functionArgs) {
    for (const object of contractAbi)
        if (object.name == functionName)
            return web3.eth.abi.encodeFunctionCall(object, functionArgs);
    throw new Error("function " + functionName + " does not exist");
}

The variables web3 and MultiSigWalletAbi are assumed global and initialized.


As you can understand from value: 0, the code above supports only functions that do not pass ether, but that can easily be enabled by passing value is input.

As you can understand from chainId: 1, the code above supports only contracts deployed on mainnet, but that can easily be enabled by passing chainId is input.

2
  • 1
    Super good information sharing - thank you once again! – Mikko Ohtamaa May 2 '20 at 9:12
  • 1
    Also please note that Gnosis Multisig 2.0 a.k.a. Gnosis Safe for Teams is now out. The transaction signing mechanism seems to be a signature based. – Mikko Ohtamaa May 2 '20 at 9:12
1

You need to have ABI files for the smart contract. Then you can use Node.js console, JavaScript or TypeScript to generate a payload for Data to trigger a smart contract function call with the transaction.

Steps to make such a call

  1. Get ABI files needed for the smart contracts, usually from Github

  2. Create a snippet that is pasted in Node.js console for the ABI encoding.

  3. Get the result of this snippet to be used as Data in the transaction

  4. Create a new transaction. To field is the smart contract address and Data value is the result of the ABI encoding from the script.

Here is an example that uses TypeScript and OpenZeppelin SDK to craft data payload for approve() call.

import { ZWeb3, Contracts } from '@openzeppelin/upgrades';
import { createProvider } from './src/utils/deploy';

const INFURA_PROJECT_ID = '...';

async function run(): Promise<void> {
  // Initialze
  const provider = createProvider([], INFURA_PROJECT_ID, 'mainnet');
  ZWeb3.initialize(provider);

  // Instiate contracts
  const DawnTokenImpl = Contracts.getFromLocal('DawnTokenImpl');
  const TokenSwap = Contracts.getFromLocal('TokenSwap');
  const token = DawnTokenImpl.at('0x580c8520dEDA0a441522AEAe0f9F7A5f29629aFa');
  const tokenSwap = DawnTokenImpl.at('0x2e776B7BFb8E8307E476BA4B77B21D4532ed47d2');
  const holder = '0xedae4cfB12ECfCDE46853f63aBa76D8EA3CF3871';

  // Read the full balance of the multisig wallet
  const allOfBalance = await token.methods.balanceOf(holder).call();

  // Approve this balance to be used for the token swap
  const dataPayload = token.methods.approve(tokenSwap.address, allOfBalance).encodeABI();
  console.log('Data payload for approve() tx is', dataPayload);
}

run();

Example output is

Data payload for approve() tx is 0x095ea7b30000000000000000000000002e776b7bfb8e8307e476ba4b77b21d4532ed47d20000000000000000000000000000000000000000004d50b9c6c40cb6192cfce0

Further information how to run console script snippets in OpenZeppelin SDK environment.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.