Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Safe creation #48

Merged
merged 16 commits into from
Jul 19, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 35 additions & 23 deletions packages/safe-core-sdk/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,29 +27,17 @@ npm build

## Getting Started

A Safe account with three owners and threshold equal three will be used as the starting point for this example but any Safe configuration is valid. Let's start defining the three owners and the Safe address.
### 1. Set up the SDK using `Ethers` or `Web3`

If the app integrating the SDK is using `Ethers` `v5`, create an instance of the `EthersAdapter`. `owner1` is the Ethereum account we are connecting and the one who will sign the transactions.

```js
import { ethers } from 'ethers'
import { EthersAdapter } from '@gnosis.pm/safe-core-sdk'

const web3Provider = // ...
const provider = new ethers.providers.Web3Provider(web3Provider)
const owner1 = provider.getSigner(0)
const owner2 = provider.getSigner(1)
const owner3 = provider.getSigner(2)

// Existing Safe address (e.g. Safe created via https://app.gnosis-safe.io)
// Where owner1, owner2 and owner3 are the Safe owners
const safeAddress = '0x<safe_address>'
```

### 1. Set up the SDK using `Ethers` or `Web3`

If the app integrating the SDK is using `Ethers` `v5`, create an instance of the `EthersAdapter`.

```js
import { ethers } from 'ethers'
import { EthersAdapter } from '@gnosis.pm/safe-core-sdk'

const ethAdapterOwner1 = new EthersAdapter({
ethers,
Expand All @@ -69,15 +57,39 @@ const ethAdapterOwner1 = new Web3Adapter({
})
```

Create an instance of the Safe Core SDK calling the method `create` from the `Safe` class and passing a `safeAddress` and an instance of the `EthAdapter` class (`EthersAdapter` or `Web3Adapter` depending on the library used by the app). The signer connected to the Safe will be the one selected when the `ethAdapter` was instantiated, in this case, `owner1`.
### 2. Deploy a new Safe

To deploy a new Safe account instantiate the `SafeFactory` class and call the method `deploySafe` with the right params to configure the new Safe. This includes defining the list of owners and the threshold of the Safe. A Safe account with three owners and threshold equal three will be used as the starting point for this example but any Safe configuration is valid.

```js
import { Safe, SafeFactory, SafeAccountConfig } from '@gnosis.pm/safe-code-sdk'

const safeFactory = await SafeFactory.create({ ethAdapter })

const owners = ['0x<address>', '0x<address>', '0x<address>']
const threshold = 3
const safeAccountConfig: SafeAccountConfig = { owners, threshold }

const safeSdk: Safe = await safeFactory.deploySafe(safeAccountConfig)
```

The method `deploySafe` executes a transaction from `owner1` account, deploys a new Safe and returns an instance of the Safe Core SDK connected to the new Safe.

Call the method `getAddress`, for example, to check the address of the newly deployed Safe.

```js
const newSafeAddress = safeSdk.getAddress()
```

To instantiate the Safe Core SDK from an existing Safe just pass to it an instance of the `EthAdapter` class and the Safe address.

```js
import Safe from '@gnosis.pm/safe-core-sdk'

const safeSdk = await Safe.create({ ethAdapter: ethAdapterOwner1, safeAddress })
const safeSdk: Safe = await Safe.create({ ethAdapter: ethAdapterOwner1, safeAddress })
```

### 2. Create a Safe transaction
### 3. Create a Safe transaction

```js
import { SafeTransactionDataPartial } from '@gnosis.pm/safe-core-sdk'
Expand All @@ -92,7 +104,7 @@ const safeTransaction = await safeSdk.createTransaction(...transactions)

Before executing this transaction, it must be signed by the owners and this can be done off-chain or on-chain. In this example `owner1` will sign it off-chain, `owner2` will sign it on-chain and `owner3` will execute it (the executor also signs the transaction transparently).

### 2.a. Off-chain signatures
### 3.a. Off-chain signatures

The `owner1` account signs the transaction off-chain.

Expand All @@ -102,7 +114,7 @@ const owner1Signature = await safeSdk.signTransaction(safeTransaction)

Because the signature is off-chain, there is no interaction with the contract and the signature becomes available at `safeTransaction.signatures`.

### 2.b. On-chain signatures
### 3.b. On-chain signatures

To connect `owner2` to the Safe we need to create a new instance of the class `EthAdapter` passing to its constructor the owner we would like to connect. After `owner2` account is connected to the SDK as a signer the transaction hash will be approved on-chain.

Expand All @@ -114,7 +126,7 @@ const approveTxResponse = await safeSdk2.approveTransactionHash(txHash)
await approveTxResponse.wait()
```

