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)
);
};