Skip to content

Commit

Permalink
feat: add okx wallet
Browse files Browse the repository at this point in the history
  • Loading branch information
chybisov committed Oct 28, 2024
1 parent 35d56b0 commit 58b93b5
Show file tree
Hide file tree
Showing 2 changed files with 205 additions and 0 deletions.
204 changes: 204 additions & 0 deletions packages/client/src/connectors/okx.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
import type { SignPsbtParameters, UTXOWalletProvider } from '@bigmi/core'
import {
type Address,
MethodNotSupportedRpcError,
UserRejectedRequestError,
withRetry,
} from 'viem'
import { ProviderNotFoundError, createConnector } from 'wagmi'
import type { UTXOConnectorParameters } from './types.js'

export type OKXBitcoinEventMap = {
accountsChanged(accounts: Address[]): void
}

export type OKXBitcoinEvents = {
addListener<TEvent extends keyof OKXBitcoinEventMap>(
event: TEvent,
listener: OKXBitcoinEventMap[TEvent]
): void
removeListener<TEvent extends keyof OKXBitcoinEventMap>(
event: TEvent,
listener: OKXBitcoinEventMap[TEvent]
): void
}

type OKXConnectorProperties = {
getAccounts(): Promise<readonly Address[]>
onAccountsChanged(accounts: Address[]): void
getInternalProvider(): Promise<OKXBitcoinProvider>
} & UTXOWalletProvider

type OKXBitcoinProvider = {
requestAccounts(): Promise<Address[]>
getAccounts(): Promise<Address[]>
signPsbt(
psbtHex: string,
options: {
toSignInputs: {
index: number
address: string
sighashTypes?: number[]
}[]
autoFinalized?: boolean
}
): Promise<string>
} & OKXBitcoinEvents

