Skip to content

Commit

Permalink
add gnosis safe connector
Browse files Browse the repository at this point in the history
  • Loading branch information
NoahZinsmeister committed May 2, 2022
1 parent 5281d0a commit 3becf08
Show file tree
Hide file tree
Showing 10 changed files with 756 additions and 22 deletions.
55 changes: 33 additions & 22 deletions packages/example-next/components/ConnectWithSelect.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import type { CoinbaseWallet } from '@web3-react/coinbase-wallet'
import type { Web3ReactHooks } from '@web3-react/core'
import { GnosisSafe } from '@web3-react/gnosis-safe'
import type { MetaMask } from '@web3-react/metamask'
import { Network } from '@web3-react/network'
import { WalletConnect } from '@web3-react/walletconnect'
import { useCallback, useState } from 'react'
import { CHAINS, getAddChainParameters, URLS } from '../chains'

function Select({
function ChainSelect({
chainId,
switchChain,
displayDefault,
Expand Down Expand Up @@ -42,7 +43,7 @@ export function ConnectWithSelect({
error,
isActive,
}: {
connector: MetaMask | WalletConnect | CoinbaseWallet | Network
connector: MetaMask | WalletConnect | CoinbaseWallet | Network | GnosisSafe
chainId: ReturnType<Web3ReactHooks['useChainId']>
isActivating: ReturnType<Web3ReactHooks['useIsActivating']>
error: ReturnType<Web3ReactHooks['useError']>
Expand Down Expand Up @@ -74,16 +75,20 @@ export function ConnectWithSelect({
if (error) {
return (
<div style={{ display: 'flex', flexDirection: 'column' }}>
<Select
chainId={desiredChainId}
switchChain={switchChain}
displayDefault={displayDefault}
chainIds={chainIds}
/>
{!(connector instanceof GnosisSafe) && (
<ChainSelect
chainId={desiredChainId}
switchChain={switchChain}
displayDefault={displayDefault}
chainIds={chainIds}
/>
)}
<div style={{ marginBottom: '1rem' }} />
<button
onClick={() =>
connector instanceof WalletConnect || connector instanceof Network
connector instanceof GnosisSafe
? void connector.activate()
: connector instanceof WalletConnect || connector instanceof Network
? void connector.activate(desiredChainId === -1 ? undefined : desiredChainId)
: void connector.activate(desiredChainId === -1 ? undefined : getAddChainParameters(desiredChainId))
}
Expand All @@ -95,32 +100,38 @@ export function ConnectWithSelect({
} else if (isActive) {
return (
<div style={{ display: 'flex', flexDirection: 'column' }}>
<Select
chainId={desiredChainId === -1 ? -1 : chainId}
switchChain={switchChain}
displayDefault={displayDefault}
chainIds={chainIds}
/>
{!(connector instanceof GnosisSafe) && (
<ChainSelect
chainId={desiredChainId === -1 ? -1 : chainId}
switchChain={switchChain}
displayDefault={displayDefault}
chainIds={chainIds}
/>
)}
<div style={{ marginBottom: '1rem' }} />
<button onClick={() => void connector.deactivate()}>Disconnect</button>
</div>
)
} else {
return (
<div style={{ display: 'flex', flexDirection: 'column' }}>
<Select
chainId={desiredChainId}
switchChain={isActivating ? undefined : switchChain}
displayDefault={displayDefault}
chainIds={chainIds}
/>
{!(connector instanceof GnosisSafe) && (
<ChainSelect
chainId={desiredChainId}
switchChain={isActivating ? undefined : switchChain}
displayDefault={displayDefault}
chainIds={chainIds}
/>
)}
<div style={{ marginBottom: '1rem' }} />
<button
onClick={
isActivating
? undefined
: () =>
connector instanceof WalletConnect || connector instanceof Network
connector instanceof GnosisSafe
? void connector.activate()
: connector instanceof WalletConnect || connector instanceof Network
? connector.activate(desiredChainId === -1 ? undefined : desiredChainId)
: connector.activate(desiredChainId === -1 ? undefined : getAddChainParameters(desiredChainId))
}
Expand Down
46 changes: 46 additions & 0 deletions packages/example-next/components/connectors/GnosisSafeCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { useEffect } from 'react'
import { gnosisSafe, hooks } from '../../connectors/gnosisSafe'
import { Accounts } from '../Accounts'
import { Card } from '../Card'
import { Chain } from '../Chain'
import { ConnectWithSelect } from '../ConnectWithSelect'
import { Status } from '../Status'

const { useChainId, useAccounts, useError, useIsActivating, useIsActive, useProvider, useENSNames } = hooks

export default function GnosisSafeCard() {
const chainId = useChainId()
const accounts = useAccounts()
const error = useError()
const isActivating = useIsActivating()

const isActive = useIsActive()

const provider = useProvider()
const ENSNames = useENSNames(provider)

// attempt to connect eagerly on mount
useEffect(() => {
void gnosisSafe.connectEagerly()
}, [])

return (
<Card>
<div>
<b>Gnosis Safe</b>
<Status isActivating={isActivating} error={error} isActive={isActive} />
<div style={{ marginBottom: '1rem' }} />
<Chain chainId={chainId} />
<Accounts accounts={accounts} provider={provider} ENSNames={ENSNames} />
</div>
<div style={{ marginBottom: '1rem' }} />
<ConnectWithSelect
connector={gnosisSafe}
chainId={chainId}
isActivating={isActivating}
error={error}
isActive={isActive}
/>
</Card>
)
}
4 changes: 4 additions & 0 deletions packages/example-next/connectors/gnosisSafe.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { initializeConnector } from '@web3-react/core'
import { GnosisSafe } from '@web3-react/gnosis-safe'

export const [gnosisSafe, hooks] = initializeConnector<GnosisSafe>((actions) => new GnosisSafe(actions))
1 change: 1 addition & 0 deletions packages/example-next/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"@web3-react/core": "8.0.25-beta.0",
"@web3-react/eip1193": "8.0.19-beta.0",
"@web3-react/empty": "8.0.13-beta.0",
"@web3-react/gnosis-safe": "8.0.0-beta.0",
"@web3-react/metamask": "8.0.20-beta.0",
"@web3-react/network": "8.0.19-beta.0",
"@web3-react/types": "8.0.13-beta.0",
Expand Down
2 changes: 2 additions & 0 deletions packages/example-next/pages/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import CoinbaseWalletCard from '../components/connectors/CoinbaseWalletCard'
import GnosisSafeCard from '../components/connectors/GnosisSafeCard'
import MetaMaskCard from '../components/connectors/MetaMaskCard'
import NetworkCard from '../components/connectors/NetworkCard'
import WalletConnectCard from '../components/connectors/WalletConnectCard'
Expand All @@ -13,6 +14,7 @@ export default function Home() {
<WalletConnectCard />
<CoinbaseWalletCard />
<NetworkCard />
<GnosisSafeCard />
</div>
</>
)
Expand Down
1 change: 1 addition & 0 deletions packages/gnosis-safe/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# @web3-react/gnosis-safe
31 changes: 31 additions & 0 deletions packages/gnosis-safe/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{
"name": "@web3-react/gnosis-safe",
"keywords": [
"web3-react",
"gnosis-safe"
],
"author": "Noah Zinsmeister <[email protected]>",
"license": "GPL-3.0-or-later",
"repository": "github:NoahZinsmeister/web3-react",
"publishConfig": {
"access": "public"
},
"version": "8.0.0-beta.0",
"files": [
"dist/*"
],
"type": "commonjs",
"types": "./dist/index.d.ts",
"main": "./dist/index.js",
"exports": "./dist/index.js",
"scripts": {
"prebuild": "rm -rf dist",
"build": "tsc",
"start": "tsc --watch"
},
"dependencies": {
"@gnosis.pm/safe-apps-provider": "^0.11.0",
"@gnosis.pm/safe-apps-sdk": "^7.3.0",
"@web3-react/types": "^8.0.13-beta.0"
}
}
112 changes: 112 additions & 0 deletions packages/gnosis-safe/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import type { SafeAppProvider } from '@gnosis.pm/safe-apps-provider'
import type SafeAppsSDK from '@gnosis.pm/safe-apps-sdk'
import type { Opts } from '@gnosis.pm/safe-apps-sdk'
import type { Actions } from '@web3-react/types'
import { Connector } from '@web3-react/types'

export class NoSafeContext extends Error {
public constructor() {
super('The app is loaded outside safe context')
this.name = NoSafeContext.name
Object.setPrototypeOf(this, NoSafeContext.prototype)
}
}

export class GnosisSafe extends Connector {
/** {@inheritdoc Connector.provider} */
public provider: SafeAppProvider | undefined

private readonly options?: Opts
private eagerConnection?: Promise<void>

/**
* A `SafeAppsSDK` instance.
*/
public sdk: SafeAppsSDK | undefined

/**
* @param connectEagerly - A flag indicating whether connection should be initiated when the class is constructed.
*/
constructor(actions: Actions, connectEagerly = false, options?: Opts) {
super(actions)

if (connectEagerly && typeof window === 'undefined') {
throw new Error('connectEagerly = true is invalid for SSR, instead use the connectEagerly method in a useEffect')
}

this.options = options

if (connectEagerly) void this.connectEagerly()
}

// check if we're in an iframe
private get inIframe() {
if (typeof window === 'undefined') return false
if (window !== window.parent) return true
return false
}

private async isomorphicInitialize(): Promise<void> {
if (this.eagerConnection) return this.eagerConnection

// kick off import early to minimize waterfalls
const SafeAppProviderPromise = import('@gnosis.pm/safe-apps-provider').then(
({ SafeAppProvider }) => SafeAppProvider
)

await (this.eagerConnection = import('@gnosis.pm/safe-apps-sdk').then(async (m) => {
this.sdk = new m.default(this.options)

const safe = await Promise.race([
this.sdk.safe.getInfo(),
new Promise<undefined>((resolve) => setTimeout(resolve, 500)),
])

if (safe) {
const SafeAppProvider = await SafeAppProviderPromise
this.provider = new SafeAppProvider(safe, this.sdk)
}
}))
}

/** {@inheritdoc Connector.connectEagerly} */
public async connectEagerly(): Promise<void> {
if (!this.inIframe) return

const cancelActivation = this.actions.startActivation()

await this.isomorphicInitialize()
if (!this.provider) return cancelActivation()

try {
this.actions.update({
chainId: this.provider.chainId,
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
accounts: [await this.sdk!.safe.getInfo().then(({ safeAddress }) => safeAddress)],
})
} catch (error) {
console.debug('Could not connect eagerly', error)
cancelActivation()
}
}

public async activate(): Promise<void> {
if (!this.inIframe) return this.actions.reportError(new NoSafeContext())

// only show activation if this is a first-time connection
if (!this.sdk) this.actions.startActivation()

await this.isomorphicInitialize()
if (!this.provider) return this.actions.reportError(new NoSafeContext())

try {
this.actions.update({
chainId: this.provider.chainId,
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
accounts: [await this.sdk!.safe.getInfo().then(({ safeAddress }) => safeAddress)],
})
} catch (error) {
this.actions.reportError(error as Error | undefined)
}
}
}
7 changes: 7 additions & 0 deletions packages/gnosis-safe/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"extends": "../../tsconfig.json",
"include": ["./src"],
"compilerOptions": {
"outDir": "./dist"
}
}
Loading

0 comments on commit 3becf08

Please sign in to comment.