Skip to content

Commit

Permalink
feat(connectors): modularize connectors
Browse files Browse the repository at this point in the history
  • Loading branch information
tarrencev authored and fracek committed Mar 14, 2022
1 parent dbaf7b2 commit 8171dae
Show file tree
Hide file tree
Showing 14 changed files with 138 additions and 54 deletions.
5 changes: 5 additions & 0 deletions .changeset/tiny-crews-deliver.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@starknet-react/core': minor
---

Modularize connectors
6 changes: 3 additions & 3 deletions examples/starknet-react-next/src/components/ConnectWallet.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { useStarknet } from '@starknet-react/core'
import { useStarknet, InjectedConnector } from '@starknet-react/core'

export function ConnectWallet() {
const { account, connectBrowserWallet } = useStarknet()
const { account, connect } = useStarknet()

if (account) {
return <p>Account: {account}</p>
}

return <button onClick={connectBrowserWallet}>Connect</button>
return <button onClick={() => connect(new InjectedConnector())}>Connect</button>
}
19 changes: 19 additions & 0 deletions packages/core/src/connectors/base.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { AccountInterface } from 'starknet'

export abstract class Connector<Options = any> {
/** Unique connector id */
abstract readonly id: string
/** Connector name */
abstract readonly name: string
/** Whether connector is usable */
static readonly ready: boolean
/** Options to use with connector */
readonly options: Options

constructor({ options }: { options: Options }) {
this.options = options
}

abstract connect(): Promise<AccountInterface>
abstract account(): Promise<AccountInterface>
}
2 changes: 2 additions & 0 deletions packages/core/src/connectors/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { Connector } from './base'
export { InjectedConnector } from './injected'
56 changes: 56 additions & 0 deletions packages/core/src/connectors/injected.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { getStarknet } from '@argent/get-starknet'

import { Connector } from './index'
import {
ConnectorNotConnectedError,
ConnectorNotFoundError,
UserRejectedRequestError,
} from '../errors'

type InjectedConnectorOptions = {
showModal?: boolean
}

export class InjectedConnector extends Connector<InjectedConnectorOptions> {
readonly id = 'injected'
readonly name = 'argent'
static readonly ready = typeof window != 'undefined' && !!window.starknet

private starknet = getStarknet()

constructor(options?: InjectedConnectorOptions) {
super({ options })
}

async connect() {
if (!InjectedConnector.ready) {
throw new ConnectorNotFoundError()
}

try {
await this.starknet.enable(this.options)
} catch {
// NOTE: Argent v3.0.0 swallows the `.enable` call on reject, so this won't get hit.
throw new UserRejectedRequestError()
}

if (!this.starknet.isConnected) {
// NOTE: Argent v3.0.0 swallows the `.enable` call on reject, so this won't get hit.
throw new UserRejectedRequestError()
}

return this.starknet.account
}

account() {
if (!InjectedConnector.ready) {
throw new ConnectorNotFoundError()
}

return this.starknet.account
? Promise.resolve(this.starknet.account)
: Promise.reject(new ConnectorNotConnectedError())
}
}

export type EventHandler = (accounts: string[]) => void
19 changes: 19 additions & 0 deletions packages/core/src/errors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
export class ConnectorAlreadyConnectedError extends Error {
name = 'ConnectorAlreadyConnectedError'
message = 'Connector already connected'
}

export class ConnectorNotConnectedError extends Error {
name = 'ConnectorNotConnectedError'
message = 'Connector not connected'
}

export class ConnectorNotFoundError extends Error {
name = 'ConnectorNotFoundError'
message = 'Connector not found'
}

export class UserRejectedRequestError extends Error {
name = 'UserRejectedRequestError'
message = 'User rejected request'
}
1 change: 1 addition & 0 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ export { useStarknetTransactionManager } from './providers/transaction'
export type { Transaction } from './providers/transaction'
export { StarknetProvider } from './providers'
export * from './hooks'
export * from './connectors'
40 changes: 13 additions & 27 deletions packages/core/src/providers/starknet/manager.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { getStarknet } from '@argent/get-starknet'
import { useCallback, useEffect, useReducer, useState } from 'react'
import { useCallback, useReducer } from 'react'
import { defaultProvider, ProviderInterface } from 'starknet'

import { StarknetState } from './model'
import { Connector } from '../../connectors'

interface StarknetManagerState {
account?: string
Expand Down Expand Up @@ -45,38 +45,24 @@ function reducer(state: StarknetManagerState, action: Action): StarknetManagerSt
}

