How can I start a new transaction on an existing Gnosis Safe from my react app?

796 Views Asked by At

Currently I have a React app which connects to a Metamask wallet. On clicking a 'Transfer' button, it transfers some ethers from the connected Metamask wallet to the one input by the user. All of this works fine and is pretty straightforward. It looks something like this (I am using ethers.js):

// fetch signer from connected metamask account
const signer = new ethers.providers.Web3Provider(ethereum).getSigner()
const contract = new ethers.Contract(contractAddress, ERC20ABI, signer)

// create a new transaction - that's it! so simple!
const txResponse = await contract.transfer(_recipientWalletAddress, utils.parseUnits(_userinput).toString()) 

Now, I want the same functionality but instead of transferring from the connected Metamask wallet, I want to connect to an existing Gnosis Safe and transfer funds from the safe to a recipient. I already have a test Gnosis Safe set up on gnosis-safe.io over Goerli Testnet with 3 owners. I have the Safe address. I have the addresses of the owners.

In my app, I have connected my app to the test Gnosis Safe using WalletConnect. Now, I want that I click a button, enter a recipient address and submit the form. This should create a new transaction on the test Gnosis Safe. And then on the Gnosis Safe, the owners can approve/reject the created transaction. I am struggling with this part.

This is what I've tried

import Safe from '@gnosis.pm/safe-core-sdk'
import { ethers, utils } from 'ethers'
import EthersAdapter from '@gnosis.pm/safe-ethers-lib'


// have all addresses here
const gnosisSafeAddress = '0xsafexaddress'
const gnosisSafeOwners = ['0xsafexownerx1', '0xsafexownerx2', '0xsafexownerx3']


// create a ethers provider over goerli network
const provider = new ethers.providers.JsonRpcProvider(`https://goerli.infura.io/v3/${infuraAPIKey}`);

// create signer using the provider and private key of the one of the gnosis safe owner
const signer = new ethers.Wallet(PRIVATE_KEY_OF_SAFE_OWNER_1, provider);

// create connection to safe using the gnosis safe address
const ethAdapter = new EthersAdapter({ ethers, signer });
const safeSdk = await Safe.create({ ethAdapter, safeAddress: gnosisSafeAddress });

// create a transaction
const transaction = {
   to: _recipientWalletAddress,
   value: utils.parseUnits(_userinput).toString(),
   data: '0x',
};

const safeTransaction = await safeSdk.createTransaction(transaction);

This does not work at all. I logged every step and every variable in between is empty objects. Kindly help me understand what I am doing wrong. I am extremely confused reading the Gnosis SDK repos.

I don't want to deploy a test Gnosis Safe programmatically. I don't want to create a Safe app. I just want to create a transaction, programmatically, on Gnosis Safe (a wallet) to transfer funds, like I do for metamask.

1

There are 1 best solutions below

0
On

There are not many good resources on how to interact with Safes from react. I suggest trying to write a script in node to grok the flow first. I created a script here that deploys and sends a transaction from the safe

to do the same thing in react (without using the transaction relayer service) you can do the following

import React, { useEffect, useState } from 'react'
import { Button } from "@nextui-org/react";
import { ethers } from "ethers";
import { useSigner } from 'wagmi'
import EthersAdapter from '@gnosis.pm/safe-ethers-lib'
import Safe, { SafeFactory } from '@gnosis.pm/safe-core-sdk'
import { SafeTransactionDataPartial, TransactionResult, SafeTransaction } from '@gnosis.pm/safe-core-sdk-types'

const threshold: number = 1;
const owners = ['0x0533F9d586ABd3334a0E90cA162602D6574F0493']

const safe = () => {
  const [gnosisLoaded, setGnosisLoaded] = useState<boolean>(false)
  const [safeDeployed, setSafeDeployed] = useState<boolean>(false)
  const [safeAddress, setSafeAddress] = useState<string>('')
  const { data: signer } = useSigner()
  const [safeSdk, setSafeSdk] = useState<Safe | null>(null)
  const [safeFactory, setSafeFactory] = useState<SafeFactory | null>(null)


  const deploySafe = async () => {
    const safeSdk = await safeFactory?.deploySafe({ safeAccountConfig: { owners, threshold } })
    if (!safeSdk) return
    setSafeSdk(safeSdk)
    setSafeAddress(safeSdk.getAddress())
    setSafeDeployed(true)
  }

  const sendSafeTransaction = async () => {
    const to = await signer?.getAddress()
    const transaction: SafeTransactionDataPartial = {
      to,
      value: "1",
      data: '0x',
    };

    const safeTransaction: SafeTransaction = await safeSdk?.createTransaction({ safeTransactionData: transaction });
    const executeTxResponse: TransactionResult = await safeSdk?.executeTransaction(safeTransaction)
    await executeTxResponse.transactionResponse?.wait()
  }

  useEffect(() => {
    if (!signer) return

    const setupGnosis = async () => {
      const ethAdapter = new EthersAdapter({ ethers, signer })
      const safeFactory = await SafeFactory.create({ ethAdapter })
      setSafeFactory(safeFactory)
      setGnosisLoaded(true)
    }

    setupGnosis()
  }, [signer])


  return (
    <div>
      {gnosisLoaded ? <div>gnosis loaded</div> : <div>gnosis loading...</div>}
      {safeDeployed ? <div>safe deployed: {safeAddress}</div> : <div>safe not deployed</div>}
      <Button disabled={!gnosisLoaded} onClick={() => deploySafe()}>
        Deploy Safe
      </Button>
      <Button disabled={!safeDeployed} onClick={() => sendSafeTransaction()}>Send Transaction</Button>
    </div>
  )
}
export default safe