2

SOLVED - SOLUTION TO BE POSTED BY ME TOMORROW!

Hello builders!

One Line summary: safeSdk.executeTransaction(safeTransaction) from @safe-global/protocol-kit does not work for signing transactions, I need help finding a way to submit the transactions.

The fun part (the problem in detail)

I have a button that I want to programmatically trigger createEnableGuardTx & createEnableModuleTx from the gnosis safe docs. https://docs.safe.global/reference/protocol-kit#createenablemoduletx

Here are the logs folloing button onSubmit:

Console.logs demonstrating where the failure occurs

Following my "onSubmit" we can see that everything is functioning up to the executeTransaction() functions . We can see that this is because Alchemy does not allow eth_sendTransaction. This is a security feature.

How can I submit both createEnableGuardTx & createEnableModuleTx if the SDK does not allow for this?

Overall I want to enable a Safe Guard and a Safe Module on a safe. AKA enable existingHSGAddress on SafeAddress.

Below you will find extensive logging followed by the thing's I have tried.

import { EthereumAddress } from './ReadForm';
import Web3 from 'web3';
import Safe, { Web3Adapter } from '@safe-global/protocol-kit';

async function connectSafeToHSG(
  existingHSGAddress: EthereumAddress,
  connectedAddress: EthereumAddress
): Promise<void> {
  const SafeAddress = `${process.env.NEXT_PUBLIC_VALID_SAFE_ADDRESS}`;

  console.log('inside connectSafeToHSG');
  console.log('SafeAddress: ', SafeAddress.substring(0, 8));
  console.log('existingHSGAddress: ', existingHSGAddress.substring(0, 8));
  console.log('signerAddress: ', connectedAddress.substring(0, 8));

  if (!connectedAddress) {
    throw new Error('No address connected');
  }

  // I need to use the EthersAdapter to connect to the @safe-global/protocol-kit SDK.

  // To use 'Ethers' for the EthersAdapter, we must use V5. Because this project uses Ethers V6, I opted for a Web3 implementation instead.
  const provider = new Web3.providers.HttpProvider(
    `https://eth-goerli.g.alchemy.com/v2/${process.env.NEXT_PUBLIC_ALCHEMY_API_KEY}`
  );
  const web3 = new Web3(provider);

  const ethAdapter = new Web3Adapter({
    web3,
    signerAddress: connectedAddress,
  });
  console.log('ethAdapter: ', ethAdapter);

  let safeSdk;
  try {
    // Connects the 'Connected user' to the safe in question so that the user can perform the required actions: 'createEnableModuleTx' & 'createEnableGuardTx'
    safeSdk = await Safe.create({
      ethAdapter,
      safeAddress: SafeAddress,
    });
    console.log('safeSdk: ', safeSdk);
  } catch (error) {
    console.error('Error creating Safe instance:', error);
    return;
  }

  try {
    const enableModuleTx = await safeSdk.createEnableModuleTx(
      existingHSGAddress
    );
    console.log('enableModuleTx', enableModuleTx);

    // Fail occurs on the next line. (1)
    const moduleTxResponse = await safeSdk.executeTransaction(enableModuleTx);
    console.log('moduleTxResponse', moduleTxResponse);

    await moduleTxResponse.transactionResponse?.wait();
    console.log(
      'Module transaction executed successfully. Response: ',
      moduleTxResponse
    );
  } catch (error) {
    console.error('Error in module transaction:', error);
  }

  try {
    const enableGuardTx = await safeSdk.createEnableGuardTx(existingHSGAddress);
    console.log('enableGuardTx', enableGuardTx);

    // Fail occurs on the next line. (2)
    const guardTxResponse = await safeSdk.executeTransaction(enableGuardTx);
    console.log('guardTxResponse', guardTxResponse);

    await guardTxResponse.transactionResponse?.wait();
    console.log(
      'Guard transaction executed successfully. Response: ',
      guardTxResponse
    );
  } catch (error) {
    console.error('Error in guard transaction:', error);
  }
}

export async function handleConnect(
  existingHSGAddress: EthereumAddress,
  connectedAddress: EthereumAddress
): Promise<void> {
  console.log('inside handleConnect');
  try {
    await connectSafeToHSG(existingHSGAddress, connectedAddress);
  } catch (error) {
    if (error instanceof Error) {
      console.error('Error connecting Safe to HSG:', error.message);
    } else {
      console.error('An unexpected error occurred:', error);
    }
  }
}

What have I tried? I have tried to sign this transaction with the sign functions within the SDK prior to submitting with executeTransaction, but this faces errors:

  1. signTransaction - uses eth_sign (Invalid for Alchemy and other providers)
  2. signTypedData - uses EIP-712 (Invalid for Alchemy and other providers)

That leaves one more, this is my next and final approach:

  1. signTransactionHash