okx.type = 'UTXO' as const
export function okx(parameters: UTXOConnectorParameters = {}) {
const { chainId, shimDisconnect = true } = parameters
let accountsChanged: ((accounts: Address[]) => void) | undefined
return createConnector<
UTXOWalletProvider | undefined,
OKXConnectorProperties
>((config) => ({
id: 'com.okex.wallet.bitcoin',
name: 'OKX Wallet',
type: okx.type,
icon: 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIzMiIgaGVpZ2h0PSIzMiIgZmlsbD0ibm9uZSI+CiAgICA8cGF0aCBmaWxsPSIjMDAwIiBkPSJNMCAwaDMydjMySDB6Ii8+CiAgICA8cGF0aCBmaWxsPSIjZmZmIiBkPSJNMTIuNiA3SDcuNGEuNC40IDAgMCAwLS40LjR2NS4yYzAgLjIyLjE3OS40LjQuNGg1LjJhLjQuNCAwIDAgMCAuNC0uNFY3LjRhLjQuNCAwIDAgMC0uNC0uNE0xOC42MDIgMTMuMDAyaC01LjJhLjQuNCAwIDAgMC0uNC40djUuMmMwIC4yMi4xNzkuNC40LjRoNS4yYS40LjQgMCAwIDAgLjQtLjR2LTUuMmEuNC40IDAgMCAwLS40LS40TTE5LjQgN2g1LjJhLjQuNCAwIDAgMSAuNC40djUuMmEuNC40IDAgMCAxLS40LjRoLTUuMmEuNC40IDAgMCAxLS40LS40VjcuNGEuNC40IDAgMCAxIC40LS40TTEyLjYgMTlINy40YS40LjQgMCAwIDAtLjQuNHY1LjJjMCAuMjIuMTc5LjQuNC40aDUuMmEuNC40IDAgMCAwIC40LS40di01LjJhLjQuNCAwIDAgMC0uNC0uNE0xOS40IDE5aDUuMmEuNC40IDAgMCAxIC40LjR2NS4yYS40LjQgMCAwIDEtLjQuNGgtNS4yYS40LjQgMCAwIDEtLjQtLjR2LTUuMmEuNC40IDAgMCAxIC40LS40Ii8+Cjwvc3ZnPgo=',
async setup() {
//
},
async getInternalProvider() {
if (typeof window === 'undefined') {
return
}
if ('okxwallet' in window) {
const anyWindow: any = window
const internalProvider = anyWindow.okxwallet?.bitcoin

if (internalProvider?.isOkxWallet) {
return internalProvider
}
}
},
async getProvider() {
const internalProvider = await this.getInternalProvider()
if (!internalProvider) {
return
}
const provider = {
request: this.request.bind(internalProvider),
}
return provider
},
async request(this: OKXBitcoinProvider, { method, params }): Promise<any> {
switch (method) {
case 'signPsbt': {
const { psbt, ...options } = params as SignPsbtParameters
const toSignInputs = options.inputsToSign.flatMap(
({ sigHash, address, signingIndexes }) =>
signingIndexes.map((index) => ({
index,
address,
sighashTypes: sigHash !== undefined ? [sigHash] : undefined,
}))
)
const signedPsbt = await this.signPsbt(psbt, {
toSignInputs,
autoFinalized: options.finalize,
})
return signedPsbt
}
default:
throw new MethodNotSupportedRpcError(
new Error(MethodNotSupportedRpcError.name),
{
method,
}
)
}
},
async connect() {
const provider = await this.getInternalProvider()
if (!provider) {
throw new ProviderNotFoundError()
}
try {
const accounts = await provider.requestAccounts()
const chainId = await this.getChainId()

if (!accountsChanged) {
accountsChanged = this.onAccountsChanged.bind(this)
provider.addListener('accountsChanged', accountsChanged)
}

// Remove disconnected shim if it exists
if (shimDisconnect) {
await Promise.all([
config.storage?.setItem(`${this.id}.connected`, true),
config.storage?.removeItem(`${this.id}.disconnected`),
])
}
return { accounts, chainId }
} catch (error: any) {
throw new UserRejectedRequestError({
name: UserRejectedRequestError.name,
message: error.message,
})
}
},
async disconnect() {
const provider = await this.getInternalProvider()

if (accountsChanged) {
provider?.removeListener('accountsChanged', accountsChanged)
accountsChanged = undefined
}

// Add shim signalling connector is disconnected
if (shimDisconnect) {
await Promise.all([
config.storage?.setItem(`${this.id}.disconnected`, true),
config.storage?.removeItem(`${this.id}.connected`),
])
}
},
async getAccounts() {
const provider = await this.getInternalProvider()
if (!provider) {
throw new ProviderNotFoundError()
}
const accounts = await provider.getAccounts()
return accounts as Address[]
},
async getChainId() {
return chainId!
},
async isAuthorized() {
try {
const isDisconnected =
shimDisconnect &&
// If shim exists in storage, connector is disconnected
(await config.storage?.getItem(`${this.id}.disconnected`))
if (isDisconnected) {
return false
}
const accounts = await withRetry(() => this.getAccounts())
return !!accounts.length
} catch {
return false
}
},
async onAccountsChanged(accounts) {
if (accounts.length === 0) {
this.onDisconnect()
} else {
config.emitter.emit('change', {
accounts: accounts as Address[],
})
}
},
onChainChanged(chain) {
const chainId = Number(chain)
config.emitter.emit('change', { chainId })
},
async onDisconnect(_error) {
// No need to remove `${this.id}.disconnected` from storage because `onDisconnect` is typically
// only called when the wallet is disconnected through the wallet's interface, meaning the wallet
// actually disconnected and we don't need to simulate it.
config.emitter.emit('disconnect')
},
}))
}
1 change: 1 addition & 0 deletions packages/client/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export { getConnectorClient } from './actions/getConnectorClient.js'
export { reconnect } from './actions/reconnect.js'
export { watchAccount } from './actions/watchAccount.js'
export { ctrl } from './connectors/ctrl.js'
export { okx } from './connectors/okx.js'
export type {
CtrlBitcoinEventMap,
CtrlBitcoinEvents,
Expand Down

0 comments on commit 58b93b5

Please sign in to comment.