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

Integrate okxwallet #931

Merged
merged 1 commit into from
Jul 24, 2023
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
14 changes: 14 additions & 0 deletions src/assets/images/OKXWallet.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
225 changes: 225 additions & 0 deletions src/connectors/OkxWallet.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,225 @@
import detectEthereumProvider from '@metamask/detect-provider';
import {
Actions,
AddEthereumChainParameter,
ProviderConnectInfo,
ProviderRpcError,
WatchAssetParameters,
} from '@web3-react/types';
import { Connector } from '@web3-react/types';

export class NoOkxWalletError extends Error {
public constructor() {
super('BitKeep not installed');
this.name = NoOkxWalletError.name;
Object.setPrototypeOf(this, NoOkxWalletError.prototype);
}
}

function parseChainId(chainId: string) {
return Number.parseInt(chainId, 16);
}

/**
* @param options - Options to pass to `@metamask/detect-provider`
* @param onError - Handler to report errors thrown from eventListeners.
*/
export interface OkxWalletConstructorArgs {
actions: Actions;
options?: Parameters<typeof detectEthereumProvider>[0];
onError?: (error: Error) => void;
}

export class OkxWallet extends Connector {
/** {@inheritdoc Connector.provider} */
public provider: any;

private readonly options?: Parameters<typeof detectEthereumProvider>[0];
private eagerConnection?: Promise<void>;

constructor({ actions, options, onError }: OkxWalletConstructorArgs) {
super(actions, onError);
this.options = options;
}

private async isomorphicInitialize(): Promise<void> {
if (this.eagerConnection) return;

return (this.eagerConnection = import('@metamask/detect-provider').then(
async (m) => {
const windowAsAny = window as any;
const provider = windowAsAny.okxwallet;
if (provider) {
this.provider = provider;

this.provider.on(
'connect',
({ chainId }: ProviderConnectInfo): void => {
this.actions.update({ chainId: parseChainId(chainId) });
},
);

this.provider.on('disconnect', (error: ProviderRpcError): void => {
// 1013 indicates that MetaMask is attempting to reestablish the connection
// https://github.com/MetaMask/providers/releases/tag/v8.0.0
if (error.code === 1013) {
console.debug(
'MetaMask logged connection error 1013: "Try again later"',
);
return;
}
this.actions.resetState();
this.onError?.(error);
});

this.provider.on('chainChanged', (chainId: string): void => {
this.actions.update({ chainId: parseChainId(chainId) });
});

this.provider.on('accountsChanged', (accounts: string[]): void => {
if (accounts.length === 0) {
// handle this edge case by disconnecting
this.actions.resetState();
} else {
this.actions.update({ accounts });
}
});
}
},
));
}

/** {@inheritdoc Connector.connectEagerly} */
public async connectEagerly(): Promise<void> {
const cancelActivation = this.actions.startActivation();

try {
await this.isomorphicInitialize();
if (!this.provider) return cancelActivation();

// Wallets may resolve eth_chainId and hang on eth_accounts pending user interaction, which may include changing
// chains; they should be requested serially, with accounts first, so that the chainId can settle.
const accounts = (await this.provider.request({
method: 'eth_accounts',
})) as string[];
if (!accounts.length) throw new Error('No accounts returned');
const chainId = (await this.provider.request({
method: 'eth_chainId',
})) as string;
this.actions.update({ chainId: parseChainId(chainId), accounts });
} catch (error) {
console.debug('Could not connect eagerly', error);
// we should be able to use `cancelActivation` here, but on mobile, metamask emits a 'connect'
// event, meaning that chainId is updated, and cancelActivation doesn't work because an intermediary
// update has occurred, so we reset state instead
this.actions.resetState();
}
}

/**
* Initiates a connection.
*
* @param desiredChainIdOrChainParameters - If defined, indicates the desired chain to connect to. If the user is
* already connected to this chain, no additional steps will be taken. Otherwise, the user will be prompted to switch
* to the chain, if one of two conditions is met: either they already have it added in their extension, or the
* argument is of type AddEthereumChainParameter, in which case the user will be prompted to add the chain with the
* specified parameters first, before being prompted to switch.
*/
public async activate(
desiredChainIdOrChainParameters?: number | AddEthereumChainParameter,
): Promise<void> {
let cancelActivation: () => void;
if (!this.provider?.isConnected?.())
cancelActivation = this.actions.startActivation();

return this.isomorphicInitialize()
.then(async () => {
if (!this.provider) throw new NoOkxWalletError();

// Wallets may resolve eth_chainId and hang on eth_accounts pending user interaction, which may include changing
// chains; they should be requested serially, with accounts first, so that the chainId can settle.
const accounts = (await this.provider.request({
method: 'eth_requestAccounts',
})) as string[];
const chainId = (await this.provider.request({
method: 'eth_chainId',
})) as string;
const receivedChainId = parseChainId(chainId);
const desiredChainId =
typeof desiredChainIdOrChainParameters === 'number'
? desiredChainIdOrChainParameters
: desiredChainIdOrChainParameters?.chainId;

// if there's no desired chain, or it's equal to the received, update
if (!desiredChainId || receivedChainId === desiredChainId)
return this.actions.update({ chainId: receivedChainId, accounts });

const desiredChainIdHex = `0x${desiredChainId.toString(16)}`;

// if we're here, we can try to switch networks
return this.provider
.request({
method: 'wallet_switchEthereumChain',
params: [{ chainId: desiredChainIdHex }],
})
.catch((error: ProviderRpcError) => {
// https://github.com/MetaMask/metamask-mobile/issues/3312#issuecomment-1065923294
const errorCode =
(error.data as any)?.originalError?.code || error.code;

// 4902 indicates that the chain has not been added to MetaMask and wallet_addEthereumChain needs to be called
// https://docs.metamask.io/guide/rpc-api.html#wallet-switchethereumchain
if (
errorCode === 4902 &&
typeof desiredChainIdOrChainParameters !== 'number'
) {
if (!this.provider) throw new Error('No provider');
// if we're here, we can try to add a new network
return this.provider.request({
method: 'wallet_addEthereumChain',
params: [
{
...desiredChainIdOrChainParameters,
chainId: desiredChainIdHex,
},
],
});
}

throw error;
})
.then(() => this.activate(desiredChainId));
})
.catch((error) => {
cancelActivation?.();
throw error;
});
}

public async watchAsset({
address,
symbol,
decimals,
image,
}: WatchAssetParameters): Promise<true> {
if (!this.provider) throw new Error('No provider');

return this.provider
.request({
method: 'wallet_watchAsset',
params: {
type: 'ERC20', // Initially only supports ERC20, but eventually more!
options: {
address, // The address that the token is at.
symbol, // A ticker symbol or shorthand, up to 5 chars.
decimals, // The number of decimals in the token
image, // A string url of the token logo
},
},
})
.then((success: any) => {
if (!success) throw new Error('Rejected');
return true;
});
}
}
24 changes: 24 additions & 0 deletions src/connectors/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import BlockWalletIcon from 'assets/images/blockwalletIcon.svg';
import BraveWalletIcon from 'assets/images/braveWalletIcon.png';
import cypherDIcon from 'assets/images/cypherDIcon.png';
import BitKeepIcon from 'assets/images/bitkeep.png';
import OkxWalletIcon from 'assets/images/OKXWallet.svg';
import CoinbaseWalletIcon from 'assets/images/coinbaseWalletIcon.svg';
import WalletConnectIcon from 'assets/images/walletConnectIcon.svg';
import PhantomIcon from 'assets/images/wallets/phantomIconPurple.svg';
Expand All @@ -30,6 +31,7 @@ import { BlockWallet } from './BlockWallet';
import { BraveWallet } from './BraveWallet';
import { CypherD } from './CypherD';
import { isMobile } from 'react-device-detect';
import { OkxWallet } from './OkxWallet';