Please help give some possible directions, I appreciate you may not have time to solve this! Your assistance in pointing me in the right direction will be fantastic!

Muchas gracias amigos :D

0

1 Answer 1

0
import { EthereumAddress } from './ReadForm';
import Web3 from 'web3';
import Safe, { Web3Adapter } from '@safe-global/protocol-kit';
import { MetaTransactionData } from '@safe-global/safe-core-sdk-types';

// Add error rendering

async function connectSafeToHSG(
  existingHSGAddress: EthereumAddress,
  connectedAddress: EthereumAddress,
  safeAddress: EthereumAddress,
  setTransactionHash: (transactionHash?: string) => void,
  setIsSigningExecuting: (isSigningExecuting: boolean) => void
): Promise<boolean> {
  if (typeof window.ethereum === 'undefined') {
    throw new Error('MetaMask is not installed!');
  }

  const web3 = new Web3(window.ethereum); // Use MetaMask's provider

  // Request user connection to MetaMask
  const accounts = await window.ethereum.request({
    method: 'eth_requestAccounts',
  });
  if (!accounts || accounts.length === 0) {
    throw new Error('Failed to connect to MetaMask');
  }

  const ethAdapter = new Web3Adapter({
    web3,
    signerAddress: accounts[0], // Use the first account from MetaMask
  });

  if (!connectedAddress) {
    throw new Error('No address connected');
  }

  // I need to use the EthersAdapter to connect to the @safe-global/protocol-kit SDK.
  // To use 'Ethers' for the EthersAdapter, we must use V5. Because this project uses Ethers V6, I opted for a Web3 implementation instead.
  let safeSdk;
  try {
    // Connects the 'Connected user' to the safe in question so that the user can perform the required actions: 'createEnableModuleTx' & 'createEnableGuardTx'
    safeSdk = await Safe.create({
      ethAdapter,
      safeAddress: safeAddress,
    });
    console.log('safeSdk: ', safeSdk);
  } catch (error) {
    console.error('Error creating Safe instance:', error);
    return false;
  }

  try {
    // 1. Prepare the transactions
    const enableModuleTx = await safeSdk.createEnableModuleTx(
      existingHSGAddress
    );
    console.log('enableModuleTx: ', enableModuleTx);

    const enableGuardTx = await safeSdk.createEnableGuardTx(existingHSGAddress);
    console.log('enableGuardTx: ', enableGuardTx);

    const safeTransactionData: MetaTransactionData[] = [
      {
        to: enableModuleTx.data.to,
        value: enableModuleTx.data.value.toString(),
        data: enableModuleTx.data.data,
        // you can add operation if needed, but it's optional
      },
      {
        to: enableGuardTx.data.to,
        value: enableGuardTx.data.value.toString(),
        data: enableGuardTx.data.data,
        // you can add operation if needed, but it's optional
      },
    ];
    console.log('safeTransactionData: ', safeTransactionData);

    // 2. Use the `createTransaction` function for MultiSend transactions
    const batchedTransaction = await safeSdk.createTransaction({
      safeTransactionData,
    });
    console.log('batchedTransaction: ', batchedTransaction);

    // 3. Sign the batched transaction
    const signedBatchedTx = await safeSdk.signTransaction(
      batchedTransaction,
      'eth_signTypedData_v4'
    );
    console.log('signedBatchedTx: ', signedBatchedTx);

    // 4. Execute the batched transaction
    const txResponse = await safeSdk.executeTransaction(signedBatchedTx);
    console.log('txResponse1', txResponse);
    await txResponse.transactionResponse?.wait();
    console.log(
      'Batched transaction executed successfully. Response:',
      txResponse
    );

    // 5. Pass new data back to parents
    if (txResponse.hash) setTransactionHash(txResponse.hash);

    return true;
  } catch (error) {
    // Enables the user to click the submit button again.
    setIsSigningExecuting(false);

    console.error('Error in batched transaction:', error);
    return false;
  }
}

export async function handleConnect(
  existingHSGAddress: EthereumAddress,
  connectedAddress: EthereumAddress,
  safeAddress: EthereumAddress,
  setTransactionHash: (transactionHash?: string) => void,
  setIsSigningExecuting: (isSigningExecuting: boolean) => void
): Promise<void> {
  console.log('inside handleConnect');

  try {
    await connectSafeToHSG(
      existingHSGAddress,
      connectedAddress,
      safeAddress,
      setTransactionHash,
      setIsSigningExecuting
    ); // <-- Get the return value (True/False)

  } catch (error) {
    // Enables the user to click the submit button again.
    setIsSigningExecuting(false);

    if (error instanceof Error) {
      console.error('Error connecting Safe to HSG:', error.message);
    } else {
      console.error('An unexpected error occurred:', error);
    }
  }
}

// const signedGuardTx = await signSafeTransaction(safeSdk, enableGuardTx);
// console.log('signedGuardTx', signedGuardTx);

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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