Skip to content

Commit

Permalink
Merge pull request safe-global#16 from gnosis/on-chain-signatures
Browse files Browse the repository at this point in the history
On-chain signatures
  • Loading branch information
germartinez authored Apr 6, 2021
2 parents fc490b1 + 968ece5 commit 6dd3cb9
Show file tree
Hide file tree
Showing 17 changed files with 1,222 additions and 244 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [12.x]
node-version: [14.x]
steps:
- uses: actions/checkout@v2
- name: Use Node.js ${{ matrix.node-version }}
Expand Down
5 changes: 5 additions & 0 deletions .nycrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"include": [
"src/**/*.ts"
]
}
237 changes: 221 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
# Safe Core SDK

[![Coverage Status](https://coveralls.io/repos/github/gnosis/safe-core-sdk/badge.svg?branch=main)](https://coveralls.io/github/gnosis/safe-core-sdk?branch=main)

Software development kit that facilitates the interaction with the [Gnosis Safe contracts](https://github.com/gnosis/safe-contracts).

## Install
## Installation

Install the package with yarn or npm:

Expand All @@ -20,27 +22,33 @@ yarn build
npm build
```

## Documentation

### Getting Started
## Getting Started

This is how executing a Safe transaction with off-chain signatures looks like.
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.

1. Create a Safe account with two owners and threshold equal two (this configuration is just an example):
```js
import { ethers } from 'ethers')
import { ethers } from 'ethers'
import EthersSafe, { SafeTransaction } from 'safe-core-sdk'

const provider = ethers.getDefaultProvider('homestead')
const wallet1 = ethers.Wallet.createRandom().connect(provider)
const wallet2 = ethers.Wallet.createRandom().connect(provider)
const wallet3 = ethers.Wallet.createRandom().connect(provider)

// Existing Safe address (e.g. Safe created via https://app.gnosis-safe.io
// Existing Safe address (e.g. Safe created via https://app.gnosis-safe.io)
// Where wallet1.address, wallet2.address and wallet3.address are the Safe owners
const safeAddress = "0x<safe_address>"
const safeNonce = <safe_nonce>
```

2. Create a Safe transaction:
Create an instance of the Safe Core SDK with wallet1 connected as the signer.

```js
const safeSdk1 = new EthersSafe(ethers, safeAddress, wallet1)
```

### 1. Create a Safe transaction

```js
const tx = new SafeTransaction({
to: safeAddress,
Expand All @@ -50,19 +58,216 @@ const tx = new SafeTransaction({
})
```

3. Generate the signatures with both owners:
Before executing this transaction, it must be signed by the owners and this can be done off-chain or on-chain. In this example the owner `wallet1` will sign it off-chain and the owner `wallet2` will sign it on-chain. It is not needed that `wallet3` signs the transaction explicitly because it will be the one executing the transaction. If an account that is not an owner executes the transaction, `wallet3` would have to explicitly sign it too.

### 2.a. Off-chain signatures

The owner `wallet1` signs the transaction off-chain.

```js
let safeSdk = new EthersSafe(ethers, wallet1, safeAddress)
await safeSdk.confirmTransaction(tx)
const wallet1Signature = await safeSdk1.signTransaction(tx)
```

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

### 2.b. On-chain signatures

safeSdk = new EthersSafe(ethers, wallet2, safeAddress)
await safeSdk.confirmTransaction(tx)
After `wallet2` account is connected to the SDK as the signer the transaction hash is approved on-chain.

```js
const safeSdk2 = safeSdk1.connect(wallet2)
const txHash = await safeSdk2.getTransactionHash(tx)
const wallet2Signature = await safeSdk2.approveTransactionHash(txHash)
```

4. Execute the transaction with the two signatures added:
### 3. Transaction execution

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

```js
const txResponse = await safeSdk.executeTransaction(tx)
const safeSdk3 = safeSdk2.connect(wallet3)
const txResponse = await safeSdk3.executeTransaction(tx)
```

All the signatures used to execute the transaction are available at `tx.signatures`.

## API Reference

### constructor

Returns an instance of the Safe Core SDK with the `providerOrSigner` connected to the `safeAddress`.

```js
const safeSdk = new EthersSafe(ethers, safeAddress, providerOrSigner)
```

If `providerOrSigner` is not provided, `ethers` default provider will be used.

```js
const safeSdk = new EthersSafe(ethers, safeAddress)
```

### connect

Returns a new instance of the Safe Core SDK with the `providerOrSigner` connected to the `safeAddress`.

```js
const safeSdk2 = safeSdk.connect(providerOrSigner, safeAddress)
```

If `safeAddress` is not provided, the `providerOrSigner` will be connected to the previous Safe.

```js
const safeSdk2 = safeSdk.connect(providerOrSigner)
```

### getProvider

Returns the connected provider.

```js
const provider = safeSdk.getProvider()
```

### getSigner

Returns the connected signer.

```js
const signer = safeSdk.getSigner()
```

### getAddress

Returns the address of the current Safe Proxy contract.

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

### getContractVersion

Returns the Safe Master Copy contract version.

```js
const contractVersion = await safeSdk.getContractVersion()
```

### getOwners

Returns the list of Safe owner accounts.

```js
const owners = await safeSdk.getOwners()
```

### getThreshold

Returns the Safe threshold.

```js
const threshold = await safeSdk.getThreshold()
```

### getChainId

Returns the chainId of the connected network.

```js
const chainId = await safeSdk.getChainId()
```

### getBalance

Returns the ETH balance of the Safe.

```js
const balance = await safeSdk.getBalance()
```

### getModules

Returns the list of addresses of all the enabled Safe modules.

```js
const modules = await safeSdk.getModules()
```

### isModuleEnabled

Checks if a specific Safe module is enabled for the current Safe.

```js
const isEnabled = await safeSdk.isModuleEnabled(moduleAddress)
```

### getTransactionHash

Returns the transaction hash of a Safe transaction.

```js
const tx = new SafeTransaction({
// ...
})
const txHash = await safeSdk.getTransactionHash(tx)
```

### signTransactionHash

Signs a hash using the current signer account.

```js
const tx = new SafeTransaction({
// ...
})
const txHash = await safeSdk.getTransactionHash(tx)
const signature = await safeSdk.signTransactionHash(txHash)
```

### signTransaction

Adds the signature of the current signer to the Safe transaction object.

```js
const tx = new SafeTransaction({
// ...
})
await safeSdk.signTransaction(tx)
```

### approveTransactionHash

Approves on-chain a hash using the current signer account.

```js
const tx = new SafeTransaction({
// ...
})
const txHash = await safeSdk.getTransactionHash(tx)
const signature = await safeSdk.approveTransactionHash(txHash)
```

### getOwnersWhoApprovedTx

Returns a list of owners who have approved a specific Safe transaction.

```js
const tx = new SafeTransaction({
// ...
})
const txHash = await safeSdk.getTransactionHash(tx)
const owners = await safeSdk.getOwnersWhoApprovedTx(txHash)
```

### executeTransaction

Executes a Safe transaction.

```js
const tx = new SafeTransaction({
// ...
})
const txResponse = await safeSdk.executeTransaction(tx)
```

## License
Expand Down
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
],
"scripts": {
"build": "yarn rimraf dist && tsc && hardhat compile",
"test": "hardhat deploy && hardhat test",
"test": "hardhat deploy && nyc hardhat test",
"coverage": "nyc report --reporter=text-lcov | coveralls",
"format": "prettier --write \"{src,tests,hardhat}/**/*.ts\"",
"lint": "tslint -p tsconfig.json"
},
Expand Down Expand Up @@ -39,6 +40,7 @@
"@typescript-eslint/parser": "^4.15.0",
"chai": "^4.3.1",
"chai-as-promised": "^7.1.1",
"coveralls": "^3.1.0",
"dotenv": "^8.2.0",
"eslint": "^7.20.0",
"eslint-config-prettier": "^7.2.0",
Expand All @@ -51,6 +53,7 @@
"husky": "^5.0.9",
"lint-staged": "^10.5.4",
"mocha": "^8.3.0",
"nyc": "^15.1.0",
"prettier": "^2.2.1",
"rimraf": "^3.0.2",
"ts-generator": "^0.1.1",
Expand Down
Loading

0 comments on commit 6dd3cb9

Please sign in to comment.