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

Update wagmi connector v1 #409

Merged
merged 5 commits into from
Jan 11, 2024
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
7 changes: 7 additions & 0 deletions .changeset/cool-gorillas-invite.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@bitski/wagmi-connector': major
---

- Update to be compatible with latest RainbowKit.
- Abstract Bitski SDK inclusion and simplify connector options. Now only accepts chains and options, where options requires an appId and optionally bitskiOptions which map to Bitski SDK provider options.
- Include bitskiWallet export for usage with RainbowKit.
26,427 changes: 11,300 additions & 15,127 deletions package-lock.json

Large diffs are not rendered by default.

74 changes: 69 additions & 5 deletions packages/wagmi-connector/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,27 @@ npm install @bitski/wagmi-connector

## Usage

Below are common examples. For more details on all configurable options, please also see the wagmi and RainbowKit docs.

### Wagmi Only

```javascript
import { BitskiWagmiConnector } from '@bitski/wagmi-connector';
import { BitskiConnector } from '@bitski/wagmi-connector';
import { createConfig } from '@wagmi/core';

wagmiConfig = createConfig({
connectors: [
new BitskiWagmiConnector({
new BitskiConnector({
chains,
options: {
id: 'my-connector',
name: 'My App Wallet',
bitski,
loginHint: LOGIN_HINT,
waas: { enabled: true },
appId: 'my-bitski-app-id',
bitskiOptions: {
waas: { enabled: false },
callbackURL: 'https://callback.url:3000',
// For more options, see the list of ProviderOptions under the bitski package
},
},
}),
new WalletConnectConnector({
Expand All @@ -34,3 +41,60 @@ wagmiConfig = createConfig({
...defaultConfig,
});
```

### RainbowKit + Wagmi