export function useStarknetManager(): StarknetState {
const [hasStarknet, setHasStarknet] = useState(false)
const [state, dispatch] = useReducer(reducer, {
library: defaultProvider,
})

const { account, library, error } = state

useEffect(() => {
if (typeof window !== undefined) {
// calling getStarknet here makes the detection more reliable
const starknet = getStarknet()
if (starknet.version !== 'uninstalled') {
setHasStarknet(true)
const connect = useCallback((connector: Connector) => {
connector.connect().then(
(account) => {
dispatch({ type: 'set_account', account: account.address })
dispatch({ type: 'set_provider', provider: account })
},
(err) => {
console.error(err)
dispatch({ type: 'set_error', error: 'could not activate StarkNet' })
}
}
}, [])

const connectBrowserWallet = useCallback(async () => {
try {
if (typeof window === undefined) return
if (window.starknet === undefined) return
const [account] = await window.starknet.enable()
dispatch({ type: 'set_account', account })
const starknet = getStarknet()
if (starknet.account) {
dispatch({ type: 'set_provider', provider: starknet.account })
}
} catch (err) {
console.error(err)
dispatch({ type: 'set_error', error: 'could not activate StarkNet' })
}
)
}, [])

return { account, hasStarknet, connectBrowserWallet, library, error }
return { account, connect, library, error }
}
7 changes: 3 additions & 4 deletions packages/core/src/providers/starknet/model.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
import { defaultProvider, ProviderInterface } from 'starknet'
import { Connector } from '../../connectors'

export interface StarknetState {
account?: string
hasStarknet: boolean
connectBrowserWallet: () => void
connect: (connector: Connector) => void
library: ProviderInterface
error?: string
}

export const STARKNET_INITIAL_STATE: StarknetState = {
account: undefined,
hasStarknet: false,
connectBrowserWallet: () => undefined,
connect: () => undefined,
library: defaultProvider,
}
3 changes: 1 addition & 2 deletions packages/core/test/providers/starknet.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@ describe('useStarknet', () => {
const wrapper = ({ children }) => <StarknetProvider>{children}</StarknetProvider>
const { result } = renderHook(() => useStarknet(), { wrapper })

const { account, hasStarknet } = result.current
const { account } = result.current
expect(account).toBeUndefined()
expect(hasStarknet).toBeFalsy()
})
})
5 changes: 2 additions & 3 deletions website/docs/hooks/starknet.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,15 @@ Hook to access the current instance of the underlying StarkNet library.
```typescript
import { useStarknet } from '@starknet-react/core'

const { account, hasStarknet, connectBrowserWallet, library, error } = useStarknet()
const { account, connect, library, error } = useStarknet()
```

## Return Values

```typescript
{
account?: string
hasStarknet: boolean
connectBrowserWallet: () => void
connect: (Connector) => Promise<void>
library: ProviderInterface
error?: string
}
Expand Down
17 changes: 10 additions & 7 deletions website/docs/intro.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ yarn add @starknet-react/core @argent/get-starknet starknet
```

Or with npm:

```
npm install @starknet-react/core @argent/get-starknet starknet
```
Expand All @@ -36,21 +37,23 @@ function App() {
```

3. Connect the wallet (needs Argent X StartkNet Wallet extension installed)

```typescript
import { useStarknet } from '@starknet-react/core';
import { useStarknet, InjectedConnector } from '@starknet-react/core'

function YourComponent() {
const { connectBrowserWallet } = useStarknet()
const { connect } = useStarknet()

return (
<button onClick={connectBrowserWallet}>
Connect Wallet
</button>
)
if (!InjectedConnector.ready) {
;<span>Injected connector not found</span>
}

return <button onClick={connect(new InjectedConnector())}>Connect Wallet</button>
}
```

4. Retrieve the account address

```typescript
import { useStarknet } from '@starknet-react/core'

Expand Down
7 changes: 4 additions & 3 deletions website/src/components/Demo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
useStarknetTransactionManager,
Transaction,
useStarknet,
InjectedConnector,
} from '@starknet-react/core'

import CounterAbi from '../abi/counter.json'
Expand Down Expand Up @@ -48,16 +49,16 @@ function useCounterContract() {
}

function DemoAccount() {
const { account, connectBrowserWallet, hasStarknet } = useStarknet()
const { account, connect } = useStarknet()
return (
<Section>
<SectionTitle>Account</SectionTitle>
<div>
<p>Connected Account: {account}</p>
</div>
{hasStarknet ? (
{InjectedConnector.ready ? (
<ActionRoot>
<Button onClick={connectBrowserWallet}>Connect Argent-X</Button>
<Button onClick={() => connect(new InjectedConnector())}>Connect Argent-X</Button>
</ActionRoot>
) : (
<div>
Expand Down
5 changes: 0 additions & 5 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -138,11 +138,6 @@
"@jridgewell/trace-mapping" "^0.2.2"
sourcemap-codec "1.4.8"

"@argent/get-starknet@^2.1.1":
version "2.1.1"
resolved "https://registry.yarnpkg.com/@argent/get-starknet/-/get-starknet-2.1.1.tgz#cc44b02ad11d427d0c6300c3c8e6d8eb9f2366cc"
integrity sha512-CqW/bXrvcTnLlhCpxc51uhRjVb7UUbUgkckhvIWBG3Ma9wKFWaJyJyZizRjh+I47NL5VL3PpJwzJC1tEKeaZBQ==

"@argent/get-starknet@^3.0":
version "3.0.0"
resolved "https://registry.yarnpkg.com/@argent/get-starknet/-/get-starknet-3.0.0.tgz#a1c581b8b3777e7dfb4d8da1a1cbab84dfbf28b1"
Expand Down

0 comments on commit 8171dae

Please sign in to comment.