Skip to content

Commit

Permalink
Merge pull request #41 from gnosis/development
Browse files Browse the repository at this point in the history
v0.4.0
  • Loading branch information
mmv08 authored Sep 15, 2020
2 parents 03e36ae + 7bf4a48 commit dcd2cf6
Show file tree
Hide file tree
Showing 11 changed files with 918 additions and 418 deletions.
143 changes: 121 additions & 22 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,49 +36,56 @@ This library exposes a single method called `initSdk` that receives a single opt
- rinkeby: https://rinkeby.gnosis-safe.io,
- rinkeby-staging: https://safe-team-rinkeby.staging.gnosisdev.com,
- rinkeby-dev: https://safe-team.dev.gnosisdev.com
- localhost (for the desktop app)

By passing the argument to `initSdk` you can add more URLs to the list. It's useful when you are running a local instance of Safe Multisig.
By passing the argument to `initSdk` you can add more URLs to the list. It's useful when you are running your own instance of Safe Multisig.

```js
import React, { useState, useEffect } from 'react';
import initSdk from '@gnosis.pm/safe-apps-sdk';

const [appsSdk] = useState(initSdk());
const appsSdk = initSdk();
```

It returns a SDK instance that allows you to interact with the Safe Multisig application.

### Register events
### Subscribing to events

Once you get the SDK instance, you will be able to subscribe to events from the Safe Multisig.

The SDK instance exposes a method called `addListener` that receives an object with known keys, over these keys you will be able to subscribe to different events.
The SDK instance exposes a method called `addListeners` that receives an object with known keys, over these keys you will be able to subscribe to different events.

The first event that you should subscribe to is `onSafeInfo`; It will provide you first level information like the safeAddress, network, etc.
- `onSafeInfo`: It will provide you first level information like the safeAddress, network, etc.
- `onTransactionConfirmation`: Fired when the user confirms the transaction inside his wallet. The response will include `requestId` and `safeTxHash` of the transaction.

```js
const [safeInfo, setSafeInfo] = useState(); // Hook for SafeInfo to be stored
import { SafeInfo } from '@gnosis.pm/safe-apps-sdk';

useEffect(() => {
appsSdk.addListeners({
onSafeInfo: setSafeInfo,
});
const onSafeInfo = (safeInfo: SafeInfo): void => {
console.log(safeInfo);
};

return () => appsSdk.removeListeners();
}, [appsSdk]);
const onTransactionConfirmation = ({ requestId, safeTxHash }) => {
console.log(requestId, safeTxHash);
};

appsSdk.addListeners({
onSafeInfo,
onTransactionConfirmation,
});
```

You can remove listeners by calling `appsSdk.removeListeners()`.

### Sending TXs

Sending a TX through the Safe Multisig is as simple as invoking `sendTransaction` method with an array of TXs.

```js
// Create a web3 instance
const web3: any = new Web3('https://rinkeby.infura.io/v3/token');
const web3 = new Web3('https://rinkeby.infura.io/v3/token');
const contract = new web3.eth.Contract(abi, contractAddress);

// Set Txs array
txs = [
const txs = [
{
to: someAddress,
value: 0,
Expand All @@ -92,14 +99,29 @@ txs = [
];

// Send to Safe-multisig
appsSdk.sendTransactions(txs);
const message = appsSdk.sendTransactions(txs);
console.log(message.requestId);
```

`sendTransactions` returns a message containing the requestId. You can use it to map transaction calls with `onTransactionConfirmation` events.

> Note: `value` accepts a number or a string as a decimal or hex number.
### Retrieving transaction's status

Once you received safe transaction hash from `onTransactionConfirmation` event listener, you might want to get the status of the transaction (was it executed? how many confirmations does it have?):

```js
const tx = sdk.txs.getBySafeTxHash(safeTxHash);
```

> Note: `value` accepts a number or a string as a decimal number (hexadecimal is not supported).
It will return the following structure https://github.com/gnosis/safe-apps-sdk/blob/development/src/types.ts#L157 or throw an error if the backend hasn't synced the transaction yet

## Testing in the Safe Multisig application

Once your app is ready you need to deploy it on the internet. It is mandatory that your app exposes a `manifest.json` file in the root dir with this structure:
### Manifest

It is mandatory that your app exposes a `manifest.json` file in the root dir with this structure:

```json
{
Expand All @@ -111,13 +133,80 @@ Once your app is ready you need to deploy it on the internet. It is mandatory th

> Note: iconPath it's the public relative path where the Safe Multisig will try to load your app icon. For this example, it should be https://yourAppUrl/myAppIcon.svg.
Remember to also enable **Cross Site Requests** for the site. For example if using Netlify add a file `_headers` with the following content:
### CORS

As the Safe app is included into the Safe Multisig application via an iframe it is required to enable **Cross Site Requests** by setting the **CORS** headers when serving the Safe app.

The required headers are:

```
"Access-Control-Allow-Origin": "\*",
"Access-Control-Allow-Methods": "GET",
"Access-Control-Allow-Headers": "X-Requested-With, content-type, Authorization"
```

### React development

It is possible to use the local React development server. For this you need to set the **CORS** headers and make sure to use the same protocol (http or https) as the Safe Multisig interface.

#### CORS

For this we recommend to use [react-app-rewired](https://www.npmjs.com/package/react-app-rewired). To enable the library update the `scripts` section in the `package.json`:

```json
"scripts": {
"start": "react-app-rewired start",
"build": "react-app-rewired build",
"test": "react-app-rewired test"
},
```
/*
Access-Control-Allow-Origin: *

Additionally you need to create the `config-overrides.js` file in the root of the project to confirgure the **CORS** headers. The content of the file should be:

```js
/* config-overrides.js */