```javascript
import { bitskiWallet } from '@bitski/wagmi-connector';
import {
connectorsForWallets,
RainbowKitProvider,
Locale,
} from "@rainbow-me/rainbowkit";
import {
injectedWallet,
metaMaskWallet,
coinbaseWallet,
} from "@rainbow-me/rainbowkit/wallets";
import { configureChains, createConfig, WagmiConfig } from "wagmi";
import { mainnet, polygon, optimism, arbitrum, base, zora } from "viem/chains";
import { publicProvider } from "wagmi/providers/public";

const connectors = connectorsForWallets([
{
groupName: "Recommended",
wallets: [
bitskiWallet({
options: { appId: 'my-bitski-app-id', bitskiOptions: { network } },
chains,
}),
],
},
{
groupName: "Other Wallets",
wallets: [
injectedWallet({ chains }),
metaMaskWallet({ chains, projectId: "YOUR_PROJECT_ID" }),
coinbaseWallet({ appName: "YOUR_APP_NAME", chains }),
],
},
]);

const wagmiConfig = createConfig({
autoConnect: true,
connectors,
publicClient,
webSocketPublicClient,
});

function MyApp({ Component, pageProps }: AppProps) {
const { locale } = useRouter() as { locale: Locale };
return (
<WagmiConfig config={wagmiConfig}>
<RainbowKitProvider appInfo={demoAppInfo} chains={chains} locale={locale}>
<Component {...pageProps} />
</RainbowKitProvider>
</WagmiConfig>
);
}
```
9 changes: 5 additions & 4 deletions packages/wagmi-connector/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,14 @@
},
"scripts": {
"lint": "eslint . --cache",
"build": "tsc -p tsconfig.main.json && tsc -p tsconfig.module.json",
"build": "tsc -p tsconfig.json",
"prettier": "prettier --config ../../.prettierrc '{src,tests}/**/*.ts' --write"
},
"dependencies": {
"@wagmi/core": "^1.4.11",
"bitski": "^3.8.0",
"viem": "^1.19.13"
"@wagmi/core": "^1.4.13",
"bitski": "^3.8.1",
"viem": "^1.19.13",
"@rainbow-me/rainbowkit": "^1.3.3"
},
"devDependencies": {
"@babel/core": "^7.6.4",
Expand Down
205 changes: 108 additions & 97 deletions packages/wagmi-connector/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,104 +1,74 @@
import type { BitskiProviderShim } from 'bitski/lib/provider-shim';
import type { Chain } from 'viem/chains';

import { Bitski, Mainnet, Network } from 'bitski';
import { Bitski, ProviderOptions } from 'bitski';
import type { Chain, Wallet } from '@rainbow-me/rainbowkit';
import { Connector, ConnectorData, WalletClient } from '@wagmi/core';
import { createWalletClient, custom, getAddress } from 'viem';

export interface BitskiWagmiConnectorOptions {
import { normalizeChainId, ConnectorNotFoundError } from '@wagmi/connectors';

export interface BitskiConnectorOptions {
id?: string;
name?: string;
bitski: Bitski;
loginHint?: string;
waas?: {
enabled: boolean;
userId?: string;
};
appId: string;
bitskiOptions?: ProviderOptions;
shimDisconnect?: boolean;
}

export class BitskiWagmiConnector extends Connector<
BitskiProviderShim,
BitskiWagmiConnectorOptions
> {
async getChainId(): Promise<number> {
const provider = await this.getProvider();
const result: any = await provider.request({ method: 'net_version' });
return result as number;
}
async isAuthorized(): Promise<boolean> {
return true;
}
export class BitskiConnector extends Connector<BitskiProviderShim, BitskiConnectorOptions> {
readonly id: string;
readonly name: string;
readonly ready = true;

// These never happen
provider?: BitskiProviderShim;
bitski: Bitski;

// eslint-disable-next-line @typescript-eslint/no-empty-function
protected onAccountsChanged(): void {}
// eslint-disable-next-line @typescript-eslint/no-empty-function
protected onChainChanged(): void {}
// eslint-disable-next-line @typescript-eslint/no-empty-function
protected onDisconnect(): void {}
bitskiOptions: ProviderOptions;

readonly id;
readonly name;
readonly ready = true;
protected shimDisconnectKey = `${this.id}.shimDisconnect`;

bitski: Bitski;
#provider?: BitskiProviderShim;
loginHint?: string;
waasEnabled: boolean;
userId?: string;
constructor({
chains,
options: options_,
}: {
chains?: Chain[];
options: BitskiConnectorOptions;
}) {
const options = {
shimDisconnect: true,
...options_,
};

constructor({ chains, options }: { chains?: Chain[]; options: BitskiWagmiConnectorOptions }) {
super({
chains,
options,
});

this.id = options.id ?? 'bitski';
this.name = options.name ?? 'Bitski';

this.bitski = options.bitski;
this.loginHint = options.loginHint;
this.waasEnabled = options.waas?.enabled ?? false;
this.userId = options.waas?.userId;
}
const { bitskiOptions = {} } = options;

async getProvider(): Promise<BitskiProviderShim> {
if (this.#provider) {
return this.#provider;
}

return await this.getProviderInternal();
this.bitski = new Bitski(options.appId, bitskiOptions.callbackURL);
this.bitskiOptions = bitskiOptions;
}

async getProviderInternal(config?: { chainId?: number }): Promise<BitskiProviderShim> {
const chainId = config?.chainId ?? this.chains[0].id ?? 1;

const chain = this.chains.find((chain) => {
return chain.id === chainId;
});
async connect(config?: { chainId?: number }): Promise<Required<ConnectorData>> {
if (this.bitskiOptions.waas?.enabled) {
const loginHint = `fa_${btoa(this.options.appId)}`;
await this.bitski.start({
login_hint: loginHint,
prompt: 'login',
});
} else {
await this.bitski.signIn();
}

const network = (chain && getNetwork(chain)) ?? {
chainId: chainId,
rpcUrl: `https://api.bitski.com/v1/web3/${chainId}`,
};
const provider = await this.getProvider();

const provider = await this.bitski.getProvider({
network,
waas: {
enabled: this.waasEnabled,
userId: this.userId,
},
const result: any = await provider.request({
method: 'eth_requestAccounts',
});
this.#provider = provider;

return provider;
}

async connect(config?: { chainId?: number }): Promise<Required<ConnectorData>> {
await this.bitski.start({ login_hint: this.loginHint, prompt: 'login' });

const provider = await this.getProviderInternal(config);
const result: any = await provider.request({ method: 'eth_accounts', params: [] });
const account = result[0];
const chain = this.chains.find((x) => x.id === config?.chainId);

Expand All @@ -122,17 +92,27 @@ export class BitskiWagmiConnector extends Connector<
return getAddress(result[0] as string);
}

async getChainId(): Promise<number> {
const provider = await this.getProvider();
if (!provider) throw new ConnectorNotFoundError();

const chainId = normalizeChainId((await provider.request({ method: 'eth_chainId' })) as number);

return chainId;
}

async getProvider(): Promise<BitskiProviderShim> {
if (!this.provider) {
this.provider = await this.bitski.getProvider(this.bitskiOptions);
}

return this.provider;
}

async getWalletClient(config?: { chainId?: number }): Promise<WalletClient> {
const chainId = config?.chainId ?? 1;
const chain = this.chains.find((x) => x.id === chainId);
const network = chain ? getNetwork(chain) : undefined;
const provider = this.bitski.getProvider({
network,
waas: {
enabled: this.waasEnabled,
userId: this.userId,
},
});
const provider = await this.getProvider();
if (!provider) throw new Error('provider is required.');
const account = await this.getAccount();
const walletClient = createWalletClient({
Expand All @@ -143,22 +123,53 @@ export class BitskiWagmiConnector extends Connector<

return walletClient as WalletClient;
}
}

function getNetwork(chain: Chain): Network {
const chainId = chain.id;
const rpcUrl = getBitskiRpcUrl(chain);
return {
chainId,
rpcUrl,
};
async isAuthorized(): Promise<boolean> {
try {
if (
this.options.shimDisconnect &&
// If shim does not exist in storage, wallet is disconnected
!this.storage?.getItem(this.shimDisconnectKey)
)
return false;

const provider = await this.getProvider();
if (!provider) throw new ConnectorNotFoundError();
const account = await this.getAccount();
return !!account;
} catch {
return false;
}
}

// eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-empty-function
protected onAccountsChanged(): void {}

// eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-empty-function
protected onChainChanged(): void {}

// eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-empty-function
protected onDisconnect(): void {}
}

function getBitskiRpcUrl(chain: Chain): string {
switch (chain.id) {
case 1:
return Mainnet.rpcUrl;
default:
return chain.rpcUrls.public.http[0];
}
export interface BitskiWalletOptions {
chains: Chain[];
options: BitskiConnectorOptions & Wallet;
}

export const bitskiWallet = ({ chains, options }: BitskiWalletOptions): Wallet => ({
id: options.id ?? 'bitski',
name: options.id ?? 'Bitski',
iconUrl: options.iconUrl ?? 'https://cdn.bitskistatic.com/docs-web/bitskiWallet.svg',
iconBackground: options.iconBackground ?? '#fff',
downloadUrls: options.downloadUrls ?? {
browserExtension:
'https://chrome.google.com/webstore/detail/bitski/feejiigddaafeojfddjjlmfkabimkell',
desktop: 'https://chrome.google.com/webstore/detail/bitski/feejiigddaafeojfddjjlmfkabimkell',
ios: 'https://apps.apple.com/us/app/bitski-wallet/id1587199538',
mobile: 'https://apps.apple.com/us/app/bitski-wallet/id1587199538',
},
createConnector: () => ({
connector: new BitskiConnector({ chains, options }),
}),
});
Loading
Loading