Unable to recover signer address using api/v1/safes/{address}/multisig-transactions

I am trying to sign a valid transaction hash (obtained from the onchain Gnosis Safe wallet itself, so it is correct) but when I sign the hash and then provide it to the multisig-transactions endpoint to propose the tx to Safe’s backend, where it can later be signed by other owners of the safe, the signing address that is recovered from the signature I provide does not match the address that I actually used to sign the transaction hash.

*note I unfortunately cannot use the SDK in this scenario

The signature is wrong because of some reason like ethersJS is prepending ‘x19 Ethereum Signed Message’ to the hash or because I am signing an ethers.utils.arrayify (ie bytes) version of the hexstring hash. I have tried all permutations I can think of to fix the invalid signature but I keep receiving response 422: "Signer=0xC2278C77001F5c5164ed97Ff945f6B0E6e006363 is not an owner or delegate. Current owners=[${owners}]

The code in question:

const getAndSignTxHash = () => {
  // get txhash from contract
  // The Gnosis Safe contract ABI
  const safeAbi = fetch(
    "https://raw.githubusercontent.com/safe-global/safe-deployments/main/src/assets/v1.3.0/gnosis_safe_l2.json"
  ).body;
  const abiJson = JSON.parse(safeAbi)["abi"];
  const signer = Ethers.provider().getSigner();
  const safe = new ethers.Contract(state.safeAddress, abiJson, signer);

  // obtain txHash
  const txHash = safe
    .getTransactionHash(
      state.recipient,
      state.value,
      "0x", //state.data,
      state.operation,
      state.safeTxGas,
      state.baseGas,
      state.gasPrice,
      state.gasToken,
      state.refundReceiver,
      state.nonce
    )
    .then((res) => {
      State.update({ txHash: res });
      // sign contractTransactionHash using private key of Gnosis Safe owner (or deployer)
      const signature = signer
        .signMessage(ethers.utils.arrayify(res))
        .then((sig) => {
          State.update({ signature: sig });
          console.log(sig);
        });
    });
};

const postToSafeApi = () => {
  // craft transaction from state vars
  const transaction = {
    safe: state.safeAddress,
    to: state.recipient,
    value: state.value,
    data: "0x", //state.data,
    operation: state.operation,
    gasToken: state.gasToken,
    safeTxGas: state.safeTxGas,
    baseGas: state.baseGas,
    gasPrice: state.gasPrice,
    refundReceiver: state.refundReceiver,
    nonce: state.nonce, // Nonce of the Safe, transaction cannot be executed until Safe's nonce is accurate
    contractTransactionHash: state.txHash, // Contract transaction hash calculated from all the fields
    sender: state.sender, // must be checksummed Owner of the Safe
    signature: state.signature, // One or more ECDSA signatures of the `contractTransactionHash` as an hex string
    origin: state.origin,
  };

  const transactionsUrl = state.baseUrl + `multisig-transactions/`;
  const params = JSON.stringify(transaction);
  const proposalOptions = {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
    },
    mode: "no-cors",
    body: params,
  };

  // post to gnosis API backend
  const proposed = asyncFetch(transactionsUrl, proposalOptions).then((res) =>
    console.log(res)
  );
};

you need to adjust the signature type byte accordingly

2 Likes

Thanks so much! I read the smart contract and reached the same conclusion but I did not know why so I appreciate the resource