@bigmi/client
Version:
Reactive primitives for Bitcoin apps.
143 lines (129 loc) • 4.21 kB
text/typescript
import {
type Address,
ConnectorAccountNotFoundError,
createClient,
custom,
parseAccount,
} from '@bigmi/core'
import type { Compute } from '@bigmi/core'
import {
ConnectorChainMismatchError,
ConnectorNotConnectedError,
} from '@bigmi/core'
import type { Client } from '@bigmi/core'
import type { Account } from '@bigmi/core'
import type { Config } from '../factories/createConfig.js'
import type { Connection } from '../types/connection.js'
import type { Connector } from '../types/connector.js'
import { getAddress } from './getAddress.js'
export type ChainIdParameter<
config extends Config,
chainId extends
| config['chains'][number]['id']
| undefined = config['chains'][number]['id'],
> = {
chainId?:
| (chainId extends config['chains'][number]['id'] ? chainId : undefined)
| config['chains'][number]['id']
| undefined
}
export type GetConnectorClientReturnType<
config extends Config = Config,
chainId extends
config['chains'][number]['id'] = config['chains'][number]['id'],
> = Compute<
Client<
config['_internal']['transports'][chainId],
Extract<config['chains'][number], { id: chainId }>,
Account
>
>
export type ConnectorParameter = {
connector?: Connector | undefined
}
export type GetConnectorClientParameters<
config extends Config = Config,
chainId extends
config['chains'][number]['id'] = config['chains'][number]['id'],
> = Compute<
ChainIdParameter<config, chainId> &
ConnectorParameter & {
/**
* Account to use for the client.
*
* - `Account | Address`: An Account MUST exist on the connector.
* - `null`: Account MAY NOT exist on the connector. This is useful for
* actions that can infer the account from the connector (e.g. sending a
* call without a connected account – the user will be prompted to select
* an account within the wallet).
*/
account?: Address | null | undefined
}
>
export async function getConnectorClient<
C extends Config,
ChainId extends C['chains'][number]['id'],
>(
config: C,
parameters: GetConnectorClientParameters<C, ChainId> = {}
): Promise<GetConnectorClientReturnType<C, ChainId>> {
// Get connection
let connection: Connection | undefined
if (parameters.connector) {
const { connector } = parameters
const [accounts, chainId] = await Promise.all([
connector.getAccounts(),
connector.getChainId(),
])
connection = {
accounts: accounts as readonly [Address, ...Address[]],
chainId,
connector,
}
} else {
connection = config.state.connections.get(config.state.current!)
}
if (!connection) {
throw new ConnectorNotConnectedError()
}
const chainId = parameters.chainId ?? connection.chainId
// Check connector using same chainId as connection
const connectorChainId = await connection.connector.getChainId()
if (connectorChainId !== connection.chainId) {
throw new ConnectorChainMismatchError({
connectionChainId: connection.chainId,
connectorChainId,
})
}
// If connector has custom `getClient` implementation
type Return = GetConnectorClientReturnType<C, ChainId>
const connector = connection.connector
if (connector.getClient) {
return connector.getClient({ chainId }) as unknown as Return
}
// Default using `custom` transport
const account = parseAccount(parameters.account ?? connection.accounts[0]!)
account.address = getAddress(account.address)
const chain = config.chains.find((chain: { id: any }) => chain.id === chainId)
const provider = (await connection.connector.getProvider({ chainId })) as {
request(...args: any): Promise<any>
}
// If account was provided, check that it exists on the connector
if (
parameters.account &&
!connection.accounts.some(
(x: string) => x.toLowerCase() === account.address.toLowerCase()
)
) {
throw new ConnectorAccountNotFoundError({
address: account.address,
connector: connector.name,
})
}
return createClient({
account,
chain,
name: 'Connector Client',
transport: (opts) => custom(provider)({ ...opts, retryCount: 0 }),
}) as Return
}