const POLLING_INTERVAL = 12000;

Expand All @@ -50,6 +52,7 @@ export enum ConnectionType {
BLOCKWALLET = 'BLOCKWALLET',
BRAVEWALLET = 'BRAVEWALLET',
CYPHERD = 'CYPHERD',
OKXWALLET = 'OKXWALLET',
}

export interface Connection {
Expand Down Expand Up @@ -216,6 +219,25 @@ export const bitKeepConnection: Connection = {
description: 'BitKeep browser extension.',
};

const [web3OkxWallet, web3OkxWalletHooks] = initializeConnector<OkxWallet>(
(actions) =>
new OkxWallet({
actions,
onError,
}),
);

export const okxWalletConnection: Connection = {
key: 'OkxWallet',
name: GlobalConst.walletName.OKXWALLET,
connector: web3OkxWallet,
hooks: web3OkxWalletHooks,
type: ConnectionType.OKXWALLET,
iconName: OkxWalletIcon,
color: '#E8831D',
description: 'OkxWallet browser extension.',
};

const [web3CypherD, web3CypherDHooks] = initializeConnector<CypherD>(
(actions) =>
new CypherD({
Expand Down Expand Up @@ -394,6 +416,7 @@ export function getConnections() {
zengoConnectConnection,
arkaneConnection,
bitKeepConnection,
okxWalletConnection,
]
: [
cypherDConnection,
Expand All @@ -408,5 +431,6 @@ export function getConnections() {
zengoConnectConnection,
arkaneConnection,
bitKeepConnection,
okxWalletConnection,
];
}
1 change: 1 addition & 0 deletions src/constants/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,7 @@ export const GlobalConst = {
WALLET_LINK: 'Coinbase Wallet',
WALLET_CONNECT: 'WalletConnect',
ZENGO_CONNECT: 'ZenGo',
OKXWALLET: 'OkxWallet',
},
};

Expand Down
3 changes: 3 additions & 0 deletions src/hooks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
gnosisSafeConnection,
metamaskConnection,
networkConnection,
okxWalletConnection,
phantomConnection,
trustWalletConnection,
walletConnectConnection,
Expand Down Expand Up @@ -94,6 +95,8 @@ export function useGetConnection() {
return braveWalletConnection;
case ConnectionType.CYPHERD:
return cypherDConnection;
case ConnectionType.OKXWALLET:
return okxWalletConnection;
default:
throw Error('unsupported connector');
}
Expand Down
1 change: 1 addition & 0 deletions src/hooks/useOrderedConnections.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const SELECTABLE_WALLETS = [
ConnectionType.BLOCKWALLET,
ConnectionType.BRAVEWALLET,
ConnectionType.CYPHERD,
ConnectionType.OKXWALLET,
];

export default function useOrderedConnections() {
Expand Down
Loading