Skip to content

Commit 85218aa

Browse files
author
ignaciosantise
committed
chore: migrated coinbase connector
1 parent 941ceaf commit 85218aa

File tree

2 files changed

+174
-229
lines changed

2 files changed

+174
-229
lines changed

packages/coinbase-wagmi/src/index.ts

Lines changed: 148 additions & 209 deletions
Original file line numberDiff line numberDiff line change
@@ -1,238 +1,177 @@
1-
import { Connector, type ConnectorData, type WalletClient } from 'wagmi';
1+
import { ChainNotConfiguredError, createConnector } from 'wagmi';
22
import {
3-
createWalletClient,
4-
custom,
53
getAddress,
6-
type Address,
74
UserRejectedRequestError,
85
numberToHex,
96
ProviderRpcError,
10-
SwitchChainError,
11-
type Chain
7+
SwitchChainError
128
} from 'viem';
139
import { WalletMobileSDKEVMProvider, configure } from '@coinbase/wallet-mobile-sdk';
1410
import type { WalletMobileSDKProviderOptions } from '@coinbase/wallet-mobile-sdk/build/WalletMobileSDKEVMProvider';
1511

16-
const ADD_ETH_CHAIN_METHOD = 'wallet_addEthereumChain';
17-
const SWITCH_ETH_CHAIN_METHOD = 'wallet_switchEthereumChain';
18-
19-
type CoinbaseConnectorOptions = WalletMobileSDKProviderOptions & {
12+
type CoinbaseConnectorParameters = WalletMobileSDKProviderOptions & {
2013
redirect: string;
2114
};
2215

23-
export class CoinbaseConnector extends Connector<
24-
WalletMobileSDKEVMProvider,
25-
CoinbaseConnectorOptions
26-
> {
27-
readonly id = 'coinbaseWallet';
28-
readonly name = 'Coinbase Wallet';
29-
readonly ready = true;
30-
31-
private _provider?: WalletMobileSDKEVMProvider;
32-
private _initProviderPromise?: Promise<void>;
33-
34-
constructor(config: { chains?: Chain[]; options: CoinbaseConnectorOptions }) {
35-
super(config);
36-
this._createProvider();
37-
}
38-
39-
override connect = async (
40-
config?: { chainId?: number | undefined } | undefined
41-
): Promise<Required<ConnectorData>> => {
42-
try {
43-
await this._setupListeners();
16+
type Provider = WalletMobileSDKEVMProvider;
17+
18+
coinbaseConnector.type = 'coinbaseWallet' as const;
19+
export function coinbaseConnector(parameters: CoinbaseConnectorParameters) {
20+
let _provider: Provider;
21+
22+
return createConnector<Provider>(config => ({
23+
id: 'coinbaseWallet',
24+
name: 'Coinbase Wallet',
25+
type: coinbaseConnector.type,
26+
async setup() {},
27+
async connect({ chainId } = {}) {
28+
try {
29+
const provider = await this.getProvider();
30+
let accounts;
31+
const isConnected = provider.connected;
32+
33+
if (!isConnected) {
34+
accounts = (
35+
(await provider.request({
36+
method: 'eth_requestAccounts'
37+
})) as string[]
38+
).map(getAddress);
39+
} else {
40+
accounts = provider.selectedAddress ? [getAddress(provider.selectedAddress)] : [];
41+
}
42+
43+
provider.on('accountsChanged', this.onAccountsChanged);
44+
provider.on('chainChanged', this.onChainChanged);
45+
provider.on('disconnect', this.onDisconnect.bind(this));
46+
47+
// Switch to chain if provided
48+
let currentChainId = await this.getChainId();
49+
if (chainId && currentChainId !== chainId) {
50+
const chain = await this.switchChain!({ chainId }).catch(() => ({
51+
id: currentChainId
52+
}));
53+
currentChainId = chain?.id ?? currentChainId;
54+
}
55+
56+
return { accounts, chainId: currentChainId };
57+
} catch (error) {
58+
if (/(Error error 0|User rejected the request)/i.test((error as Error).message))
59+
throw new UserRejectedRequestError(error as Error);
60+
61+
if (/(Error error 5|Could not open wallet)/i.test((error as Error).message))
62+
throw new Error(`Wallet not found. SDK Error: ${(error as Error).message}`);
63+
64+
throw error;
65+
}
66+
},
67+
async disconnect() {
4468
const provider = await this.getProvider();
4569

46-
const isConnected = provider.connected;
70+
provider.removeListener('accountsChanged', this.onAccountsChanged);
71+
provider.removeListener('chainChanged', this.onChainChanged);
72+
provider.removeListener('disconnect', this.onDisconnect.bind(this));
4773

48-
if (!isConnected) {
49-
await provider.request({
50-
method: 'eth_requestAccounts',
51-
params: []
74+
provider.disconnect();
75+
},
76+
async getAccounts() {
77+
const provider = await this.getProvider();
78+
79+
return (
80+
await provider.request<string[]>({
81+
method: 'eth_accounts'
82+
})
83+
).map(getAddress);
84+
},
85+
async getChainId() {
86+
const provider = await this.getProvider();
87+
88+
return Number(provider.chainId);
89+
},
90+
async getProvider({ chainId } = {}) {
91+
function initProvider() {
92+
configure({
93+
callbackURL: new URL(parameters.redirect),
94+
hostURL: new URL('https://wallet.coinbase.com/wsegue'),
95+
hostPackageName: 'org.toshi'
5296
});
97+
98+
return new WalletMobileSDKEVMProvider({ ...parameters });
99+
}
100+
101+
if (!_provider) {
102+
_provider = initProvider();
53103
}
54104

55-
const address = provider.selectedAddress!;
56-
const chainId = config?.chainId;
105+
if (chainId) {
106+
await this.switchChain?.({ chainId });
107+
}
57108

58-
this.emit('message', { type: 'connecting' });
109+
return _provider;
110+
},
111+
async isAuthorized() {
112+
try {
113+
const accounts = await this.getAccounts();
59114

60-
// Switch to chain if provided
61-
let id = await this.getChainId();
62-
let unsupported = this.isChainUnsupported(id);
63-
if (chainId && id !== chainId) {
64-
const chain = await this.switchChain(chainId);
65-
id = chain.id;
66-
unsupported = this.isChainUnsupported(id);
115+
return !!accounts.length;
116+
} catch {
117+
return false;
67118
}
119+
},
120+
async switchChain({ chainId }) {
121+
const chain = config.chains.find(c => c.id === chainId);
122+
if (!chain) throw new SwitchChainError(new ChainNotConfiguredError());
68123

69-
return {
70-
account: address as `0x${string}`,
71-
chain: { id, unsupported }
72-
};
73-
} catch (error) {
74-
if (/(Error error 0|User rejected the request)/i.test((error as Error).message))
75-
throw new UserRejectedRequestError(error as Error);
124+
const provider = await this.getProvider();
125+
const chainId_ = numberToHex(chain.id);
76126

77-
if (/(Error error 5|Could not open wallet)/i.test((error as Error).message))
78-
throw new Error(`Wallet not found. SDK Error: ${(error as Error).message}`);
127+
try {
128+
await provider.request({
129+
method: 'wallet_switchEthereumChain',
130+
params: [{ chainId: chainId_ }]
131+
});
79132

80-
throw error;
81-
}
82-
};
83-
84-
override disconnect = async (): Promise<void> => {
85-
if (!this._provider) return;
86-
87-
const provider = await this.getProvider();
88-
this._removeListeners();
89-
provider.disconnect();
90-
};
91-
92-
override async getAccount(): Promise<`0x${string}`> {
93-
const provider = await this.getProvider();
94-
const accounts = await provider.request<Address[]>({
95-
method: 'eth_accounts'
96-
});
97-
98-
return getAddress(accounts[0] as string);
99-
}
100-
101-
override getChainId = async (): Promise<number> => {
102-
const provider = await this.getProvider();
103-
104-
return this._normalizeChainId(provider.chainId);
105-
};
106-
107-
override getProvider = async ({ chainId }: { chainId?: number } = {}) => {
108-
if (!this._provider) await this._createProvider();
109-
if (chainId) await this.switchChain(chainId);
110-
111-
return this._provider!;
112-
};
113-
114-
override getWalletClient = async ({
115-
chainId
116-
}: { chainId?: number } = {}): Promise<WalletClient> => {
117-
const [provider, account] = await Promise.all([this.getProvider(), this.getAccount()]);
118-
const chain = this.chains.find(x => x.id === chainId);
119-
if (!provider) throw new Error('provider is required.');
120-
121-
// @ts-ignore
122-
return createWalletClient({
123-
account,
124-
chain,
125-
transport: custom(provider)
126-
});
127-
};
128-
129-
override isAuthorized = async (): Promise<boolean> => {
130-
try {
131-
const account = await this.getAccount();
132-
133-
return !!account;
134-
} catch {
135-
return false;
136-
}
137-
};
138-
139-
override switchChain = async (chainId: number) => {
140-
const provider = await this.getProvider();
141-
const id = numberToHex(chainId);
142-
const chain = this.chains.find(_chain => _chain.id === chainId);
143-
if (!chain)
144-
throw new SwitchChainError(
145-
new Error(`Chain "${chainId}" not configured for connector "${this.id}".`)
146-
);
147-
148-
try {
149-
await provider.request({
150-
method: SWITCH_ETH_CHAIN_METHOD,
151-
params: [{ chainId: id }]
152-
});
153-
154-
return chain;
155-
} catch (error) {
156-
// Indicates chain is not added to provider
157-
if ((error as ProviderRpcError).code === 4902) {
158-
try {
159-
await provider.request({
160-
method: ADD_ETH_CHAIN_METHOD,
161-
params: [
162-
{
163-
chainId: id,
164-
chainName: chain.name,
165-
nativeCurrency: chain.nativeCurrency,
166-
rpcUrls: [chain.rpcUrls.public?.http[0] ?? ''],
167-
blockExplorerUrls: this.getBlockExplorerUrls(chain)
168-
}
169-
]
170-
});
171-
172-
return chain;
173-
} catch (e) {
174-
throw new UserRejectedRequestError(e as Error);
133+
return chain;
134+
} catch (error) {
135+
// Indicates chain is not added to provider
136+
if ((error as ProviderRpcError).code === 4902) {
137+
try {
138+
await provider.request({
139+
method: 'wallet_addEthereumChain',
140+
params: [
141+
{
142+
chainId: chainId_,
143+
chainName: chain.name,
144+
nativeCurrency: chain.nativeCurrency,
145+
rpcUrls: [chain.rpcUrls.default?.http[0] ?? ''],
146+
blockExplorerUrls: [chain.blockExplorers?.default.url]
147+
}
148+
]
149+
});
150+
151+
return chain;
152+
} catch (e) {
153+
throw new UserRejectedRequestError(e as Error);
154+
}
175155
}
156+
157+
throw new SwitchChainError(error as Error);
176158
}
159+
},
160+
onAccountsChanged(accounts) {
161+
if (accounts.length === 0) config.emitter.emit('disconnect');
162+
else config.emitter.emit('change', { accounts: accounts.map(getAddress) });
163+
},
164+
onChainChanged(chain) {
165+
const chainId = Number(chain);
166+
config.emitter.emit('change', { chainId });
167+
},
168+
async onDisconnect(_error) {
169+
config.emitter.emit('disconnect');
177170

178-
throw new SwitchChainError(error as Error);
179-
}
180-
};
181-
182-
protected override onAccountsChanged = (accounts: `0x${string}`[]): void => {
183-
if (accounts.length === 0) this.emit('disconnect');
184-
else this.emit('change', { account: getAddress(accounts[0] as string) });
185-
};
186-
187-
protected override onChainChanged = (chain: string | number): void => {
188-
const id = Number(chain);
189-
const unsupported = this.isChainUnsupported(id);
190-
this.emit('change', { chain: { id, unsupported } });
191-
};
192-
193-
protected override onDisconnect = (): void => {
194-
this.emit('disconnect');
195-
};
196-
197-
private async _createProvider() {
198-
if (!this._initProviderPromise) {
199-
this._initProviderPromise = this._initProvider();
171+
const provider = await this.getProvider();
172+
provider.removeListener('accountsChanged', this.onAccountsChanged);
173+
provider.removeListener('chainChanged', this.onChainChanged);
174+
provider.removeListener('disconnect', this.onDisconnect.bind(this));
200175
}
201-
202-
return this._initProviderPromise;
203-
}
204-
205-
private _initProvider = async () => {
206-
configure({
207-
callbackURL: new URL(this.options.redirect),
208-
hostURL: new URL('https://wallet.coinbase.com/wsegue'), // Don't change -> Coinbase url
209-
hostPackageName: 'org.toshi' // Don't change -> Coinbase wallet scheme
210-
});
211-
212-
this._provider = new WalletMobileSDKEVMProvider({ ...this.options });
213-
};
214-
215-
private _setupListeners = async () => {
216-
const provider = await this.getProvider();
217-
this._removeListeners();
218-
provider.on('accountsChanged', this.onAccountsChanged);
219-
provider.on('chainChanged', this.onChainChanged);
220-
provider.on('disconnect', this.onDisconnect);
221-
};
222-
223-
private _removeListeners = () => {
224-
if (!this._provider) return;
225-
226-
this._provider.removeListener('accountsChanged', this.onAccountsChanged);
227-
this._provider.removeListener('chainChanged', this.onChainChanged);
228-
this._provider.removeListener('disconnect', this.onDisconnect);
229-
};
230-
231-
private _normalizeChainId = (chainId: string | number | bigint) => {
232-
if (typeof chainId === 'string')
233-
return Number.parseInt(chainId, chainId.trim().substring(0, 2) === '0x' ? 16 : 10);
234-
if (typeof chainId === 'bigint') return Number(chainId);
235-
236-
return chainId;
237-
};
176+
}));
238177
}

0 commit comments

Comments
 (0)