### 3. Transaction execution
### 4. Transaction execution

Lastly, `owner3` account is connected to the SDK as a signer and executor of the Safe transaction to execute it.

Expand Down Expand Up @@ -440,7 +452,7 @@ await txResponse.wait()

Optionally, `gasLimit` and `gasPrice` values can be passed as execution options, avoiding the gas estimation.

```
```js
const options: TransactionOptions = {
gasLimit: 123456,
gasPrice: 123 // Optional parameter.
Expand Down
34 changes: 26 additions & 8 deletions packages/safe-core-sdk/src/configuration/contracts.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
export interface ContractNetworkConfig {
/** multiSendAddress - Address of the MultiSend contract deployed in a specific network id */
/** multiSendAddress - Address of the MultiSend contract deployed on a specific network */
multiSendAddress: string
/** safeMasterCopyAddress - Address of the Gnosis Safe Master Copy contract deployed on a specific network */
safeMasterCopyAddress: string
/** safeProxyFactoryAddress - Address of the Gnosis Safe Proxy Factory contract deployed on a specific network */
safeProxyFactoryAddress: string
}

export interface ContractNetworksConfig {
Expand All @@ -11,30 +15,44 @@ export interface ContractNetworksConfig {
export const defaultContractNetworks: ContractNetworksConfig = {
// mainnet
1: {
multiSendAddress: '0x8D29bE29923b68abfDD21e541b9374737B49cdAD'
multiSendAddress: '0x8D29bE29923b68abfDD21e541b9374737B49cdAD',
safeMasterCopyAddress: '0x6851D6fDFAfD08c0295C392436245E5bc78B0185',
safeProxyFactoryAddress: '0x76E2cFc1F5Fa8F6a5b3fC4c8F4788F0116861F9B'
},
// rinkeby
4: {
multiSendAddress: '0x8D29bE29923b68abfDD21e541b9374737B49cdAD'
multiSendAddress: '0x8D29bE29923b68abfDD21e541b9374737B49cdAD',
germartinez marked this conversation as resolved.
Show resolved Hide resolved
safeMasterCopyAddress: '0x6851D6fDFAfD08c0295C392436245E5bc78B0185',
safeProxyFactoryAddress: '0x76E2cFc1F5Fa8F6a5b3fC4c8F4788F0116861F9B'
},
// goerli
5: {
multiSendAddress: '0x8D29bE29923b68abfDD21e541b9374737B49cdAD'
multiSendAddress: '0x8D29bE29923b68abfDD21e541b9374737B49cdAD',
safeMasterCopyAddress: '0x6851D6fDFAfD08c0295C392436245E5bc78B0185',
safeProxyFactoryAddress: '0x76E2cFc1F5Fa8F6a5b3fC4c8F4788F0116861F9B'
},
// kovan
42: {
multiSendAddress: '0x8D29bE29923b68abfDD21e541b9374737B49cdAD'
multiSendAddress: '0x8D29bE29923b68abfDD21e541b9374737B49cdAD',
safeMasterCopyAddress: '0x6851D6fDFAfD08c0295C392436245E5bc78B0185',
safeProxyFactoryAddress: '0x76E2cFc1F5Fa8F6a5b3fC4c8F4788F0116861F9B'
},
// xdai
100: {
multiSendAddress: '0x8D29bE29923b68abfDD21e541b9374737B49cdAD'
multiSendAddress: '0x8D29bE29923b68abfDD21e541b9374737B49cdAD',
safeMasterCopyAddress: '0x6851D6fDFAfD08c0295C392436245E5bc78B0185',
safeProxyFactoryAddress: '0x76E2cFc1F5Fa8F6a5b3fC4c8F4788F0116861F9B'
},
// energy web chain
246: {
multiSendAddress: '0x8D29bE29923b68abfDD21e541b9374737B49cdAD'
multiSendAddress: '0x8D29bE29923b68abfDD21e541b9374737B49cdAD',
safeMasterCopyAddress: '0x6851D6fDFAfD08c0295C392436245E5bc78B0185',
safeProxyFactoryAddress: '0x76E2cFc1F5Fa8F6a5b3fC4c8F4788F0116861F9B'
},
// energy web volta
73799: {
multiSendAddress: '0x8D29bE29923b68abfDD21e541b9374737B49cdAD'
multiSendAddress: '0x8D29bE29923b68abfDD21e541b9374737B49cdAD',
safeMasterCopyAddress: '0x6851D6fDFAfD08c0295C392436245E5bc78B0185',
safeProxyFactoryAddress: '0x76E2cFc1F5Fa8F6a5b3fC4c8F4788F0116861F9B'
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
[
{
"constant": false,
"inputs": [
{
"internalType": "address",
"name": "masterCopy",
"type": "address"
},
{
"internalType": "bytes",
"name": "data",
"type": "bytes"
}
],
"name": "createProxy",
"outputs": [
{
"internalType": "contract GnosisSafeProxy",
"name": "proxy",
"type": "address"
}
],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "proxyRuntimeCode",
"outputs": [
{
"internalType": "bytes",
"name": "",
"type": "bytes"
}
],
"payable": false,
"stateMutability": "pure",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "proxyCreationCode",
"outputs": [
{
"internalType": "bytes",
"name": "",
"type": "bytes"
}
],
"payable": false,
"stateMutability": "pure",
"type": "function"
},
{
"constant": false,
"inputs": [
{
"internalType": "address",
"name": "_mastercopy",
"type": "address"
},
{
"internalType": "bytes",
"name": "initializer",
"type": "bytes"
},
{
"internalType": "uint256",
"name": "saltNonce",
"type": "uint256"
}
],
"name": "createProxyWithNonce",
"outputs": [
{
"internalType": "contract GnosisSafeProxy",
"name": "proxy",
"type": "address"
}
],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": false,
"inputs": [
{
"internalType": "address",
"name": "_mastercopy",
"type": "address"
},
{
"internalType": "bytes",
"name": "initializer",
"type": "bytes"
},
{
"internalType": "uint256",
"name": "saltNonce",
"type": "uint256"
},
{
"internalType": "contract IProxyCreationCallback",
"name": "callback",
"type": "address"
}
],
"name": "createProxyWithCallback",
"outputs": [
{
"internalType": "contract GnosisSafeProxy",
"name": "proxy",
"type": "address"
}
],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": false,
"inputs": [
{
"internalType": "address",
"name": "_mastercopy",
"type": "address"
},
{
"internalType": "bytes",
"name": "initializer",
"type": "bytes"
},
{
"internalType": "uint256",
"name": "saltNonce",
"type": "uint256"
}
],
"name": "calculateCreateProxyWithNonceAddress",
"outputs": [
{
"internalType": "contract GnosisSafeProxy",
"name": "proxy",
"type": "address"
}
],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
}
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { TransactionOptions } from '../../utils/transactions/types'

export interface CreateProxyProps {
safeMasterCopyAddress: string
initializer: string
saltNonce?: number
options?: TransactionOptions
}

interface GnosisSafeProxyFactory {
getAddress(): string
createProxy(options: CreateProxyProps): Promise<string>
encode(methodName: string, params: any[]): string
}

export default GnosisSafeProxyFactory
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { ContractTransaction, Event } from 'ethers'
import { GnosisSafeProxyFactory } from '../../types/typechain/ethers-v5/GnosisSafeProxyFactory'
import GnosisSafeProxyFactoryContract, { CreateProxyProps } from './GnosisSafeProxyFactoryContract'

class GnosisSafeProxyFactoryEthersV5Contract implements GnosisSafeProxyFactoryContract {
constructor(public contract: GnosisSafeProxyFactory) {}

getAddress(): string {
return this.contract.address
}

async createProxy({
safeMasterCopyAddress,
initializer,
saltNonce,
options
}: CreateProxyProps): Promise<string> {
let txResponse: ContractTransaction
if (saltNonce) {
txResponse = await this.contract.createProxyWithNonce(
safeMasterCopyAddress,
initializer,
saltNonce,
options
)
} else {
txResponse = await this.contract.createProxy(safeMasterCopyAddress, initializer, options)
}
const txReceipt = await txResponse.wait()
const proxyCreationEvent = txReceipt.events?.find(({ event }: Event) => event === 'ProxyCreation')
if (!proxyCreationEvent || !proxyCreationEvent.args) {
throw new Error('Safe Proxy was not deployed correctly')
}
const proxyAddress: string = proxyCreationEvent.args[0]
return proxyAddress
}

encode(methodName: string, params: any[]): string {
return (this.contract as any).interface.encodeFunctionData(methodName, params)
}
}

export default GnosisSafeProxyFactoryEthersV5Contract
Loading