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

feat: add connectors #51

Merged
merged 1 commit into from
Mar 14, 2022
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
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
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
fracek marked this conversation as resolved.
Show resolved Hide resolved
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