module.exports = {
// The function to use to create a webpack dev server configuration when running the development
// server with 'npm run start' or 'yarn start'.
// Example: set the dev server to use a specific certificate in https.
devServer: function (configFunction) {
// Return the replacement function for create-react-app to use to generate the Webpack
// Development Server config. "configFunction" is the function that would normally have
// been used to generate the Webpack Development server config - you can use it to create
// a starting configuration to then modify instead of having to create a config from scratch.
return function (proxy, allowedHost) {
// Create the default config by calling configFunction with the proxy/allowedHost parameters
const config = configFunction(proxy, allowedHost);

config.headers = {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET',
'Access-Control-Allow-Headers': 'X-Requested-With, content-type, Authorization',
};

// Return your customised Webpack Development Server config.
return config;
};
},
};
```

#### SSL

To enable SSL with `react-scripts` it is necessary to set the `HTTPS` environment variable to `true`. This can be done in the `package.json` file by adjusting the `scripts` section to:

```json

"scripts": {
"start": "HTTPS=true react-app-rewired start",
},
```

As in most cases the SSL certificate provided by `react-scripts` is not valid it is required to mark it as trusted in your browser. For this open the Safe App in a separate tab (not in the Safe Multisig interface) and accept the certificate/ ignore the warning.

### Loading the Safe App

When your app is live, you can import it to the Safe Multisig application. To do so, you should select the "Apps" tab:

![alt text][safeappstab]
Expand All @@ -130,10 +219,20 @@ Use the `Manage Apps` button and add your app using a link:

[safeaddapp]: https://raw.githubusercontent.com/gnosis/safe-apps-sdk/master/assets/third-pary-app-modal.png 'Safe Multisig: Add Safe App'

## Deploy to IPFS

This requires that you have `ipfs` installed ([Instructions](https://gist.github.com/MiguelBel/b3b5f711aa8d9362afa5f16e4e972461))

```bash
yarn build
ipfs add -r build
```

## Examples of applications built with this SDK

- https://github.com/gnosis/safe-react-apps
- https://github.com/Uxio0/safe-react-collectibles
- https://docs.gnosis.io/safe/docs/sdks_safe_apps/#existing-safe-apps

## License

Expand Down
Binary file modified assets/safe-tab-apps.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
26 changes: 13 additions & 13 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@gnosis.pm/safe-apps-sdk",
"version": "0.3.1",
"version": "0.4.0",
"description": "SDK developed to integrate third-party apps with Safe-Multisig app.",
"main": "dist/index.js",
"typings": "dist/index.d.ts",
Expand All @@ -23,22 +23,22 @@
"author": "Gnosis (https://gnosis.pm)",
"license": "MIT",
"devDependencies": {
"@types/jest": "^26.0.9",
"@types/node": "^14.0.27",
"@typescript-eslint/eslint-plugin": "^3.8.0",
"@typescript-eslint/parser": "^3.8.0",
"eslint": "^7.6.0",
"@types/jest": "^26.0.13",
"@types/node": "^14.6.4",
"@typescript-eslint/eslint-plugin": "^4.1.0",
"@typescript-eslint/parser": "^4.1.0",
"eslint": "^7.8.1",
"eslint-config-prettier": "^6.11.0",
"eslint-plugin-prettier": "^3.1.4",
"husky": "^4.2.5",
"jest": "^26.3.0",
"lint-staged": "^10.2.11",
"prettier": "^2.0.5",
"husky": "^4.3.0",
"jest": "^26.4.2",
"lint-staged": "^10.3.0",
"prettier": "^2.1.1",
"rimraf": "^3.0.2",
"ts-jest": "^26.1.4",
"tslint": "^6.1.2",
"ts-jest": "^26.3.0",
"tslint": "^6.1.3",
"tslint-config-prettier": "^1.18.0",
"typescript": "^3.9.7"
"typescript": "^4.0.2"
},
"husky": {
"hooks": {
Expand Down
26 changes: 18 additions & 8 deletions src/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import initSdk, { SdkInstance, SDK_MESSAGES } from './index';
import initSdk, { SdkInstance } from './index';
import { SDK_MESSAGES } from './messageIds';

describe('safe app sdk', () => {
let sdkInstance: SdkInstance;
Expand All @@ -16,17 +17,26 @@ describe('safe app sdk', () => {
});

describe('sendTransaction', () => {
test('Should do nothing when passing an empty array', () => {
const spy = jest.spyOn(window.parent, 'postMessage');
sdkInstance.sendTransactions([]);
expect(spy).not.toHaveBeenCalled();
test('Should throw an error when passing an empty array', () => {
expect(() => {
sdkInstance.sendTransactions([]);
}).toThrow();
});

test('Should call window.parent.postMessage when passing array of TXs', () => {
test('Should call window.parent.postMessage with a requestId when passing array of TXs', () => {
const requestId = '1000';
const spy = jest.spyOn(window.parent, 'postMessage');
const txs = [{ to: 'address', value: '0', data: '0x' }];
sdkInstance.sendTransactions(txs);
expect(spy).toHaveBeenCalledWith({ messageId: SDK_MESSAGES.SEND_TRANSACTIONS, data: txs }, '*');
sdkInstance.sendTransactions(txs, requestId);
expect(spy).toHaveBeenCalledWith({ messageId: SDK_MESSAGES.SEND_TRANSACTIONS, data: txs, requestId }, '*');
});

test('Should return a message containing requestId', () => {
const txs = [{ to: 'address', value: '0', data: '0x' }];
const request = sdkInstance.sendTransactions(txs);

expect(typeof request.requestId).toBe('number');
expect(request.data).toEqual(txs);
});
});
});
Loading

0 comments on commit dcd2cf6

Please sign in to comment.