diff --git a/.changeset/good-jokes-shave.md b/.changeset/good-jokes-shave.md new file mode 100644 index 0000000000..4fa4f99c30 --- /dev/null +++ b/.changeset/good-jokes-shave.md @@ -0,0 +1,29 @@ +--- +'@reown/appkit-controllers': patch +'@reown/appkit-scaffold-ui': patch +'pay-test-exchange': patch +'@reown/appkit-adapter-bitcoin': patch +'@reown/appkit-adapter-ethers': patch +'@reown/appkit-adapter-ethers5': patch +'@reown/appkit-adapter-solana': patch +'@reown/appkit-adapter-wagmi': 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-core': patch +'@reown/appkit-experimental': patch +'@reown/appkit-pay': patch +'@reown/appkit-polyfills': 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 +--- + +Improved "Fund Wallet" flow by adding validation checks from remote config diff --git a/packages/controllers/src/utils/ConstantsUtil.ts b/packages/controllers/src/utils/ConstantsUtil.ts index adbb4cb098..910e371a21 100644 --- a/packages/controllers/src/utils/ConstantsUtil.ts +++ b/packages/controllers/src/utils/ConstantsUtil.ts @@ -183,6 +183,10 @@ export const ConstantsUtil = { CommonConstantsUtil.CHAIN.EVM, CommonConstantsUtil.CHAIN.SOLANA ] as ChainNamespace[], + PAY_WITH_EXCHANGE_SUPPORTED_CHAIN_NAMESPACES: [ + CommonConstantsUtil.CHAIN.EVM, + CommonConstantsUtil.CHAIN.SOLANA + ] as ChainNamespace[], ACTIVITY_ENABLED_CHAIN_NAMESPACES: [CommonConstantsUtil.CHAIN.EVM] as ChainNamespace[], NATIVE_TOKEN_ADDRESS: { eip155: '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee', diff --git a/packages/scaffold-ui/src/partials/w3m-account-default-widget/index.ts b/packages/scaffold-ui/src/partials/w3m-account-default-widget/index.ts index eae288a8b9..8d89e4c715 100644 --- a/packages/scaffold-ui/src/partials/w3m-account-default-widget/index.ts +++ b/packages/scaffold-ui/src/partials/w3m-account-default-widget/index.ts @@ -163,14 +163,18 @@ export class W3mAccountDefaultWidget extends LitElement { return null } - const hasNetworkSupport = CoreConstantsUtil.ONRAMP_SUPPORTED_CHAIN_NAMESPACES.includes( + const isOnrampSupported = CoreConstantsUtil.ONRAMP_SUPPORTED_CHAIN_NAMESPACES.includes( this.namespace ) + const isPayWithExchangeSupported = + CoreConstantsUtil.PAY_WITH_EXCHANGE_SUPPORTED_CHAIN_NAMESPACES.includes(this.namespace) - const isOnrampEnabled = this.remoteFeatures?.onramp && hasNetworkSupport const isReceiveEnabled = Boolean(this.features?.receive) + const isOnrampEnabled = this.remoteFeatures?.onramp && isOnrampSupported + const isPayWithExchangeEnabled = + this.remoteFeatures?.payWithExchange && isPayWithExchangeSupported - if (!isOnrampEnabled && !isReceiveEnabled) { + if (!isOnrampEnabled && !isReceiveEnabled && !isPayWithExchangeEnabled) { return null } diff --git a/packages/scaffold-ui/src/partials/w3m-account-wallet-features-widget/index.ts b/packages/scaffold-ui/src/partials/w3m-account-wallet-features-widget/index.ts index de6526532a..415a519f79 100644 --- a/packages/scaffold-ui/src/partials/w3m-account-wallet-features-widget/index.ts +++ b/packages/scaffold-ui/src/partials/w3m-account-wallet-features-widget/index.ts @@ -183,10 +183,22 @@ export class W3mAccountWalletFeaturesWidget extends LitElement { } private fundWalletTemplate() { - const isOnrampEnabled = this.remoteFeatures?.onramp + if (!this.namespace) { + return null + } + + const isOnrampSupported = CoreConstantsUtil.ONRAMP_SUPPORTED_CHAIN_NAMESPACES.includes( + this.namespace + ) + const isPayWithExchangeSupported = + CoreConstantsUtil.PAY_WITH_EXCHANGE_SUPPORTED_CHAIN_NAMESPACES.includes(this.namespace) + const isReceiveEnabled = this.features?.receive + const isOnrampEnabled = this.remoteFeatures?.onramp && isOnrampSupported + const isPayWithExchangeEnabled = + this.remoteFeatures?.payWithExchange && isPayWithExchangeSupported - if (!isOnrampEnabled && !isReceiveEnabled) { + if (!isOnrampEnabled && !isReceiveEnabled && !isPayWithExchangeEnabled) { return null } diff --git a/packages/scaffold-ui/src/views/w3m-deposit-from-exchange-view/index.ts b/packages/scaffold-ui/src/views/w3m-deposit-from-exchange-view/index.ts index ca04f8a5d3..3265365d3b 100644 --- a/packages/scaffold-ui/src/views/w3m-deposit-from-exchange-view/index.ts +++ b/packages/scaffold-ui/src/views/w3m-deposit-from-exchange-view/index.ts @@ -75,6 +75,18 @@ export class W3mDepositFromExchangeView extends LitElement { public override firstUpdated() { ExchangeController.fetchExchanges() ExchangeController.fetchTokenPrice() + + if (this.network) { + ExchangeController.setPaymentAsset({ + network: `${this.network.chainNamespace}:${this.network.id}`, + asset: 'native', + metadata: { + name: this.network.nativeCurrency.name, + symbol: this.network.nativeCurrency.symbol, + decimals: this.network.nativeCurrency.decimals + } + }) + } } // -- Render -------------------------------------------- // diff --git a/packages/scaffold-ui/src/views/w3m-fund-wallet-view/index.ts b/packages/scaffold-ui/src/views/w3m-fund-wallet-view/index.ts index e90eeb576b..60f3caf497 100644 --- a/packages/scaffold-ui/src/views/w3m-fund-wallet-view/index.ts +++ b/packages/scaffold-ui/src/views/w3m-fund-wallet-view/index.ts @@ -79,9 +79,15 @@ export class W3mFundWalletView extends LitElement { } private depositFromExchangeTemplate() { - const isDepositFromExchangeEnabled = this.remoteFeatures?.payWithExchange + if (!this.namespace) { + return null + } + + const isPayWithExchangeEnabled = + this.remoteFeatures?.payWithExchange && + CoreConstantsUtil.PAY_WITH_EXCHANGE_SUPPORTED_CHAIN_NAMESPACES.includes(this.namespace) - if (!isDepositFromExchangeEnabled) { + if (!isPayWithExchangeEnabled) { return null } diff --git a/packages/scaffold-ui/test/partials/w3m-account-default-widget.test.ts b/packages/scaffold-ui/test/partials/w3m-account-default-widget.test.ts index 5d2952e74d..52c80f69b8 100644 --- a/packages/scaffold-ui/test/partials/w3m-account-default-widget.test.ts +++ b/packages/scaffold-ui/test/partials/w3m-account-default-widget.test.ts @@ -197,6 +197,28 @@ describe('W3mAccountDefaultWidget', () => { expect(swapButton).not.toBeNull() expect(sendButton).toBeNull() }) + + it('should not show fund wallet if payWithExchange, onramp and receive are disabled', async () => { + vi.spyOn(OptionsController, 'state', 'get').mockReturnValue({ + ...OptionsController.state, + features: { + swaps: true, + receive: false + }, + remoteFeatures: { + payWithExchange: false, + onramp: false + } + }) + + const element: W3mAccountDefaultWidget = await fixture( + html`` + ) + + const fundWalletButton = HelpersUtil.getByTestId(element, FUND_WALLET_BUTTON_TEST_ID) + + expect(fundWalletButton).toBeNull() + }) }) describe('solana wallet features', () => { @@ -281,6 +303,28 @@ describe('W3mAccountDefaultWidget', () => { expect(swapButton).toBeNull() expect(sendButton).toBeNull() }) + + it('should not show fund wallet if payWithExchange, onramp and receive are disabled', async () => { + vi.spyOn(OptionsController, 'state', 'get').mockReturnValue({ + ...OptionsController.state, + features: { + swaps: true, + receive: false + }, + remoteFeatures: { + payWithExchange: false, + onramp: false + } + }) + + const element: W3mAccountDefaultWidget = await fixture( + html`` + ) + + const fundWalletButton = HelpersUtil.getByTestId(element, FUND_WALLET_BUTTON_TEST_ID) + + expect(fundWalletButton).toBeNull() + }) }) describe('bitcoin wallet features', () => { diff --git a/packages/scaffold-ui/test/partials/w3m-account-wallet-features-widget.test.ts b/packages/scaffold-ui/test/partials/w3m-account-wallet-features-widget.test.ts index 265db69d5a..d31beac957 100644 --- a/packages/scaffold-ui/test/partials/w3m-account-wallet-features-widget.test.ts +++ b/packages/scaffold-ui/test/partials/w3m-account-wallet-features-widget.test.ts @@ -294,6 +294,31 @@ describe('wallet features visibility', () => { expect(HelpersUtil.getByTestId(element, 'wallet-features-swaps-button')).not.toBeNull() expect(HelpersUtil.getByTestId(element, 'wallet-features-send-button')).toBeNull() }) + + it('should not show fund wallet if payWithExchange, onramp and receive are disabled', async () => { + vi.spyOn(OptionsController, 'state', 'get').mockReturnValue({ + ...OptionsController.state, + features: { + swaps: true, + receive: false + }, + remoteFeatures: { + payWithExchange: false, + onramp: false + } + }) + + const element: W3mAccountWalletFeaturesWidget = await fixture( + html`` + ) + + const fundWalletButton = HelpersUtil.getByTestId( + element, + 'wallet-features-fund-wallet-button' + ) + + expect(fundWalletButton).toBeNull() + }) }) describe('solana wallet features', () => { @@ -403,6 +428,31 @@ describe('wallet features visibility', () => { expect(HelpersUtil.getByTestId(element, 'wallet-features-swaps-button')).toBeNull() expect(HelpersUtil.getByTestId(element, 'wallet-features-send-button')).toBeNull() }) + + it('should not show fund wallet if payWithExchange, onramp and receive are disabled', async () => { + vi.spyOn(OptionsController, 'state', 'get').mockReturnValue({ + ...OptionsController.state, + features: { + swaps: true, + receive: false + }, + remoteFeatures: { + payWithExchange: false, + onramp: false + } + }) + + const element: W3mAccountWalletFeaturesWidget = await fixture( + html`` + ) + + const fundWalletButton = HelpersUtil.getByTestId( + element, + 'wallet-features-fund-wallet-button' + ) + + expect(fundWalletButton).toBeNull() + }) }) describe('bitcoin wallet features', () => { diff --git a/packages/scaffold-ui/test/views/w3m-deposit-from-exchange-view.test.ts b/packages/scaffold-ui/test/views/w3m-deposit-from-exchange-view.test.ts index c48fa10443..75fef006b5 100644 --- a/packages/scaffold-ui/test/views/w3m-deposit-from-exchange-view.test.ts +++ b/packages/scaffold-ui/test/views/w3m-deposit-from-exchange-view.test.ts @@ -3,11 +3,36 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import { html } from 'lit' +import type { CaipNetwork } from '@reown/appkit-common' import { ChainController, ExchangeController } from '@reown/appkit-controllers' import { W3mDepositFromExchangeView } from '../../src/views/w3m-deposit-from-exchange-view' import { HelpersUtil } from '../utils/HelpersUtil' +const mockMainnet = { + id: '1', + name: 'Mainnet', + chainNamespace: 'eip155', + nativeCurrency: { symbol: 'ETH', name: 'Ethereum', decimals: 18 }, + rpcUrls: { + default: { + http: ['https://mainnet.infura.io/v3/YOUR_INFURA_API_KEY'] + } + } +} + +const mockSolanaMainnet = { + id: '5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp', + name: 'Solana Mainnet', + chainNamespace: 'solana', + nativeCurrency: { symbol: 'SOL', name: 'Solana', decimals: 9 }, + rpcUrls: { + default: { + http: ['https://mainnet.infura.io/v3/YOUR_INFURA_API_KEY'] + } + } +} + describe('W3mDepositFromExchangeView', () => { beforeEach(() => { vi.restoreAllMocks() @@ -17,6 +42,47 @@ describe('W3mDepositFromExchangeView', () => { vi.clearAllMocks() }) + it('should set network native currency as payment asset', async () => { + vi.spyOn(ChainController, 'state', 'get').mockReturnValue({ + ...ChainController.state, + activeCaipNetwork: mockSolanaMainnet as unknown as CaipNetwork + }) + + const setPaymentAssetSpy = vi.spyOn(ExchangeController, 'setPaymentAsset') + + await fixture(html``) + + expect(setPaymentAssetSpy).toHaveBeenCalledWith({ + network: 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp', + asset: 'native', + metadata: { + name: 'Solana', + symbol: 'SOL', + decimals: 9 + } + }) + expect(setPaymentAssetSpy).toHaveBeenCalledTimes(1) + + // Test mainnet + vi.spyOn(ChainController, 'state', 'get').mockReturnValue({ + ...ChainController.state, + activeCaipNetwork: mockMainnet as unknown as CaipNetwork + }) + + await fixture(html``) + + expect(setPaymentAssetSpy).toHaveBeenCalledWith({ + network: 'eip155:1', + asset: 'native', + metadata: { + name: 'Ethereum', + symbol: 'ETH', + decimals: 18 + } + }) + expect(setPaymentAssetSpy).toHaveBeenCalledTimes(2) + }) + it('renders exchanges and asset chip, and calls controller on interactions', async () => { // Mock active network symbol for the asset chip vi.spyOn(ChainController, 'state', 'get').mockReturnValue({ diff --git a/packages/scaffold-ui/test/views/w3m-fund-wallet-view.test.ts b/packages/scaffold-ui/test/views/w3m-fund-wallet-view.test.ts index 29284a552e..dff9cbad87 100644 --- a/packages/scaffold-ui/test/views/w3m-fund-wallet-view.test.ts +++ b/packages/scaffold-ui/test/views/w3m-fund-wallet-view.test.ts @@ -9,6 +9,9 @@ import { ChainController, OptionsController, RouterController } from '@reown/app import { W3mFundWalletView } from '../../src/views/w3m-fund-wallet-view' import { HelpersUtil } from '../utils/HelpersUtil' +// -- Constants ----------------------------------------- // +const DEPOSIT_FROM_EXCHANGE_BUTTON_TEST_ID = 'wallet-features-deposit-from-exchange-button' + describe('W3mFundWalletView', () => { beforeEach(() => { vi.restoreAllMocks() @@ -219,4 +222,81 @@ describe('W3mFundWalletView', () => { ) expect(receiveFundsButton).toBeFalsy() }) + + it('should not show deposit from exchange option when payWithExchange is disabled', async () => { + vi.spyOn(ChainController, 'state', 'get').mockReturnValue({ + ...ChainController.state, + activeChain: CommonConstantsUtil.CHAIN.EVM + }) + vi.spyOn(OptionsController, 'state', 'get').mockReturnValue({ + ...OptionsController.state, + remoteFeatures: { + payWithExchange: false + } + }) + + const element: W3mFundWalletView = await fixture( + html`` + ) + + await elementUpdated(element) + + const depositFromExchangeButton = HelpersUtil.getByTestId( + element, + DEPOSIT_FROM_EXCHANGE_BUTTON_TEST_ID + ) + expect(depositFromExchangeButton).toBeFalsy() + }) + + it('should show deposit from exchange option when payWithExchange is enabled', async () => { + vi.spyOn(ChainController, 'state', 'get').mockReturnValue({ + ...ChainController.state, + activeChain: CommonConstantsUtil.CHAIN.EVM + }) + vi.spyOn(OptionsController, 'state', 'get').mockReturnValue({ + ...OptionsController.state, + remoteFeatures: { + payWithExchange: true + } + }) + + const element: W3mFundWalletView = await fixture( + html`` + ) + + await elementUpdated(element) + + const depositFromExchangeButton = HelpersUtil.getByTestId( + element, + DEPOSIT_FROM_EXCHANGE_BUTTON_TEST_ID + ) + + expect(depositFromExchangeButton).toBeTruthy() + }) + + it('should not show deposit from exchange option when chain is not supported and payWithExchange is enabled', async () => { + vi.spyOn(ChainController, 'state', 'get').mockReturnValue({ + ...ChainController.state, + activeChain: CommonConstantsUtil.CHAIN.BITCOIN + }) + vi.spyOn(OptionsController, 'state', 'get').mockReturnValue({ + ...OptionsController.state, + remoteFeatures: { + payWithExchange: true + } + }) + + const element: W3mFundWalletView = await fixture( + html`` + ) + + await elementUpdated(element) + + const depositFromExchangeButton = HelpersUtil.getByTestId( + element, + DEPOSIT_FROM_EXCHANGE_BUTTON_TEST_ID + ) + + expect(depositFromExchangeButton).toBeNull() + }) })