Skip to content
Open
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
29 changes: 29 additions & 0 deletions .changeset/large-lizards-dance.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
---
'@reown/appkit-adapter-ethers5': patch
'@reown/appkit-adapter-ethers': patch
'@reown/appkit-adapter-wagmi': patch
'pay-test-exchange': patch
'@reown/appkit-adapter-bitcoin': patch
'@reown/appkit-adapter-solana': patch
'@reown/appkit': patch
'@reown/appkit-utils': patch
'@reown/appkit-cdn': patch
'@reown/appkit-cli': patch
'@reown/appkit-codemod': patch
'@reown/appkit-common': patch
'@reown/appkit-controllers': patch
'@reown/appkit-core': patch
'@reown/appkit-experimental': patch
'@reown/appkit-pay': patch
'@reown/appkit-polyfills': patch
'@reown/appkit-scaffold-ui': patch
'@reown/appkit-siwe': patch
'@reown/appkit-siwx': patch
'@reown/appkit-testing': patch
'@reown/appkit-ui': patch
'@reown/appkit-universal-connector': patch
'@reown/appkit-wallet': patch
'@reown/appkit-wallet-button': patch
---

Lazy load Coinbase SDK
78 changes: 47 additions & 31 deletions packages/adapters/ethers/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ export class EthersAdapter extends AdapterBlueprint {
private ethersConfig?: ProviderType
private balancePromises: Record<string, Promise<AdapterBlueprint.GetBalanceResult>> = {}
private universalProvider?: UniversalProvider
private options?: AppKitOptions

constructor() {
super({
Expand All @@ -45,6 +46,34 @@ export class EthersAdapter extends AdapterBlueprint {
})
}

private async getCoinbaseProviderLazy(): Promise<Provider | undefined> {
try {
const { createCoinbaseWalletSDK } = await import('@coinbase/wallet-sdk')
if (typeof window === 'undefined') {
return undefined
}

const networks = this.getCaipNetworks()?.filter(
n => n.chainNamespace === CommonConstantsUtil.CHAIN.EVM
)
const coinbaseSdk = createCoinbaseWalletSDK({
appName: this.options?.metadata?.name,
appLogoUrl: this.options?.metadata?.icons?.[0],
appChainIds: networks?.map(n => n.id as number) || [1, 84532],
preference: {
options: this.options?.coinbasePreference ?? 'all'
}
})

return coinbaseSdk.getProvider() as unknown as Provider
} catch (error) {
// eslint-disable-next-line no-console
console.error('Failed to import Coinbase Wallet SDK:', error)

return undefined
}
}

private async createEthersConfig(options: AppKitOptions) {
if (!options.metadata) {
return undefined
Expand Down Expand Up @@ -85,43 +114,15 @@ export class EthersAdapter extends AdapterBlueprint {
return provider
}

async function getCoinbaseProvider() {
try {
const { createCoinbaseWalletSDK } = await import('@coinbase/wallet-sdk')
if (typeof window === 'undefined') {
return undefined
}

const coinbaseSdk = createCoinbaseWalletSDK({
appName: options?.metadata?.name,
appLogoUrl: options?.metadata?.icons[0],
appChainIds: options.networks?.map(caipNetwork => caipNetwork.id as number) || [1, 84532],
preference: {
options: options.coinbasePreference ?? 'all'
}
})

return coinbaseSdk.getProvider()
} catch (error) {
// eslint-disable-next-line no-console
console.error('Failed to import Coinbase Wallet SDK:', error)

return undefined
}
}

const providers: ProviderType = { metadata: options.metadata }

if (options.enableInjected !== false) {
providers.injected = getInjectedProvider()
}

if (options.enableCoinbase !== false) {
const coinbaseProvider = await getCoinbaseProvider()

if (coinbaseProvider) {
providers.coinbase = coinbaseProvider
}
// Register Coinbase connector without instantiating SDK yet (lazy-load on connect)
providers.coinbase = undefined
Comment on lines +124 to +125
Copy link

Copilot AI Oct 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Setting providers.coinbase to undefined may cause issues downstream. Consider using a placeholder object or lazy provider wrapper instead of undefined to maintain consistent provider interface expectations.

Suggested change
// Register Coinbase connector without instantiating SDK yet (lazy-load on connect)
providers.coinbase = undefined
// Register Coinbase connector with a lazy provider getter function
providers.coinbase = this.getCoinbaseProviderLazy.bind(this)

Copilot uses AI. Check for mistakes.
}

if (CoreHelperUtil.isSafeApp()) {
Expand Down Expand Up @@ -277,6 +278,7 @@ export class EthersAdapter extends AdapterBlueprint {
}

override async syncConnectors(options: AppKitOptions): Promise<void> {
this.options = options
this.ethersConfig = await this.createEthersConfig(options)

if (this.ethersConfig?.EIP6963) {
Expand Down Expand Up @@ -450,7 +452,21 @@ export class EthersAdapter extends AdapterBlueprint {
}
}

const selectedProvider = connector?.provider as Provider
let selectedProvider = connector?.provider as Provider | undefined

// Lazy-load Coinbase SDK only when user selects it
if (
!selectedProvider &&
(connector?.id === CommonConstantsUtil.CONNECTOR_ID.COINBASE_SDK ||
connector?.id === 'coinbase')
) {
selectedProvider = await this.getCoinbaseProviderLazy()
if (selectedProvider) {
// Persist on the connector so listeners work normally
// eslint-disable-next-line no-param-reassign
connector.provider = selectedProvider
Comment on lines +466 to +467
Copy link

Copilot AI Oct 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Directly mutating the connector parameter violates immutability principles. Consider using a more functional approach or documenting why this mutation is necessary for the architecture.

Suggested change
// eslint-disable-next-line no-param-reassign
connector.provider = selectedProvider
// Use a new connector object to preserve immutability
const updatedConnector = { ...connector, provider: selectedProvider }
connector = updatedConnector

Copilot uses AI. Check for mistakes.
}
}

if (!selectedProvider) {
throw new Error('Provider not found')
Expand Down
6 changes: 4 additions & 2 deletions packages/adapters/ethers/src/tests/client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1236,7 +1236,9 @@ describe('EthersAdapter', () => {
}
})

expect(providers?.coinbase).toBeDefined()
// Coinbase is lazy-loaded, so it's undefined initially but the key exists
expect(providers).toHaveProperty('coinbase')
expect(providers?.coinbase).toBeUndefined()
})

it('should create Ethers config without coinbase provider if disabled', async () => {
Expand All @@ -1252,7 +1254,7 @@ describe('EthersAdapter', () => {
}
})

expect(providers?.coinbase).toBeUndefined()
expect(providers).not.toHaveProperty('coinbase')
})

it('should create Ethers config with safe provider if in iframe and ancestor is app.safe.global', async () => {
Expand Down
79 changes: 47 additions & 32 deletions packages/adapters/ethers5/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export class Ethers5Adapter extends AdapterBlueprint {
private ethersConfig?: ProviderType
private balancePromises: Record<string, Promise<AdapterBlueprint.GetBalanceResult>> = {}
private universalProvider?: UniversalProvider
private options?: AppKitOptions

constructor() {
super({
Expand All @@ -46,6 +47,34 @@ export class Ethers5Adapter extends AdapterBlueprint {
})
}

private async getCoinbaseProviderLazy(): Promise<Provider | undefined> {
try {
const { createCoinbaseWalletSDK } = await import('@coinbase/wallet-sdk')
if (typeof window === 'undefined') {
return undefined
}

const networks = this.getCaipNetworks()?.filter(
n => n.chainNamespace === CommonConstantsUtil.CHAIN.EVM
)
const coinbaseSdk = createCoinbaseWalletSDK({
appName: this.options?.metadata?.name,
appLogoUrl: this.options?.metadata?.icons?.[0],
appChainIds: networks?.map(n => n.id as number) || [1, 84532],
preference: {
options: this.options?.coinbasePreference ?? 'all'
}
})

return coinbaseSdk.getProvider() as unknown as Provider
} catch (error) {
// eslint-disable-next-line no-console
console.error('Failed to import Coinbase Wallet SDK:', error)

return undefined
}
}

private async createEthersConfig(options: AppKitOptions) {
if (!options.metadata) {
return undefined
Expand Down Expand Up @@ -87,44 +116,15 @@ export class Ethers5Adapter extends AdapterBlueprint {
return provider
}

async function getCoinbaseProvider() {
try {
const { createCoinbaseWalletSDK } = await import('@coinbase/wallet-sdk')

if (typeof window === 'undefined') {
return undefined
}

const coinbaseSdk = createCoinbaseWalletSDK({
appName: options?.metadata?.name,
appLogoUrl: options?.metadata?.icons[0],
appChainIds: options.networks?.map(caipNetwork => caipNetwork.id as number) || [1, 84532],
preference: {
options: options.coinbasePreference ?? 'all'
}
})

return coinbaseSdk.getProvider()
} catch (error) {
// eslint-disable-next-line no-console
console.error('Failed to import Coinbase Wallet SDK:', error)

return undefined
}
}

const providers: ProviderType = { metadata: options.metadata }

if (options.enableInjected !== false) {
providers.injected = getInjectedProvider()
}

if (options.enableCoinbase !== false) {
const coinbaseProvider = await getCoinbaseProvider()

if (coinbaseProvider) {
providers.coinbase = coinbaseProvider
}
// Register Coinbase connector without instantiating SDK yet (lazy-load on connect)
providers.coinbase = undefined
Comment on lines +126 to +127
Copy link

Copilot AI Oct 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Setting providers.coinbase to undefined may cause issues downstream. Consider using a placeholder object or lazy provider wrapper instead of undefined to maintain consistent provider interface expectations.

Suggested change
// Register Coinbase connector without instantiating SDK yet (lazy-load on connect)
providers.coinbase = undefined
// Register Coinbase connector with a lazy provider wrapper (lazy-load on connect)
providers.coinbase = {
getProvider: () => this.getCoinbaseProviderLazy()
}

Copilot uses AI. Check for mistakes.
}

if (CoreHelperUtil.isSafeApp()) {
Expand Down Expand Up @@ -280,6 +280,7 @@ export class Ethers5Adapter extends AdapterBlueprint {
}

override async syncConnectors(options: AppKitOptions): Promise<void> {
this.options = options
this.ethersConfig = await this.createEthersConfig(options)

if (this.ethersConfig?.EIP6963) {
Expand Down Expand Up @@ -453,7 +454,21 @@ export class Ethers5Adapter extends AdapterBlueprint {
}
}

const selectedProvider = connector?.provider as Provider
let selectedProvider = connector?.provider as Provider | undefined

// Lazy-load Coinbase SDK only when user selects it
if (
!selectedProvider &&
(connector?.id === CommonConstantsUtil.CONNECTOR_ID.COINBASE_SDK ||
connector?.id === 'coinbase')
) {
selectedProvider = await this.getCoinbaseProviderLazy()
if (selectedProvider) {
// Persist on the connector so listeners work normally
// eslint-disable-next-line no-param-reassign
connector.provider = selectedProvider
Comment on lines +467 to +469
Copy link

Copilot AI Oct 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Directly mutating the connector parameter violates immutability principles. Consider using a more functional approach or documenting why this mutation is necessary for the architecture.

Suggested change
// Persist on the connector so listeners work normally
// eslint-disable-next-line no-param-reassign
connector.provider = selectedProvider
// Create a new connector object with the updated provider to preserve immutability
connector = { ...connector, provider: selectedProvider }

Copilot uses AI. Check for mistakes.
}
}

if (!selectedProvider) {
throw new Error('Provider not found')
Expand Down
13 changes: 11 additions & 2 deletions packages/adapters/wagmi/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -481,7 +481,11 @@ export class WagmiAdapter extends AdapterBlueprint {
return
}

const provider = (await connector.getProvider().catch(() => undefined)) as Provider | undefined
// Lazy-load Coinbase SDK: avoid calling getProvider() during setup
let provider: Provider | undefined = undefined
if (connector.id !== CommonConstantsUtil.CONNECTOR_ID.COINBASE_SDK) {
provider = (await connector.getProvider().catch(() => undefined)) as Provider | undefined
}

this.addConnector({
id: connector.id,
Expand Down Expand Up @@ -663,10 +667,15 @@ export class WagmiAdapter extends AdapterBlueprint {
socialUri
})

// Lazy-load Coinbase provider only when needed for the return result
const resolvedProvider =
(provider as Provider) ??
((await connector.getProvider().catch(() => undefined)) as Provider | undefined)

return {
address: this.toChecksummedAddress(res.accounts[0]),
chainId: res.chainId,
provider: provider as Provider,
provider: resolvedProvider,
type: type as ConnectorType,
id
}
Expand Down