From 3b0eafb71eeb61e123d5131ec34afcc13c83b5dd Mon Sep 17 00:00:00 2001 From: Enes Date: Wed, 7 Feb 2024 23:40:33 +0300 Subject: [PATCH] feat: coinbase pay sdk integration (#1487) Co-authored-by: Neel Ramachandran Co-authored-by: 0xAsimetriq Co-authored-by: tomiir Co-authored-by: Derek --- .vscode/settings.json | 3 +- .../wui-onramp-activity-item.stories.ts | 35 +++ apps/gallery/utils/PresetUtils.ts | 9 +- .../src/pages/library/ethers-email.tsx | 3 +- .../src/pages/library/ethers-siwe.tsx | 3 +- apps/laboratory/src/pages/library/ethers.tsx | 3 +- .../src/pages/library/wagmi-email.tsx | 3 +- .../src/pages/library/wagmi-siwe.tsx | 3 +- apps/laboratory/src/pages/library/wagmi.tsx | 3 +- dangerfile.ts | 25 +- packages/common/src/utils/TypeUtil.ts | 7 +- packages/core/index.ts | 3 + .../controllers/BlockchainApiController.ts | 41 ++- .../core/src/controllers/NetworkController.ts | 10 + .../core/src/controllers/OnRampController.ts | 41 +++ .../core/src/controllers/OptionsController.ts | 9 +- .../core/src/controllers/RouterController.ts | 23 +- .../src/controllers/TransactionsController.ts | 40 ++- packages/core/src/utils/ConstantsUtil.ts | 39 ++- packages/core/src/utils/CoreHelperUtil.ts | 23 +- packages/core/src/utils/FetchUtil.ts | 24 +- packages/core/src/utils/TypeUtil.ts | 39 +++ .../tests/controllers/OnRampController.ts | 20 ++ packages/scaffold/index.ts | 6 + packages/scaffold/src/client.ts | 5 + .../scaffold/src/modal/w3m-router/index.ts | 10 + .../scaffold/src/partials/w3m-header/index.ts | 7 +- .../w3m-onramp-providers-footer/index.ts | 53 ++++ .../w3m-onramp-providers-footer/styles.ts | 17 ++ .../views/w3m-account-settings-view/index.ts | 235 +++++++++++++++++ .../views/w3m-account-settings-view/styles.ts | 49 ++++ .../src/views/w3m-account-view/index.ts | 101 ++++---- .../src/views/w3m-account-view/styles.ts | 89 +++++++ .../views/w3m-buy-in-progress-view/index.ts | 236 ++++++++++++++++++ .../views/w3m-buy-in-progress-view/styles.ts | 84 +++++++ .../src/views/w3m-networks-view/index.ts | 3 +- .../views/w3m-onramp-activity-view/index.ts | 151 +++++++++++ .../views/w3m-onramp-activity-view/styles.ts | 19 ++ .../views/w3m-onramp-providers-view/index.ts | 114 +++++++++ .../src/views/w3m-transactions-view/index.ts | 40 ++- .../src/views/w3m-transactions-view/styles.ts | 1 + .../src/views/w3m-what-is-a-buy-view/index.ts | 39 +++ packages/ui/.eslintrc.json | 5 +- packages/ui/index.ts | 4 +- packages/ui/src/assets/svg/add.ts | 17 ++ .../ui/src/assets/svg/arrow-bottom-circle.ts | 13 + packages/ui/src/assets/svg/bank.ts | 16 ++ packages/ui/src/assets/svg/card.ts | 16 ++ packages/ui/src/assets/svg/checkmark.ts | 16 +- packages/ui/src/assets/svg/copy.ts | 15 +- packages/ui/src/assets/svg/plus.ts | 15 ++ .../ui/src/assets/svg/recycle-horizontal.ts | 11 + packages/ui/src/assets/svg/send.ts | 17 ++ .../ui/src/assets/svg/swapHorizontalMedium.ts | 18 ++ packages/ui/src/assets/visual/coinbase.ts | 13 + packages/ui/src/assets/visual/moonpay.ts | 24 ++ packages/ui/src/assets/visual/onramp-card.ts | 16 ++ packages/ui/src/assets/visual/paypal.ts | 32 +++ packages/ui/src/assets/visual/stripe.ts | 26 ++ packages/ui/src/components/wui-card/styles.ts | 2 +- packages/ui/src/components/wui-icon/index.ts | 18 +- packages/ui/src/components/wui-text/styles.ts | 17 +- .../ui/src/components/wui-visual/index.ts | 20 +- .../ui/src/components/wui-visual/styles.ts | 9 +- .../ui/src/composites/wui-button/styles.ts | 8 +- .../ui/src/composites/wui-icon-link/index.ts | 8 + .../ui/src/composites/wui-icon-link/styles.ts | 4 +- .../ui/src/composites/wui-list-item/index.ts | 6 +- .../wui-onramp-activity-item/index.ts | 83 ++++++ .../wui-onramp-activity-item/styles.ts | 56 +++++ .../wui-onramp-provider-item/index.ts | 80 ++++++ .../wui-onramp-provider-item/styles.ts | 56 +++++ .../ui/src/composites/wui-snackbar/styles.ts | 2 + .../wui-transaction-list-item/index.ts | 8 +- .../wui-transaction-list-item/styles.ts | 2 +- packages/ui/src/utils/JSXTypeUtil.ts | 8 +- packages/ui/src/utils/ThemeUtil.ts | 9 + packages/ui/src/utils/TransactionUtil.ts | 15 +- packages/ui/src/utils/TypeUtil.ts | 18 ++ packages/wagmi/src/client.ts | 8 +- .../wagmi/src/connectors/EmailConnector.ts | 8 - tsconfig.json | 2 +- 82 files changed, 2226 insertions(+), 163 deletions(-) create mode 100644 apps/gallery/stories/composites/wui-onramp-activity-item.stories.ts create mode 100644 packages/core/src/controllers/OnRampController.ts create mode 100644 packages/core/tests/controllers/OnRampController.ts create mode 100644 packages/scaffold/src/partials/w3m-onramp-providers-footer/index.ts create mode 100644 packages/scaffold/src/partials/w3m-onramp-providers-footer/styles.ts create mode 100644 packages/scaffold/src/views/w3m-account-settings-view/index.ts create mode 100644 packages/scaffold/src/views/w3m-account-settings-view/styles.ts create mode 100644 packages/scaffold/src/views/w3m-buy-in-progress-view/index.ts create mode 100644 packages/scaffold/src/views/w3m-buy-in-progress-view/styles.ts create mode 100644 packages/scaffold/src/views/w3m-onramp-activity-view/index.ts create mode 100644 packages/scaffold/src/views/w3m-onramp-activity-view/styles.ts create mode 100644 packages/scaffold/src/views/w3m-onramp-providers-view/index.ts create mode 100644 packages/scaffold/src/views/w3m-what-is-a-buy-view/index.ts create mode 100644 packages/ui/src/assets/svg/add.ts create mode 100644 packages/ui/src/assets/svg/arrow-bottom-circle.ts create mode 100644 packages/ui/src/assets/svg/bank.ts create mode 100644 packages/ui/src/assets/svg/card.ts create mode 100644 packages/ui/src/assets/svg/plus.ts create mode 100644 packages/ui/src/assets/svg/recycle-horizontal.ts create mode 100644 packages/ui/src/assets/svg/send.ts create mode 100644 packages/ui/src/assets/svg/swapHorizontalMedium.ts create mode 100644 packages/ui/src/assets/visual/coinbase.ts create mode 100644 packages/ui/src/assets/visual/moonpay.ts create mode 100644 packages/ui/src/assets/visual/onramp-card.ts create mode 100644 packages/ui/src/assets/visual/paypal.ts create mode 100644 packages/ui/src/assets/visual/stripe.ts create mode 100644 packages/ui/src/composites/wui-onramp-activity-item/index.ts create mode 100644 packages/ui/src/composites/wui-onramp-activity-item/styles.ts create mode 100644 packages/ui/src/composites/wui-onramp-provider-item/index.ts create mode 100644 packages/ui/src/composites/wui-onramp-provider-item/styles.ts diff --git a/.vscode/settings.json b/.vscode/settings.json index a2e24f55f8..8b555aebe4 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -14,7 +14,8 @@ "**/out": true, "**/dist": true, "**/.turbo": true, - "**/playwright-report": true + "**/playwright-report": true, + "**/coverage": true }, "javascript.updateImportsOnFileMove.enabled": "always", "typescript.tsdk": "node_modules/typescript/lib", diff --git a/apps/gallery/stories/composites/wui-onramp-activity-item.stories.ts b/apps/gallery/stories/composites/wui-onramp-activity-item.stories.ts new file mode 100644 index 0000000000..9120ef5820 --- /dev/null +++ b/apps/gallery/stories/composites/wui-onramp-activity-item.stories.ts @@ -0,0 +1,35 @@ +import type { Meta } from '@storybook/web-components' +import '@web3modal/ui/src/composites/wui-onramp-activity-item' +import type { WuiOnRampActivityItem } from '@web3modal/ui/src/composites/wui-onramp-activity-item' +import { html } from 'lit' +import '../../components/gallery-container' + +type Component = Meta + +export default { + title: 'Composites/wui-onramp-activity-item', + args: { + completed: true, + inProgress: false, + failed: false, + purchaseCurrency: 'USD', + purchaseValue: '1000', + date: '2 days ago' + } +} as Component + +export const Default: Component = { + render: args => html` + + + + ` +} diff --git a/apps/gallery/utils/PresetUtils.ts b/apps/gallery/utils/PresetUtils.ts index 2a36327263..18c187f7f5 100644 --- a/apps/gallery/utils/PresetUtils.ts +++ b/apps/gallery/utils/PresetUtils.ts @@ -54,7 +54,8 @@ export const textOptions: TextType[] = [ 'paragraph-700', 'large-500', 'large-600', - 'large-700' + 'large-700', + '2xl-500' ] export const textAlignOptions: TextAlign[] = ['center', 'left', 'right'] @@ -225,7 +226,11 @@ export const visualOptions: VisualType[] = [ 'nft', 'noun', 'profile', - 'system' + 'system', + 'coinbase', + 'stripe', + 'moonpay', + 'paypal' ] export const logoOptions: LogoType[] = [ diff --git a/apps/laboratory/src/pages/library/ethers-email.tsx b/apps/laboratory/src/pages/library/ethers-email.tsx index 22888621f4..452525b4d5 100644 --- a/apps/laboratory/src/pages/library/ethers-email.tsx +++ b/apps/laboratory/src/pages/library/ethers-email.tsx @@ -17,7 +17,8 @@ const modal = createWeb3Modal({ enableAnalytics: true, metadata: ConstantsUtil.Metadata, termsConditionsUrl: 'https://walletconnect.com/terms', - privacyPolicyUrl: 'https://walletconnect.com/privacy' + privacyPolicyUrl: 'https://walletconnect.com/privacy', + enableOnramp: true }) ThemeStore.setModal(modal) diff --git a/apps/laboratory/src/pages/library/ethers-siwe.tsx b/apps/laboratory/src/pages/library/ethers-siwe.tsx index 7d635594dc..aafc1d098f 100644 --- a/apps/laboratory/src/pages/library/ethers-siwe.tsx +++ b/apps/laboratory/src/pages/library/ethers-siwe.tsx @@ -17,7 +17,8 @@ const modal = createWeb3Modal({ projectId: ConstantsUtil.ProjectId, enableAnalytics: true, metadata: ConstantsUtil.Metadata, - siweConfig + siweConfig, + enableOnramp: true }) ThemeStore.setModal(modal) diff --git a/apps/laboratory/src/pages/library/ethers.tsx b/apps/laboratory/src/pages/library/ethers.tsx index f7c189c7c1..3f18bab049 100644 --- a/apps/laboratory/src/pages/library/ethers.tsx +++ b/apps/laboratory/src/pages/library/ethers.tsx @@ -26,7 +26,8 @@ const modal = createWeb3Modal({ desktop_link: 'https://react-wallet.walletconnect.com', webapp_link: 'https://react-wallet.walletconnect.com' } - ] + ], + enableOnramp: true }) ThemeStore.setModal(modal) diff --git a/apps/laboratory/src/pages/library/wagmi-email.tsx b/apps/laboratory/src/pages/library/wagmi-email.tsx index b2f92d882f..31639e261b 100644 --- a/apps/laboratory/src/pages/library/wagmi-email.tsx +++ b/apps/laboratory/src/pages/library/wagmi-email.tsx @@ -25,7 +25,8 @@ const modal = createWeb3Modal({ enableAnalytics: true, metadata: ConstantsUtil.Metadata, termsConditionsUrl: 'https://walletconnect.com/terms', - privacyPolicyUrl: 'https://walletconnect.com/privacy' + privacyPolicyUrl: 'https://walletconnect.com/privacy', + enableOnramp: true }) ThemeStore.setModal(modal) diff --git a/apps/laboratory/src/pages/library/wagmi-siwe.tsx b/apps/laboratory/src/pages/library/wagmi-siwe.tsx index 4d11ab7cf2..547a59fda6 100644 --- a/apps/laboratory/src/pages/library/wagmi-siwe.tsx +++ b/apps/laboratory/src/pages/library/wagmi-siwe.tsx @@ -24,7 +24,8 @@ const modal = createWeb3Modal({ projectId: ConstantsUtil.ProjectId, enableAnalytics: true, metadata: ConstantsUtil.Metadata, - siweConfig + siweConfig, + enableOnramp: true }) ThemeStore.setModal(modal) diff --git a/apps/laboratory/src/pages/library/wagmi.tsx b/apps/laboratory/src/pages/library/wagmi.tsx index 8cbd929439..e94459801e 100644 --- a/apps/laboratory/src/pages/library/wagmi.tsx +++ b/apps/laboratory/src/pages/library/wagmi.tsx @@ -33,7 +33,8 @@ const modal = createWeb3Modal({ desktop_link: 'https://react-wallet.walletconnect.com', webapp_link: 'https://react-wallet.walletconnect.com' } - ] + ], + enableOnramp: true }) ThemeStore.setModal(modal) diff --git a/dangerfile.ts b/dangerfile.ts index 3ad6211f11..71961f4fe0 100644 --- a/dangerfile.ts +++ b/dangerfile.ts @@ -129,15 +129,31 @@ async function checkUiPackage() { fail('New layout components were added, but not exported in ui/index.ts') } - if (created_ui_components_index_ts.length && !jsx_index_diff?.added.includes('../components')) { - fail('New components were added, but not exported in ui/utils/JSXTypeUtil.ts') + if ( + created_ui_components_index_ts.length && + !jsx_index_diff?.added.includes('../components') && + !jsx_index_diff?.diff.includes('../components') + ) { + fail( + `New components were added, but not exported in ui/utils/JSXTypeUtil.ts: ${created_ui_components.join( + ', ' + )}` + ) } - if (created_ui_composites_index_ts.length && !jsx_index_diff?.added.includes('../composites')) { + if ( + created_ui_composites_index_ts.length && + !jsx_index_diff?.added.includes('../composites') && + !jsx_index_diff?.diff.includes('../composites') + ) { fail('New composites were added, but not exported in ui/utils/JSXTypeUtil.ts') } - if (created_ui_layout_index_ts.length && !jsx_index_diff?.added.includes('../layout')) { + if ( + created_ui_layout_index_ts.length && + !jsx_index_diff?.added.includes('../layout') && + !jsx_index_diff?.diff.includes('../layout') + ) { fail('New layout components were added, but not exported in ui/utils/JSXTypeUtil.ts') } @@ -277,6 +293,7 @@ checkSdkVersion() async function checkDevelopmentConstants() { for (const f of updated_files) { if (f.includes('README.md') || f.includes('.yml')) { + // eslint-disable-next-line no-continue continue } const diff = await diffForFile(f) diff --git a/packages/common/src/utils/TypeUtil.ts b/packages/common/src/utils/TypeUtil.ts index e2bb8475e4..35060a9778 100644 --- a/packages/common/src/utils/TypeUtil.ts +++ b/packages/common/src/utils/TypeUtil.ts @@ -1,3 +1,8 @@ +export type CoinbaseTransactionStatus = + | 'ONRAMP_TRANSACTION_STATUS_SUCCESS' + | 'ONRAMP_TRANSACTION_STATUS_IN_PROGRESS' + | 'ONRAMP_TRANSACTION_STATUS_FAILED' + export type TransactionStatus = 'confirmed' | 'failed' | 'pending' export type TransactionDirection = 'in' | 'out' | 'self' @@ -19,7 +24,7 @@ export interface TransactionMetadata { minedAt: string sentFrom: string sentTo: string - status: TransactionStatus + status: TransactionStatus | CoinbaseTransactionStatus nonce: number } diff --git a/packages/core/index.ts b/packages/core/index.ts index a1aa5a0278..02b276901d 100644 --- a/packages/core/index.ts +++ b/packages/core/index.ts @@ -17,6 +17,9 @@ export type { NetworkControllerState } from './src/controllers/NetworkController.js' +export { OnRampController } from './src/controllers/OnRampController.js' +export type { OnRampControllerState, OnRampProvider } from './src/controllers/OnRampController.js' + export { ConnectionController } from './src/controllers/ConnectionController.js' export type { ConnectionControllerClient, diff --git a/packages/core/src/controllers/BlockchainApiController.ts b/packages/core/src/controllers/BlockchainApiController.ts index 8a9835316a..08aacfb67b 100644 --- a/packages/core/src/controllers/BlockchainApiController.ts +++ b/packages/core/src/controllers/BlockchainApiController.ts @@ -8,6 +8,18 @@ import type { } from '../utils/TypeUtil.js' import { OptionsController } from './OptionsController.js' +type DestinationWallet = { + address: string + blockchains: string[] + assets: string[] +} + +type GenerateOnRampT = { + destinationWallets: DestinationWallet[] + partnerUserId: string + defaultNetwork?: string +} + // -- Helpers ------------------------------------------- // const baseUrl = CoreHelperUtil.getBlockchainApiUrl() const api = new FetchUtil({ baseUrl }) @@ -24,12 +36,35 @@ export const BlockchainApiController = { }) }, - fetchTransactions({ account, projectId, cursor }: BlockchainApiTransactionsRequest) { + fetchTransactions({ + account, + projectId, + cursor, + onramp, + signal + }: BlockchainApiTransactionsRequest) { const queryParams = cursor ? { cursor } : {} return api.get({ - path: `/v1/account/${account}/history?projectId=${projectId}`, - params: queryParams + path: `/v1/account/${account}/history?projectId=${projectId}${ + onramp ? `&onramp=${onramp}` : '' + }`, + params: queryParams, + signal }) + }, + + async generateOnRampURL({ destinationWallets, partnerUserId, defaultNetwork }: GenerateOnRampT) { + const response = await api.post<{ url: string }>({ + path: `/v1/generators/onrampurl?projectId=${OptionsController.state.projectId}`, + body: { + destinationWallets, + defaultNetwork, + partnerUserId, + defaultExperience: 'buy' + } + }) + + return response.url } } diff --git a/packages/core/src/controllers/NetworkController.ts b/packages/core/src/controllers/NetworkController.ts index 80551e51b1..5d711d689c 100644 --- a/packages/core/src/controllers/NetworkController.ts +++ b/packages/core/src/controllers/NetworkController.ts @@ -4,6 +4,7 @@ import type { CaipNetwork, CaipNetworkId } from '../utils/TypeUtil.js' import { PublicStateController } from './PublicStateController.js' import { EventsController } from './EventsController.js' import { ModalController } from './ModalController.js' +import { CoreHelperUtil } from '../utils/CoreHelperUtil.js' // -- Types --------------------------------------------- // export interface NetworkControllerClient { @@ -68,6 +69,15 @@ export const NetworkController = { state.requestedCaipNetworks = requestedNetworks }, + getRequestedCaipNetworks() { + const { approvedCaipNetworkIds, requestedCaipNetworks } = state + + const approvedIds = approvedCaipNetworkIds + const requestedNetworks = requestedCaipNetworks + + return CoreHelperUtil.sortRequestedNetworks(approvedIds, requestedNetworks) + }, + async getApprovedCaipNetworksData() { const data = await this._getClient().getApprovedCaipNetworksData() state.supportsAllNetworks = data.supportsAllNetworks diff --git a/packages/core/src/controllers/OnRampController.ts b/packages/core/src/controllers/OnRampController.ts new file mode 100644 index 0000000000..a801c8a23e --- /dev/null +++ b/packages/core/src/controllers/OnRampController.ts @@ -0,0 +1,41 @@ +import { subscribeKey as subKey } from 'valtio/utils' +import { proxy } from 'valtio/vanilla' +import { ONRAMP_PROVIDERS } from '../utils/ConstantsUtil.js' + +// -- Types --------------------------------------------- // +export type OnRampProviderOption = 'coinbase' | 'moonpay' | 'stripe' | 'paypal' + +export type OnRampProvider = { + label: string + name: OnRampProviderOption + feeRange: string + url: string +} + +export interface OnRampControllerState { + providers: OnRampProvider[] + selectedProvider: OnRampProvider | null + error: string | null +} + +type StateKey = keyof OnRampControllerState + +// -- State --------------------------------------------- // +const state = proxy({ + providers: ONRAMP_PROVIDERS as OnRampProvider[], + selectedProvider: null, + error: null +}) + +// -- Controller ---------------------------------------- // +export const OnRampController = { + state, + + subscribeKey(key: K, callback: (value: OnRampControllerState[K]) => void) { + return subKey(state, key, callback) + }, + + setSelectedProvider(provider: OnRampProvider | null) { + state.selectedProvider = provider + } +} diff --git a/packages/core/src/controllers/OptionsController.ts b/packages/core/src/controllers/OptionsController.ts index c4e32581ce..dde0e45307 100644 --- a/packages/core/src/controllers/OptionsController.ts +++ b/packages/core/src/controllers/OptionsController.ts @@ -5,6 +5,8 @@ import type { CustomWallet, Metadata, ProjectId, SdkVersion, Tokens } from '../u // -- Types --------------------------------------------- // export interface OptionsControllerState { projectId: ProjectId + sdkType: 'w3m' + sdkVersion: SdkVersion allWallets?: 'SHOW' | 'HIDE' | 'ONLY_MOBILE' featuredWalletIds?: string[] includeWalletIds?: string[] @@ -15,8 +17,7 @@ export interface OptionsControllerState { privacyPolicyUrl?: string enableAnalytics?: boolean metadata?: Metadata - sdkType: 'w3m' - sdkVersion: SdkVersion + enableOnramp?: boolean } type StateKey = keyof OptionsControllerState @@ -82,5 +83,9 @@ export const OptionsController = { setMetadata(metadata: OptionsControllerState['metadata']) { state.metadata = metadata + }, + + setOnrampEnabled(enableOnramp: OptionsControllerState['enableOnramp']) { + state.enableOnramp = enableOnramp } } diff --git a/packages/core/src/controllers/RouterController.ts b/packages/core/src/controllers/RouterController.ts index 6b2f38379a..b39e531c97 100644 --- a/packages/core/src/controllers/RouterController.ts +++ b/packages/core/src/controllers/RouterController.ts @@ -6,26 +6,31 @@ import type { CaipNetwork, Connector, WcWallet } from '../utils/TypeUtil.js' export interface RouterControllerState { view: | 'Account' + | 'AccountSettings' + | 'AllWallets' + | 'ApproveTransaction' + | 'BuyInProgress' | 'Connect' | 'ConnectingExternal' | 'ConnectingWalletConnect' | 'ConnectingSiwe' - | 'Networks' - | 'SwitchNetwork' - | 'AllWallets' - | 'WhatIsAWallet' - | 'WhatIsANetwork' - | 'GetWallet' | 'Downloads' | 'EmailVerifyOtp' | 'EmailVerifyDevice' - | 'ApproveTransaction' + | 'GetWallet' + | 'Networks' + | 'OnRampProviders' + | 'OnRampActivity' + | 'SwitchNetwork' | 'Transactions' - | 'UpgradeEmailWallet' + | 'UnsupportedChain' | 'UpdateEmailWallet' | 'UpdateEmailPrimaryOtp' | 'UpdateEmailSecondaryOtp' - | 'UnsupportedChain' + | 'UpgradeEmailWallet' + | 'WhatIsABuy' + | 'WhatIsANetwork' + | 'WhatIsAWallet' history: RouterControllerState['view'][] data?: { connector?: Connector diff --git a/packages/core/src/controllers/TransactionsController.ts b/packages/core/src/controllers/TransactionsController.ts index 6ec2cde577..66a921f982 100644 --- a/packages/core/src/controllers/TransactionsController.ts +++ b/packages/core/src/controllers/TransactionsController.ts @@ -1,16 +1,28 @@ import type { Transaction } from '@web3modal/common' import { proxy, subscribe as sub } from 'valtio/vanilla' -import { BlockchainApiController } from './BlockchainApiController.js' import { OptionsController } from './OptionsController.js' import { EventsController } from './EventsController.js' import { SnackController } from './SnackController.js' +import type { CoinbaseTransaction } from '../utils/TypeUtil.js' +import { BlockchainApiController } from './BlockchainApiController.js' + +export type GroupedTransaction = + | { + type: 'zerion' + value: Transaction + } + | { + type: 'coinbase' + value: CoinbaseTransaction + } // -- Types --------------------------------------------- // -type TransactionByMonthMap = Record +type TransactionByMonthMap = Record type TransactionByYearMap = Record export interface TransactionsControllerState { transactions: Transaction[] + coinbaseTransactions: CoinbaseTransaction[] transactionsByYear: TransactionByYearMap loading: boolean empty: boolean @@ -20,6 +32,7 @@ export interface TransactionsControllerState { // -- State --------------------------------------------- // const state = proxy({ transactions: [], + coinbaseTransactions: [], transactionsByYear: {}, loading: false, empty: false, @@ -91,7 +104,28 @@ export const TransactionsController = { grouped[year] = { ...yearTransactions, - [month]: [...monthTransactions, transaction] + [month]: [...monthTransactions, { type: 'zerion', value: transaction }] + } + }) + + return grouped + }, + + groupCoinbaseTransactionsByYearAndMonth( + transactionsMap: TransactionByYearMap = {}, + transactions: CoinbaseTransaction[] = [] + ) { + const grouped: TransactionByYearMap = transactionsMap + + transactions.forEach(transaction => { + const year = new Date(transaction.created_at).getFullYear() + const month = new Date(transaction.created_at).getMonth() + const yearTransactions = grouped[year] ?? {} + const monthTransactions = yearTransactions[month] ?? [] + + grouped[year] = { + ...yearTransactions, + [month]: [...monthTransactions, { type: 'coinbase', value: transaction }] } }) diff --git a/packages/core/src/utils/ConstantsUtil.ts b/packages/core/src/utils/ConstantsUtil.ts index 5dd548ceea..15bea66fe6 100644 --- a/packages/core/src/utils/ConstantsUtil.ts +++ b/packages/core/src/utils/ConstantsUtil.ts @@ -1,5 +1,14 @@ const SECURE_SITE = 'https://secure.walletconnect.com' +export const ONRAMP_PROVIDERS = [ + { + label: 'Coinbase', + name: 'coinbase', + feeRange: '1-2%', + url: '' + } +] + export const ConstantsUtil = { FOUR_MINUTES_MS: 240_000, @@ -28,5 +37,33 @@ export const ConstantsUtil = { CONNECTOR_RDNS_MAP: { coinbaseWallet: 'com.coinbase.wallet' - } as Record + } as Record, + /** + * Network name to Coinbase Pay SDK chain name map object + * @see supported chain names on Coinbase for Pay SDK: https://github.com/coinbase/cbpay-js/blob/d4bda2c05c4d5917c8db6a05476b603546046394/src/types/onramp.ts + */ + WC_COINBASE_PAY_SDK_CHAINS: [ + 'ethereum', + 'arbitrum', + 'polygon', + 'avalanche-c-chain', + 'optimism', + 'celo' + ], + + WC_COINBASE_PAY_SDK_FALLBACK_CHAIN: 'ethereum', + + WC_COINBASE_PAY_SDK_CHAIN_NAME_MAP: { + Ethereum: 'ethereum', + 'Arbitrum One': 'arbitrum', + Polygon: 'polygon', + Avalanche: 'avalanche-c-chain', + 'OP Mainnet': 'optimism', + Celo: 'celo' + }, + + WC_COINBASE_ONRAMP_APP_ID: 'bf18c88d-495a-463b-b249-0b9d3656cf5e' } + +export type CoinbasePaySDKChainNameValues = + keyof typeof ConstantsUtil.WC_COINBASE_PAY_SDK_CHAIN_NAME_MAP diff --git a/packages/core/src/utils/CoreHelperUtil.ts b/packages/core/src/utils/CoreHelperUtil.ts index 54472a8dd0..4fcfb50945 100644 --- a/packages/core/src/utils/CoreHelperUtil.ts +++ b/packages/core/src/utils/CoreHelperUtil.ts @@ -110,8 +110,8 @@ export const CoreHelperUtil = { } }, - openHref(href: string, target: '_blank' | '_self') { - window.open(href, target, 'noreferrer noopener') + openHref(href: string, target: '_blank' | '_self' | 'popupWindow', features?: string) { + window.open(href, target, features || 'noreferrer noopener') }, async preloadImage(src: string) { @@ -141,6 +141,25 @@ export const CoreHelperUtil = { return formattedBalance ? `${formattedBalance} ${symbol ?? ''}` : '0.000' }, + formatBalance2(balance: string | undefined, symbol: string | undefined) { + let formattedBalance = undefined + + if (balance === '0') { + formattedBalance = '0' + } else if (typeof balance === 'string') { + const number = Number(balance) + if (number) { + formattedBalance = number.toString().match(/^-?\d+(?:\.\d{0,3})?/u)?.[0] + } + } + + return { + value: formattedBalance ?? '0', + rest: formattedBalance === '0' ? '000' : '', + symbol + } + }, + isRestrictedRegion() { try { const { timeZone } = new Intl.DateTimeFormat().resolvedOptions() diff --git a/packages/core/src/utils/FetchUtil.ts b/packages/core/src/utils/FetchUtil.ts index fb8c690902..5a543565fb 100644 --- a/packages/core/src/utils/FetchUtil.ts +++ b/packages/core/src/utils/FetchUtil.ts @@ -7,6 +7,7 @@ interface RequestArguments { path: string headers?: HeadersInit params?: Record + signal?: AbortSignal } interface PostArguments extends RequestArguments { @@ -21,48 +22,51 @@ export class FetchUtil { this.baseUrl = baseUrl } - public async get({ headers, ...args }: RequestArguments) { + public async get({ headers, signal, ...args }: RequestArguments) { const url = this.createUrl(args) - const response = await fetch(url, { method: 'GET', headers }) + const response = await fetch(url, { method: 'GET', headers, signal, cache: 'no-cache' }) return response.json() as T } - public async getBlob({ headers, ...args }: RequestArguments) { + public async getBlob({ headers, signal, ...args }: RequestArguments) { const url = this.createUrl(args) - const response = await fetch(url, { method: 'GET', headers }) + const response = await fetch(url, { method: 'GET', headers, signal }) return response.blob() } - public async post({ body, headers, ...args }: PostArguments) { + public async post({ body, headers, signal, ...args }: PostArguments) { const url = this.createUrl(args) const response = await fetch(url, { method: 'POST', headers, - body: body ? JSON.stringify(body) : undefined + body: body ? JSON.stringify(body) : undefined, + signal }) return response.json() as T } - public async put({ body, headers, ...args }: PostArguments) { + public async put({ body, headers, signal, ...args }: PostArguments) { const url = this.createUrl(args) const response = await fetch(url, { method: 'PUT', headers, - body: body ? JSON.stringify(body) : undefined + body: body ? JSON.stringify(body) : undefined, + signal }) return response.json() as T } - public async delete({ body, headers, ...args }: PostArguments) { + public async delete({ body, headers, signal, ...args }: PostArguments) { const url = this.createUrl(args) const response = await fetch(url, { method: 'DELETE', headers, - body: body ? JSON.stringify(body) : undefined + body: body ? JSON.stringify(body) : undefined, + signal }) return response.json() as T diff --git a/packages/core/src/utils/TypeUtil.ts b/packages/core/src/utils/TypeUtil.ts index e5bf63157c..374d169b5d 100644 --- a/packages/core/src/utils/TypeUtil.ts +++ b/packages/core/src/utils/TypeUtil.ts @@ -5,6 +5,14 @@ export type CaipAddress = `${string}:${string}:${string}` export type CaipNetworkId = `${string}:${string}` +export type CaipNetworkCoinbaseNetwork = + | 'Ethereum' + | 'Arbitrum One' + | 'Polygon' + | 'Avalanche' + | 'OP Mainnet' + | 'Celo' + export interface CaipNetwork { id: CaipNetworkId name?: string @@ -137,6 +145,8 @@ export interface BlockchainApiTransactionsRequest { account: string projectId: string cursor?: string + onramp?: 'coinbase' + signal?: AbortSignal } export interface BlockchainApiTransactionsResponse { @@ -144,6 +154,35 @@ export interface BlockchainApiTransactionsResponse { next: string | null } +export interface CoinbaseApiTransactionsRequest { + pageSize: number + pageKey: string + accountAddress: string +} + +export interface CoinbaseApiTransactionsResponse { + transactions: CoinbaseTransaction[] + next_page_key: string + total_count: number +} + +export interface CoinbaseAmount { + value: string + currency: string +} + +export interface CoinbaseTransaction { + status: string + purchaseCurrency: string + purchase_network: string + payment_total: CoinbaseAmount + payment_subtotal: CoinbaseAmount + purchase_amount: CoinbaseAmount + created_at: string + purchase_currency: string + transaction_id: string +} + // -- OptionsController Types --------------------------------------------------- export interface Token { address: string diff --git a/packages/core/tests/controllers/OnRampController.ts b/packages/core/tests/controllers/OnRampController.ts new file mode 100644 index 0000000000..bf90772db3 --- /dev/null +++ b/packages/core/tests/controllers/OnRampController.ts @@ -0,0 +1,20 @@ +import { describe, expect, it } from 'vitest' +import { OnRampController } from '../../index.js' +import { ONRAMP_PROVIDERS } from '../../src/utils/ConstantsUtil.js' + +// -- Tests -------------------------------------------------------------------- +describe('OnRampController', () => { + it('should have valid default state', () => { + expect(OnRampController.state).toEqual({ + providers: ONRAMP_PROVIDERS, + selectedProvider: null, + error: null + }) + }) + + it('should update state correctly on setProjectId()', () => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + OnRampController.setSelectedProvider(ONRAMP_PROVIDERS[0] as any) + expect(OnRampController.state.selectedProvider).toEqual(ONRAMP_PROVIDERS[0]) + }) +}) diff --git a/packages/scaffold/index.ts b/packages/scaffold/index.ts index 1cd6c6d4d6..42a501f64f 100644 --- a/packages/scaffold/index.ts +++ b/packages/scaffold/index.ts @@ -5,8 +5,10 @@ export * from './src/modal/w3m-modal/index.js' export * from './src/modal/w3m-network-button/index.js' export * from './src/modal/w3m-router/index.js' +export * from './src/views/w3m-account-settings-view/index.js' export * from './src/views/w3m-account-view/index.js' export * from './src/views/w3m-all-wallets-view/index.js' +export * from './src/views/w3m-buy-in-progress-view/index.js' export * from './src/views/w3m-connect-view/index.js' export * from './src/views/w3m-connecting-external-view/index.js' export * from './src/views/w3m-connecting-siwe-view/index.js' @@ -15,9 +17,12 @@ export * from './src/views/w3m-downloads-view/index.js' export * from './src/views/w3m-get-wallet-view/index.js' export * from './src/views/w3m-network-switch-view/index.js' export * from './src/views/w3m-networks-view/index.js' +export * from './src/views/w3m-onramp-activity-view/index.js' +export * from './src/views/w3m-onramp-providers-view/index.js' export * from './src/views/w3m-transactions-view/index.js' export * from './src/views/w3m-what-is-a-network-view/index.js' export * from './src/views/w3m-what-is-a-wallet-view/index.js' +export * from './src/views/w3m-what-is-a-buy-view/index.js' export * from './src/views/w3m-email-verify-otp-view/index.js' export * from './src/views/w3m-email-verify-device-view/index.js' export * from './src/views/w3m-approve-transaction-view/index.js' @@ -41,6 +46,7 @@ export * from './src/partials/w3m-header/index.js' export * from './src/partials/w3m-help-widget/index.js' export * from './src/partials/w3m-legal-footer/index.js' export * from './src/partials/w3m-mobile-download-links/index.js' +export * from './src/partials/w3m-onramp-providers-footer/index.js' export * from './src/partials/w3m-snackbar/index.js' export * from './src/partials/w3m-email-login-widget/index.js' diff --git a/packages/scaffold/src/client.ts b/packages/scaffold/src/client.ts index e520b33a21..e3d760a4a6 100644 --- a/packages/scaffold/src/client.ts +++ b/packages/scaffold/src/client.ts @@ -47,6 +47,7 @@ export interface LibraryOptions { customWallets?: OptionsControllerState['customWallets'] enableAnalytics?: OptionsControllerState['enableAnalytics'] metadata?: OptionsControllerState['metadata'] + enableOnramp?: OptionsControllerState['enableOnramp'] _sdkVersion: OptionsControllerState['sdkVersion'] } @@ -265,6 +266,10 @@ export class Web3ModalScaffold { if (options.themeVariables) { ThemeController.setThemeVariables(options.themeVariables) } + + if (options.enableOnramp) { + OptionsController.setOnrampEnabled(Boolean(options.enableOnramp)) + } } private async initOrContinue() { diff --git a/packages/scaffold/src/modal/w3m-router/index.ts b/packages/scaffold/src/modal/w3m-router/index.ts index 728455c69a..0ac31619cb 100644 --- a/packages/scaffold/src/modal/w3m-router/index.ts +++ b/packages/scaffold/src/modal/w3m-router/index.ts @@ -71,6 +71,8 @@ export class W3mRouter extends LitElement { return html`` case 'Account': return html`` + case 'AccountSettings': + return html`` case 'WhatIsAWallet': return html`` case 'WhatIsANetwork': @@ -97,6 +99,14 @@ export class W3mRouter extends LitElement { return html`` case 'UnsupportedChain': return html`` + case 'OnRampProviders': + return html`` + case 'OnRampActivity': + return html`` + case 'WhatIsABuy': + return html`` + case 'BuyInProgress': + return html`` default: return html`` } diff --git a/packages/scaffold/src/partials/w3m-header/index.ts b/packages/scaffold/src/partials/w3m-header/index.ts index a7826417f4..71a8598858 100644 --- a/packages/scaffold/src/partials/w3m-header/index.ts +++ b/packages/scaffold/src/partials/w3m-header/index.ts @@ -24,6 +24,7 @@ function headings() { return { Connect: `Connect ${isEmail ? 'Email' : ''} Wallet`, Account: undefined, + AccountSettings: undefined, ConnectingExternal: name ?? 'Connect Wallet', ConnectingWalletConnect: name ?? 'WalletConnect', ConnectingSiwe: 'Sign In', @@ -42,7 +43,11 @@ function headings() { UpdateEmailWallet: 'Edit Email', UpdateEmailPrimaryOtp: 'Confirm Current Email', UpdateEmailSecondaryOtp: 'Confirm New Email', - UnsupportedChain: 'Switch Network' + UnsupportedChain: 'Switch Network', + OnRampProviders: 'Choose Provider', + OnRampActivity: 'Activity', + WhatIsABuy: 'What is Buy?', + BuyInProgress: 'Buy' } } diff --git a/packages/scaffold/src/partials/w3m-onramp-providers-footer/index.ts b/packages/scaffold/src/partials/w3m-onramp-providers-footer/index.ts new file mode 100644 index 0000000000..9a0328246e --- /dev/null +++ b/packages/scaffold/src/partials/w3m-onramp-providers-footer/index.ts @@ -0,0 +1,53 @@ +import { OptionsController, RouterController } from '@web3modal/core' +import { customElement } from '@web3modal/ui' +import { LitElement, html } from 'lit' +import styles from './styles.js' + +@customElement('w3m-onramp-providers-footer') +export class W3mOnRampProvidersFooter extends LitElement { + public static override styles = [styles] + + // -- Render -------------------------------------------- // + public override render() { + const { termsConditionsUrl, privacyPolicyUrl } = OptionsController.state + + if (!termsConditionsUrl && !privacyPolicyUrl) { + return null + } + + return html` + + + We work with the best providers to fit your buyer needs, region, and to get you the lowest + fees + + + ${this.whatIsBuyTemplate()} + + ` + } + + // -- Private ------------------------------------------- // + private whatIsBuyTemplate() { + return html` + + What is Buy + ` + } + + private onWhatIsBuy() { + RouterController.push('WhatIsABuy') + } +} + +declare global { + interface HTMLElementTagNameMap { + 'w3m-onramp-providers-footer': W3mOnRampProvidersFooter + } +} diff --git a/packages/scaffold/src/partials/w3m-onramp-providers-footer/styles.ts b/packages/scaffold/src/partials/w3m-onramp-providers-footer/styles.ts new file mode 100644 index 0000000000..6d7e389fad --- /dev/null +++ b/packages/scaffold/src/partials/w3m-onramp-providers-footer/styles.ts @@ -0,0 +1,17 @@ +import { css } from 'lit' + +export default css` + wui-flex { + border-top: 1px solid var(--wui-gray-glass-005); + } + + a { + text-decoration: none; + color: var(--wui-color-fg-175); + font-weight: 500; + display: flex; + align-items: center; + justify-content: center; + gap: var(--wui-spacing-3xs); + } +` diff --git a/packages/scaffold/src/views/w3m-account-settings-view/index.ts b/packages/scaffold/src/views/w3m-account-settings-view/index.ts new file mode 100644 index 0000000000..ea88d7f0d0 --- /dev/null +++ b/packages/scaffold/src/views/w3m-account-settings-view/index.ts @@ -0,0 +1,235 @@ +import { + AccountController, + ConnectionController, + AssetController, + CoreHelperUtil, + EventsController, + ModalController, + NetworkController, + RouterController, + SnackController +} from '@web3modal/core' +import { UiHelperUtil, customElement } from '@web3modal/ui' +import { LitElement, html } from 'lit' +import { state } from 'lit/decorators.js' +import { ifDefined } from 'lit/directives/if-defined.js' +import styles from './styles.js' + +@customElement('w3m-account-settings-view') +export class W3mAccountSettingsView extends LitElement { + public static override styles = styles + + // -- Members -------------------------------------------- // + private usubscribe: (() => void)[] = [] + + private readonly networkImages = AssetController.state.networkImages + + // -- State & Properties --------------------------------- // + @state() private address = AccountController.state.address + + @state() private profileImage = AccountController.state.profileImage + + @state() private profileName = AccountController.state.profileName + + @state() private balance = AccountController.state.balance + + @state() private balanceSymbol = AccountController.state.balanceSymbol + + @state() private network = NetworkController.state.caipNetwork + + @state() private disconnecting = false + + public constructor() { + super() + this.usubscribe.push( + ...[ + AccountController.subscribe(val => { + if (val.address) { + this.address = val.address + this.profileImage = val.profileImage + this.profileName = val.profileName + this.balance = val.balance + this.balanceSymbol = val.balanceSymbol + } else { + ModalController.close() + } + }) + ], + NetworkController.subscribeKey('caipNetwork', val => { + if (val?.id) { + this.network = val + } + }) + ) + } + + public override disconnectedCallback() { + this.usubscribe.forEach(unsubscribe => unsubscribe()) + } + + // -- Render -------------------------------------------- // + public override render() { + if (!this.address) { + throw new Error('w3m-account-settings-view: No account provided') + } + + const networkImage = this.networkImages[this.network?.imageId ?? ''] + + return html` + + + + + + ${this.profileName + ? UiHelperUtil.getTruncateString({ + string: this.profileName, + charsStart: 20, + charsEnd: 0, + truncate: 'end' + }) + : UiHelperUtil.getTruncateString({ + string: this.address, + charsStart: 4, + charsEnd: 6, + truncate: 'middle' + })} + + + + + + ${CoreHelperUtil.formatBalance(this.balance, this.balanceSymbol)} + + ${this.explorerBtnTemplate()} + + + + + + + + + ${this.network?.name ?? 'Unknown'} + + + + + Activity + + + Disconnect + + + + ` + } + + // -- Private ------------------------------------------- // + private onTransactions() { + EventsController.sendEvent({ type: 'track', event: 'CLICK_TRANSACTIONS' }) + RouterController.push('Transactions') + } + + private explorerBtnTemplate() { + const { addressExplorerUrl } = AccountController.state + + if (!addressExplorerUrl) { + return null + } + + return html` + + + Block Explorer + + + ` + } + + private isAllowedNetworkSwitch() { + const { requestedCaipNetworks } = NetworkController.state + const isMultiNetwork = requestedCaipNetworks ? requestedCaipNetworks.length > 1 : false + const isValidNetwork = requestedCaipNetworks?.find(({ id }) => id === this.network?.id) + + return isMultiNetwork || !isValidNetwork + } + + private onCopyAddress() { + try { + if (this.address) { + CoreHelperUtil.copyToClopboard(this.address) + SnackController.showSuccess('Address copied') + } + } catch { + SnackController.showError('Failed to copy') + } + } + + private onNetworks() { + if (this.isAllowedNetworkSwitch()) { + RouterController.push('Networks') + } + } + + private async onDisconnect() { + try { + this.disconnecting = true + await ConnectionController.disconnect() + EventsController.sendEvent({ type: 'track', event: 'DISCONNECT_SUCCESS' }) + ModalController.close() + } catch { + EventsController.sendEvent({ type: 'track', event: 'DISCONNECT_ERROR' }) + SnackController.showError('Failed to disconnect') + } finally { + this.disconnecting = false + } + } + + private onExplorer() { + const { addressExplorerUrl } = AccountController.state + if (addressExplorerUrl) { + CoreHelperUtil.openHref(addressExplorerUrl, '_blank') + } + } +} + +declare global { + interface HTMLElementTagNameMap { + 'w3m-account-settings-view': W3mAccountSettingsView + } +} diff --git a/packages/scaffold/src/views/w3m-account-settings-view/styles.ts b/packages/scaffold/src/views/w3m-account-settings-view/styles.ts new file mode 100644 index 0000000000..ff75440150 --- /dev/null +++ b/packages/scaffold/src/views/w3m-account-settings-view/styles.ts @@ -0,0 +1,49 @@ +import { css } from 'lit' + +export default css` + wui-flex { + width: 100%; + } + + wui-icon-link { + margin-right: calc(var(--wui-icon-box-size-md) * -1); + } + + .account-links { + display: flex; + justify-content: space-between; + align-items: center; + } + + .account-links wui-flex { + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + flex: 1; + background: red; + align-items: center; + justify-content: center; + height: 48px; + padding: 10px; + flex: 1 0 0; + + border-radius: var(--XS, 16px); + border: 1px solid var(--dark-accent-glass-010, rgba(71, 161, 255, 0.1)); + background: var(--dark-accent-glass-010, rgba(71, 161, 255, 0.1)); + transition: background 0.2s linear; + } + + .account-links wui-flex:hover { + background: var(--dark-accent-glass-015, rgba(71, 161, 255, 0.15)); + } + + .account-links wui-flex wui-icon { + width: var(--S, 20px); + height: var(--S, 20px); + } + + .account-links wui-flex wui-icon svg path { + stroke: #47a1ff; + } +` diff --git a/packages/scaffold/src/views/w3m-account-view/index.ts b/packages/scaffold/src/views/w3m-account-view/index.ts index 5de6ef8a37..c7176edcb9 100644 --- a/packages/scaffold/src/views/w3m-account-view/index.ts +++ b/packages/scaffold/src/views/w3m-account-view/index.ts @@ -1,16 +1,17 @@ import { AccountController, - ConnectionController, CoreHelperUtil, - EventsController, ModalController, NetworkController, RouterController, - SnackController, - ConnectorController, + AssetUtil, StorageUtil, + ConnectorController, + EventsController, + ConnectionController, + SnackController, ConstantsUtil, - AssetUtil + OptionsController } from '@web3modal/core' import { UiHelperUtil, customElement } from '@web3modal/ui' import { LitElement, html } from 'lit' @@ -23,7 +24,7 @@ export class W3mAccountView extends LitElement { public static override styles = styles // -- Members -------------------------------------------- // - private usubscribe: (() => void)[] = [] + private unsubscribe: (() => void)[] = [] // -- State & Properties --------------------------------- // @state() private address = AccountController.state.address @@ -32,17 +33,17 @@ export class W3mAccountView extends LitElement { @state() private profileName = AccountController.state.profileName - @state() private balance = AccountController.state.balance + @state() private network = NetworkController.state.caipNetwork - @state() private balanceSymbol = AccountController.state.balanceSymbol + @state() private disconnecting = false - @state() private network = NetworkController.state.caipNetwork + @state() private balance = AccountController.state.balance - @state() private disconecting = false + @state() private balanceSymbol = AccountController.state.balanceSymbol public constructor() { super() - this.usubscribe.push( + this.unsubscribe.push( ...[ AccountController.subscribe(val => { if (val.address) { @@ -65,7 +66,7 @@ export class W3mAccountView extends LitElement { } public override disconnectedCallback() { - this.usubscribe.forEach(unsubscribe => unsubscribe()) + this.unsubscribe.forEach(unsubscribe => unsubscribe()) } // -- Render -------------------------------------------- // @@ -79,7 +80,7 @@ export class W3mAccountView extends LitElement { return html` @@ -88,10 +89,9 @@ export class W3mAccountView extends LitElement { address=${this.address} imageSrc=${ifDefined(this.profileImage === null ? undefined : this.profileImage)} > - - + ${this.profileName ? UiHelperUtil.getTruncateString({ string: this.profileName, @@ -102,7 +102,7 @@ export class W3mAccountView extends LitElement { : UiHelperUtil.getTruncateString({ string: this.address, charsStart: 4, - charsEnd: 6, + charsEnd: 4, truncate: 'middle' })} @@ -113,18 +113,15 @@ export class W3mAccountView extends LitElement { @click=${this.onCopyAddress} > - - - ${CoreHelperUtil.formatBalance(this.balance, this.balanceSymbol)} - - - ${this.explorerBtnTemplate()} - + ${CoreHelperUtil.formatBalance(this.balance, this.balanceSymbol)} + ${this.explorerBtnTemplate()} - ${this.emailCardTemplate()} ${this.emailBtnTemplate()} + ${this.emailCardTemplate()} ${this.network?.name ?? 'Unknown'} + ${this.onrampTemplate()} Disconnect @@ -164,6 +160,25 @@ export class W3mAccountView extends LitElement { } // -- Private ------------------------------------------- // + private onrampTemplate() { + const { enableOnramp } = OptionsController.state + + if (!enableOnramp) { + return null + } + + return html` + + Buy + + ` + } + private emailCardTemplate() { const type = StorageUtil.getConnectedConnector() const emailConnector = ConnectorController.getEmailConnector() @@ -182,26 +197,8 @@ export class W3mAccountView extends LitElement { ` } - private emailBtnTemplate() { - const type = StorageUtil.getConnectedConnector() - const emailConnector = ConnectorController.getEmailConnector() - if (!emailConnector || type !== 'EMAIL') { - return null - } - const email = emailConnector.provider.getEmail() ?? '' - - return html` - this.onGoToUpdateEmail(email)} - > - ${email} - - ` + private handleClickPay() { + RouterController.push('OnRampProviders') } private explorerBtnTemplate() { @@ -253,7 +250,7 @@ export class W3mAccountView extends LitElement { private async onDisconnect() { try { - this.disconecting = true + this.disconnecting = true await ConnectionController.disconnect() EventsController.sendEvent({ type: 'track', event: 'DISCONNECT_SUCCESS' }) ModalController.close() @@ -261,7 +258,7 @@ export class W3mAccountView extends LitElement { EventsController.sendEvent({ type: 'track', event: 'DISCONNECT_ERROR' }) SnackController.showError('Failed to disconnect') } finally { - this.disconecting = false + this.disconnecting = false } } @@ -276,10 +273,6 @@ export class W3mAccountView extends LitElement { EventsController.sendEvent({ type: 'track', event: 'EMAIL_UPGRADE_FROM_MODAL' }) RouterController.push('UpgradeEmailWallet') } - - private onGoToUpdateEmail(email: string) { - RouterController.push('UpdateEmailWallet', { email }) - } } declare global { diff --git a/packages/scaffold/src/views/w3m-account-view/styles.ts b/packages/scaffold/src/views/w3m-account-view/styles.ts index dc966e7a9e..6d2e055f53 100644 --- a/packages/scaffold/src/views/w3m-account-view/styles.ts +++ b/packages/scaffold/src/views/w3m-account-view/styles.ts @@ -16,4 +16,93 @@ export default css` wui-notice-card { margin-bottom: var(--wui-spacing-3xs); } + + w3m-transactions-view { + max-height: 200px; + } + + .tab-content-container { + height: 300px; + overflow-y: auto; + overflow-x: hidden; + scrollbar-width: none; + } + + .account-button { + width: auto; + border: none; + display: flex; + align-items: center; + justify-content: center; + gap: var(--wui-spacing-s); + height: 48px; + padding: var(--wui-spacing-xs); + padding-right: var(--wui-spacing-s); + box-shadow: inset 0 0 0 1px var(--wui-gray-glass-002); + background-color: var(--wui-gray-glass-002); + border-radius: 24px; + transaction: background-color 0.2s linear; + } + + .account-button:hover { + background-color: var(--wui-gray-glass-005); + } + + .avatar-container { + position: relative; + } + + wui-avatar.avatar { + width: 32px; + height: 32px; + box-shadow: 0 0 0 2px var(--wui-gray-glass-005); + } + + wui-avatar.network-avatar { + width: 16px; + height: 16px; + position: absolute; + left: 100%; + top: 100%; + transform: translate(-75%, -75%); + box-shadow: 0 0 0 2px var(--wui-gray-glass-005); + } + + .account-links { + display: flex; + justify-content: space-between; + align-items: center; + } + + .account-links wui-flex { + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + flex: 1; + background: red; + align-items: center; + justify-content: center; + height: 48px; + padding: 10px; + flex: 1 0 0; + + border-radius: var(--XS, 16px); + border: 1px solid var(--dark-accent-glass-010, rgba(71, 161, 255, 0.1)); + background: var(--dark-accent-glass-010, rgba(71, 161, 255, 0.1)); + transition: background 0.2s linear; + } + + .account-links wui-flex:hover { + background: var(--dark-accent-glass-015, rgba(71, 161, 255, 0.15)); + } + + .account-links wui-flex wui-icon { + width: var(--S, 20px); + height: var(--S, 20px); + } + + .account-links wui-flex wui-icon svg path { + stroke: #47a1ff; + } ` diff --git a/packages/scaffold/src/views/w3m-buy-in-progress-view/index.ts b/packages/scaffold/src/views/w3m-buy-in-progress-view/index.ts new file mode 100644 index 0000000000..3b395147dc --- /dev/null +++ b/packages/scaffold/src/views/w3m-buy-in-progress-view/index.ts @@ -0,0 +1,236 @@ +import { + AccountController, + ConnectionController, + CoreHelperUtil, + OnRampController, + RouterController, + SnackController, + ThemeController, + BlockchainApiController, + OptionsController +} from '@web3modal/core' +import { customElement } from '@web3modal/ui' +import { LitElement, html } from 'lit' +import { property, state } from 'lit/decorators.js' +import { ifDefined } from 'lit/directives/if-defined.js' +import styles from './styles.js' + +@customElement('w3m-buy-in-progress-view') +export class W3mBuyInProgressView extends LitElement { + public static override styles = styles + + // -- Members ------------------------------------------- // + private unsubscribe: (() => void)[] = [] + + // -- State & Properties -------------------------------- // + @state() protected selectedOnRampProvider = OnRampController.state.selectedProvider + + @state() protected uri = ConnectionController.state.wcUri + + @state() protected ready = false + + @state() private showRetry = false + + @state() public buffering = false + + @state() private error = false + + @state() private intervalId: NodeJS.Timeout | null = null + + @state() private startTime: number | null = null + + @property({ type: Boolean }) public isMobile = false + + @property() public onRetry?: (() => void) | (() => Promise) = undefined + + public constructor() { + super() + this.unsubscribe.push( + ...[ + OnRampController.subscribeKey('selectedProvider', val => { + this.selectedOnRampProvider = val + }) + ] + ) + this.watchTransactions() + } + + public override disconnectedCallback() { + if (this.intervalId) { + clearInterval(this.intervalId) + } + } + + // -- Render -------------------------------------------- // + public override render() { + let label = 'Continue in external window' + + if (this.error) { + label = 'Buy failed' + } else if (this.selectedOnRampProvider) { + label = `Buy in ${this.selectedOnRampProvider?.label}` + } + + const subLabel = this.error + ? 'Buy can be declined from your side or due to and error on the provider app' + : `We’ll notify you once your Buy is processed` + + return html` + + + + + + ${this.error ? null : this.loaderTemplate()} + + + + + + + ${label} + + ${subLabel} + + + ${this.error ? this.tryAgainTemplate() : null} + + + + + + Copy link + + + ` + } + + // -- Private ------------------------------------------- // + private watchTransactions() { + if (!this.selectedOnRampProvider) { + return + } + + switch (this.selectedOnRampProvider.name) { + case 'coinbase': + this.startTime = Date.now() + this.initializeCoinbaseTransactions() + break + default: + break + } + } + + private async initializeCoinbaseTransactions() { + await this.watchCoinbaseTransactions() + this.intervalId = setInterval(() => this.watchCoinbaseTransactions(), 10000) + } + + private async watchCoinbaseTransactions() { + try { + await this.fetchCoinbaseTransactions() + } catch (error) { + SnackController.showError(error) + } + } + + private async fetchCoinbaseTransactions() { + const address = AccountController.state.address + const projectId = OptionsController.state.projectId + if (!address) { + throw new Error('No address found') + } + + const coinbaseResponse = await BlockchainApiController.fetchTransactions({ + account: address, + onramp: 'coinbase', + projectId + }) + + const pendingTransactions = coinbaseResponse.data.filter( + tx => tx.metadata.status === 'ONRAMP_TRANSACTION_STATUS_IN_PROGRESS' + ) + + if (this.intervalId) { + clearInterval(this.intervalId) + } + + if (pendingTransactions.length) { + RouterController.replace('OnRampActivity') + } else if (this.startTime && Date.now() - this.startTime >= 180_000) { + this.error = true + } + } + + private onTryAgain() { + if (!this.selectedOnRampProvider) { + return + } + + this.error = false + CoreHelperUtil.openHref( + this.selectedOnRampProvider.url, + 'popupWindow', + 'width=600,height=800,scrollbars=yes' + ) + } + + private tryAgainTemplate() { + if (!this.selectedOnRampProvider?.url) { + return null + } + + return html` + + Try again + ` + } + + private loaderTemplate() { + const borderRadiusMaster = ThemeController.state.themeVariables['--w3m-border-radius-master'] + const radius = borderRadiusMaster ? parseInt(borderRadiusMaster.replace('px', ''), 10) : 4 + + return html`` + } + + private onCopyUri() { + if (!this.selectedOnRampProvider?.url) { + SnackController.showError('No link found') + RouterController.goBack() + + return + } + + try { + CoreHelperUtil.copyToClopboard(this.selectedOnRampProvider.url) + SnackController.showSuccess('Link copied') + } catch { + SnackController.showError('Failed to copy') + } + } +} + +declare global { + interface HTMLElementTagNameMap { + 'w3m-buy-in-progress-view': W3mBuyInProgressView + } +} diff --git a/packages/scaffold/src/views/w3m-buy-in-progress-view/styles.ts b/packages/scaffold/src/views/w3m-buy-in-progress-view/styles.ts new file mode 100644 index 0000000000..5431b9a3b2 --- /dev/null +++ b/packages/scaffold/src/views/w3m-buy-in-progress-view/styles.ts @@ -0,0 +1,84 @@ +import { css } from 'lit' + +export default css` + @keyframes shake { + 0% { + transform: translateX(0); + } + 25% { + transform: translateX(3px); + } + 50% { + transform: translateX(-3px); + } + 75% { + transform: translateX(3px); + } + 100% { + transform: translateX(0); + } + } + + wui-flex:first-child:not(:only-child) { + position: relative; + } + + wui-loading-thumbnail { + position: absolute; + } + + wui-visual { + width: var(--wui-wallet-image-size-lg); + height: var(--wui-wallet-image-size-lg); + border-radius: calc(var(--wui-border-radius-5xs) * 9 - var(--wui-border-radius-xxs)); + position: relative; + overflow: hidden; + } + + wui-visual::after { + content: ''; + display: block; + width: 100%; + height: 100%; + position: absolute; + inset: 0; + border-radius: calc(var(--wui-border-radius-5xs) * 9 - var(--wui-border-radius-xxs)); + box-shadow: inset 0 0 0 1px var(--wui-gray-glass-005); + } + + wui-icon-box { + position: absolute; + right: calc(var(--wui-spacing-3xs) * -1); + bottom: calc(var(--wui-spacing-3xs) * -1); + opacity: 0; + transform: scale(0.5); + transition: all var(--wui-ease-out-power-2) var(--wui-duration-lg); + } + + wui-text[align='center'] { + width: 100%; + padding: 0px var(--wui-spacing-l); + } + + [data-error='true'] wui-icon-box { + opacity: 1; + transform: scale(1); + } + + [data-error='true'] > wui-flex:first-child { + animation: shake 250ms cubic-bezier(0.36, 0.07, 0.19, 0.97) both; + } + + [data-retry='false'] wui-link { + display: none; + } + + [data-retry='true'] wui-link { + display: block; + opacity: 1; + } + + wui-link { + padding: var(--wui-spacing-4xs) var(--wui-spacing-xxs); + } +` diff --git a/packages/scaffold/src/views/w3m-networks-view/index.ts b/packages/scaffold/src/views/w3m-networks-view/index.ts index 5bbd2684bc..51219c453a 100644 --- a/packages/scaffold/src/views/w3m-networks-view/index.ts +++ b/packages/scaffold/src/views/w3m-networks-view/index.ts @@ -64,7 +64,6 @@ export class W3mNetworksView extends LitElement { private networksTemplate() { const { approvedCaipNetworkIds, requestedCaipNetworks, supportsAllNetworks } = NetworkController.state - const approvedIds = approvedCaipNetworkIds const sortedNetworks = CoreHelperUtil.sortRequestedNetworks( approvedCaipNetworkIds, @@ -79,7 +78,7 @@ export class W3mNetworksView extends LitElement { type="network" name=${network.name ?? network.id} @click=${() => this.onSwitchNetwork(network)} - .disabled=${!supportsAllNetworks && !approvedIds?.includes(network.id)} + .disabled=${!supportsAllNetworks && !approvedCaipNetworkIds?.includes(network.id)} data-testid=${`w3m-network-switch-${network.name ?? network.id}`} > ` diff --git a/packages/scaffold/src/views/w3m-onramp-activity-view/index.ts b/packages/scaffold/src/views/w3m-onramp-activity-view/index.ts new file mode 100644 index 0000000000..ea80e6d44e --- /dev/null +++ b/packages/scaffold/src/views/w3m-onramp-activity-view/index.ts @@ -0,0 +1,151 @@ +import { DateUtil, type Transaction } from '@web3modal/common' +import { + AccountController, + OnRampController, + BlockchainApiController, + OptionsController +} from '@web3modal/core' +import { customElement } from '@web3modal/ui' +import { LitElement, html } from 'lit' +import { state } from 'lit/decorators.js' +import styles from './styles.js' +import { ifDefined } from 'lit/directives/if-defined.js' + +// -- Helpers --------------------------------------------- // +const LOADING_ITEM_COUNT = 7 + +@customElement('w3m-onramp-activity-view') +export class W3mOnRampActivityView extends LitElement { + public static override styles = styles + + // -- Members ------------------------------------------- // + private unsubscribe: (() => void)[] = [] + private refetchTimeout: NodeJS.Timeout | undefined = undefined + + // -- State & Properties -------------------------------- // + @state() protected selectedOnRampProvider = OnRampController.state.selectedProvider + + @state() protected loading = false + + @state() private coinbaseTransactions: Transaction[] = [] + + public constructor() { + super() + this.unsubscribe.push( + ...[ + OnRampController.subscribeKey('selectedProvider', val => { + this.selectedOnRampProvider = val + }), + () => { + clearTimeout(this.refetchTimeout) + } + ] + ) + this.fetchTransactions() + } + + // -- Render -------------------------------------------- // + public override render() { + return html` + + ${this.loading ? this.templateLoading() : this.onRampActivitiesTemplate()} + + ` + } + + // -- Private ------------------------------------------- // + private onRampActivitiesTemplate() { + return this.coinbaseTransactions?.map(transaction => { + const date = DateUtil.getRelativeDateFromNow(transaction.metadata.minedAt) + const transfer = transaction.transfers[0] + const fungibleInfo = transfer?.fungible_info + + if (!fungibleInfo) { + return null + } + + return html` + + ` + }) + } + + private async fetchTransactions() { + const provider = 'coinbase' + + if (provider === 'coinbase') { + await this.fetchCoinbaseTransactions() + } + } + + private async fetchCoinbaseTransactions() { + const address = AccountController.state.address + const projectId = OptionsController.state.projectId + + if (!address) { + throw new Error('No address found') + } + + if (!projectId) { + throw new Error('No projectId found') + } + + this.loading = true + + const coinbaseResponse = await BlockchainApiController.fetchTransactions({ + account: address, + onramp: 'coinbase', + projectId + }) + + this.loading = false + this.coinbaseTransactions = coinbaseResponse.data || [] + this.refetchLoadingTransactions() + } + + private refetchLoadingTransactions() { + const loadingTransactions = this.coinbaseTransactions.filter( + transaction => transaction.metadata.status === 'ONRAMP_TRANSACTION_STATUS_IN_PROGRESS' + ) + + if (loadingTransactions.length === 0) { + clearTimeout(this.refetchTimeout) + + return + } + + // Wait 2 seconds before refetching + this.refetchTimeout = setTimeout(async () => { + const address = AccountController.state.address + const projectId = OptionsController.state.projectId + const coinbaseResponse = await BlockchainApiController.fetchTransactions({ + account: address as `0x${string}`, + onramp: 'coinbase', + projectId + }) + this.coinbaseTransactions = coinbaseResponse.data || [] + this.refetchLoadingTransactions() + }, 3000) + } + + private templateLoading() { + return Array(LOADING_ITEM_COUNT) + .fill(html` `) + .map(item => item) + } +} + +declare global { + interface HTMLElementTagNameMap { + 'w3m-onramp-activity-view': W3mOnRampActivityView + } +} diff --git a/packages/scaffold/src/views/w3m-onramp-activity-view/styles.ts b/packages/scaffold/src/views/w3m-onramp-activity-view/styles.ts new file mode 100644 index 0000000000..398d2a70ab --- /dev/null +++ b/packages/scaffold/src/views/w3m-onramp-activity-view/styles.ts @@ -0,0 +1,19 @@ +import { css } from 'lit' + +export default css` + :host > wui-flex { + height: 500px; + overflow-y: auto; + overflow-x: hidden; + scrollbar-width: none; + padding: var(--wui-spacing-m); + box-sizing: border-box; + display: flex; + align-items: center; + justify-content: flex-start; + } + + wui-transaction-list-item-loader { + width: 100%; + } +` diff --git a/packages/scaffold/src/views/w3m-onramp-providers-view/index.ts b/packages/scaffold/src/views/w3m-onramp-providers-view/index.ts new file mode 100644 index 0000000000..04f29611ed --- /dev/null +++ b/packages/scaffold/src/views/w3m-onramp-providers-view/index.ts @@ -0,0 +1,114 @@ +import { + CoreHelperUtil, + AccountController, + ConstantsUtil, + OnRampController, + type OnRampProvider, + RouterController, + NetworkController, + BlockchainApiController +} from '@web3modal/core' +import { customElement } from '@web3modal/ui' +import { LitElement, html } from 'lit' +import { state } from 'lit/decorators.js' +import type { CoinbasePaySDKChainNameValues } from '@web3modal/core/src/utils/ConstantsUtil' + +@customElement('w3m-onramp-providers-view') +export class W3mOnRampProvidersView extends LitElement { + private unsubscribe: (() => void)[] = [] + + @state() private providers: OnRampProvider[] = OnRampController.state.providers + + public constructor() { + super() + this.unsubscribe.push( + ...[ + OnRampController.subscribeKey('providers', val => { + this.providers = val + }) + ] + ) + } + + public override firstUpdated(): void { + const urlPromises = this.providers.map(async provider => { + if (provider.name === 'coinbase') { + return await this.getCoinbaseOnRampURL() + } + + return Promise.resolve(provider?.url) + }) + + Promise.all(urlPromises).then(urls => { + this.providers = this.providers.map((provider, index) => ({ + ...provider, + url: urls[index] || '' + })) + }) + } + + // -- Render -------------------------------------------- // + public override render() { + return html` + + ${this.onRampProvidersTemplate()} + + + ` + } + + // -- Private ------------------------------------------- // + private onRampProvidersTemplate() { + return this.providers.map( + provider => html` + { + this.onClickProvider(provider) + }} + ?disabled=${!provider.url} + > + ` + ) + } + + private onClickProvider(provider: OnRampProvider) { + OnRampController.setSelectedProvider(provider) + RouterController.push('BuyInProgress') + CoreHelperUtil.openHref(provider.url, 'popupWindow', 'width=600,height=800,scrollbars=yes') + } + + private async getCoinbaseOnRampURL() { + const address = AccountController.state.address + const network = NetworkController.state.caipNetwork + + if (!address) { + throw new Error('No address found') + } + + if (!network?.name) { + throw new Error('No network found') + } + + const defaultNetwork = + ConstantsUtil.WC_COINBASE_PAY_SDK_CHAIN_NAME_MAP[ + network.name as CoinbasePaySDKChainNameValues + ] ?? ConstantsUtil.WC_COINBASE_PAY_SDK_FALLBACK_CHAIN + + return await BlockchainApiController.generateOnRampURL({ + defaultNetwork, + destinationWallets: [ + { address, blockchains: ConstantsUtil.WC_COINBASE_PAY_SDK_CHAINS, assets: ['USDC'] } + ], + partnerUserId: address + }) + } +} + +declare global { + interface HTMLElementTagNameMap { + 'w3m-onramp-providers-view': W3mOnRampProvidersView + } +} diff --git a/packages/scaffold/src/views/w3m-transactions-view/index.ts b/packages/scaffold/src/views/w3m-transactions-view/index.ts index 20fc76c73c..b4f45f75bf 100644 --- a/packages/scaffold/src/views/w3m-transactions-view/index.ts +++ b/packages/scaffold/src/views/w3m-transactions-view/index.ts @@ -4,13 +4,16 @@ import { AccountController, EventsController, OptionsController, - TransactionsController + TransactionsController, + AssetController } from '@web3modal/core' +import type { CoinbaseTransaction } from '@web3modal/core' import { TransactionUtil, customElement } from '@web3modal/ui' import { LitElement, html } from 'lit' import { state } from 'lit/decorators.js' import styles from './styles.js' import type { TransactionType } from '@web3modal/ui/src/utils/TypeUtil.js' +import type { GroupedTransaction } from '@web3modal/core/src/controllers/TransactionsController.js' // -- Helpers --------------------------------------------- // const PAGINATOR_ID = 'last-transaction' @@ -28,8 +31,6 @@ export class W3mTransactionsView extends LitElement { // -- State & Properties -------------------------------- // @state() private address: string | undefined = AccountController.state.address - @state() private transactions = TransactionsController.state.transactions - @state() private transactionsByYear = TransactionsController.state.transactionsByYear @state() private loading = TransactionsController.state.loading @@ -53,7 +54,6 @@ export class W3mTransactionsView extends LitElement { } }), TransactionsController.subscribe(val => { - this.transactions = val.transactions this.transactionsByYear = val.transactionsByYear this.loading = val.loading this.empty = val.empty @@ -64,9 +64,7 @@ export class W3mTransactionsView extends LitElement { } public override firstUpdated() { - if (this.transactions.length === 0) { - TransactionsController.fetchTransactions(this.address) - } + TransactionsController.fetchTransactions(this.address) this.createPaginationObserver() } @@ -81,7 +79,7 @@ export class W3mTransactionsView extends LitElement { // -- Render -------------------------------------------- // public override render() { return html` - + ${this.empty ? null : this.templateTransactionsByYear()} ${this.loading ? this.templateLoading() : null} ${!this.loading && this.empty ? this.templateEmpty() : null} @@ -128,6 +126,24 @@ export class W3mTransactionsView extends LitElement { }) } + private templateRenderCoinbaseTransaction( + transaction: CoinbaseTransaction, + isLastTransaction: boolean + ) { + return html` + + ` + } + private templateRenderTransaction(transaction: Transaction, isLastTransaction: boolean) { const { date, descriptions, direction, isAllNFT, images, status, transfers, type } = this.getTransactionListItemProps(transaction) @@ -179,11 +195,15 @@ export class W3mTransactionsView extends LitElement { ` } - private templateTransactions(transactions: Transaction[], isLastGroup: boolean) { + private templateTransactions(transactions: GroupedTransaction[], isLastGroup: boolean) { return transactions.map((transaction, index) => { const isLastTransaction = isLastGroup && index === transactions.length - 1 - return html`${this.templateRenderTransaction(transaction, isLastTransaction)}` + if (transaction.type === 'coinbase') { + return this.templateRenderCoinbaseTransaction(transaction.value, isLastTransaction) + } + + return html`${this.templateRenderTransaction(transaction.value, isLastTransaction)}` }) } diff --git a/packages/scaffold/src/views/w3m-transactions-view/styles.ts b/packages/scaffold/src/views/w3m-transactions-view/styles.ts index 0ef2615a12..24bba49f7c 100644 --- a/packages/scaffold/src/views/w3m-transactions-view/styles.ts +++ b/packages/scaffold/src/views/w3m-transactions-view/styles.ts @@ -6,6 +6,7 @@ export default css` overflow-y: auto; overflow-x: hidden; scrollbar-width: none; + padding: var(--wui-spacing-m); } :host > wui-flex:first-child::-webkit-scrollbar { diff --git a/packages/scaffold/src/views/w3m-what-is-a-buy-view/index.ts b/packages/scaffold/src/views/w3m-what-is-a-buy-view/index.ts new file mode 100644 index 0000000000..13fdc8a8ca --- /dev/null +++ b/packages/scaffold/src/views/w3m-what-is-a-buy-view/index.ts @@ -0,0 +1,39 @@ +import { customElement } from '@web3modal/ui' +import { RouterController } from '@web3modal/core' +import { LitElement, html } from 'lit' + +@customElement('w3m-what-is-a-buy-view') +export class W3mWhatIsABuyView extends LitElement { + // -- Render -------------------------------------------- // + public override render() { + return html` + + + + + Buy assets to unlock your trade opportunities + + With on-ram Buy, simply buy crypto with fiat via credit card or bank transfer and add + funds in your wallet to trade + + + + Buy + + + ` + } +} + +declare global { + interface HTMLElementTagNameMap { + 'w3m-what-is-a-buy-view': W3mWhatIsABuyView + } +} diff --git a/packages/ui/.eslintrc.json b/packages/ui/.eslintrc.json index 7df9eff912..5de2f8f642 100644 --- a/packages/ui/.eslintrc.json +++ b/packages/ui/.eslintrc.json @@ -5,5 +5,8 @@ "plugin:require-extensions/recommended", "../../.eslintrc.json" ], - "plugins": ["require-extensions"] + "plugins": ["require-extensions"], + "rules": { + "lit/attribute-value-entities": "off" + } } diff --git a/packages/ui/index.ts b/packages/ui/index.ts index f399c35938..641cc83cab 100644 --- a/packages/ui/index.ts +++ b/packages/ui/index.ts @@ -45,8 +45,10 @@ export * from './src/composites/wui-wallet-image/index.js' export * from './src/composites/wui-notice-card/index.js' export * from './src/composites/wui-list-accordion/index.js' export * from './src/composites/wui-list-content/index.js' -export * from './src/composites/wui-list-wallet-transaction/index.js' export * from './src/composites/wui-list-network/index.js' +export * from './src/composites/wui-list-wallet-transaction/index.js' +export * from './src/composites/wui-onramp-activity-item/index.js' +export * from './src/composites/wui-onramp-provider-item/index.js' export * from './src/layout/wui-flex/index.js' export * from './src/layout/wui-grid/index.js' diff --git a/packages/ui/src/assets/svg/add.ts b/packages/ui/src/assets/svg/add.ts new file mode 100644 index 0000000000..bc896de4c6 --- /dev/null +++ b/packages/ui/src/assets/svg/add.ts @@ -0,0 +1,17 @@ +import { svg } from 'lit' + +export const addSvg = svg` + ` diff --git a/packages/ui/src/assets/svg/arrow-bottom-circle.ts b/packages/ui/src/assets/svg/arrow-bottom-circle.ts new file mode 100644 index 0000000000..954f3a22de --- /dev/null +++ b/packages/ui/src/assets/svg/arrow-bottom-circle.ts @@ -0,0 +1,13 @@ +import { svg } from 'lit' + +export const arrowBottomCircleSvg = svg` + ` diff --git a/packages/ui/src/assets/svg/bank.ts b/packages/ui/src/assets/svg/bank.ts new file mode 100644 index 0000000000..194b3a16e1 --- /dev/null +++ b/packages/ui/src/assets/svg/bank.ts @@ -0,0 +1,16 @@ +import { svg } from 'lit' + +export const bankSvg = svg` + ` diff --git a/packages/ui/src/assets/svg/card.ts b/packages/ui/src/assets/svg/card.ts new file mode 100644 index 0000000000..2e04137d73 --- /dev/null +++ b/packages/ui/src/assets/svg/card.ts @@ -0,0 +1,16 @@ +import { svg } from 'lit' + +export const cardSvg = svg` + ` diff --git a/packages/ui/src/assets/svg/checkmark.ts b/packages/ui/src/assets/svg/checkmark.ts index b805747129..f790d802d4 100644 --- a/packages/ui/src/assets/svg/checkmark.ts +++ b/packages/ui/src/assets/svg/checkmark.ts @@ -1,10 +1,16 @@ import { svg } from 'lit' -export const checkmarkSvg = svg` +export const checkmarkSvg = svg` -` + d="M10.537 2.34245C10.8997 2.64654 10.9471 3.187 10.6429 3.54959L5.61072 9.54757C5.45645 9.73144 5.23212 9.84222 4.99229 9.85295C4.75247 9.86368 4.51914 9.77337 4.34906 9.60401L1.40881 6.6761C1.07343 6.34213 1.07238 5.7996 1.40647 5.46433C1.74055 5.12906 2.28326 5.12801 2.61865 5.46198L4.89731 7.73108L9.32942 2.44834C9.63362 2.08576 10.1743 2.03835 10.537 2.34245Z" + fill="currentColor" + />` diff --git a/packages/ui/src/assets/svg/copy.ts b/packages/ui/src/assets/svg/copy.ts index b48b5b3446..7d8db3a54b 100644 --- a/packages/ui/src/assets/svg/copy.ts +++ b/packages/ui/src/assets/svg/copy.ts @@ -1,10 +1,17 @@ import { svg } from 'lit' -export const copySvg = svg` +export const copySvg = svg` -` + d="M9.21498 1.28565H10.5944C11.1458 1.28562 11.6246 1.2856 12.0182 1.32093C12.4353 1.35836 12.853 1.44155 13.2486 1.66724C13.7005 1.92498 14.0749 2.29935 14.3326 2.75122C14.5583 3.14689 14.6415 3.56456 14.6789 3.9817C14.7143 4.37531 14.7142 4.85403 14.7142 5.40545V6.78489C14.7142 7.33631 14.7143 7.81503 14.6789 8.20865C14.6415 8.62578 14.5583 9.04345 14.3326 9.43912C14.0749 9.89099 13.7005 10.2654 13.2486 10.5231C12.853 10.7488 12.4353 10.832 12.0182 10.8694C11.7003 10.8979 11.3269 10.9034 10.9045 10.9045C10.9034 11.3269 10.8979 11.7003 10.8694 12.0182C10.832 12.4353 10.7488 12.853 10.5231 13.2486C10.2654 13.7005 9.89099 14.0749 9.43912 14.3326C9.04345 14.5583 8.62578 14.6415 8.20865 14.6789C7.81503 14.7143 7.33631 14.7142 6.78489 14.7142H5.40545C4.85403 14.7142 4.37531 14.7143 3.9817 14.6789C3.56456 14.6415 3.14689 14.5583 2.75122 14.3326C2.29935 14.0749 1.92498 13.7005 1.66724 13.2486C1.44155 12.853 1.35836 12.4353 1.32093 12.0182C1.2856 11.6246 1.28562 11.1458 1.28565 10.5944V9.21498C1.28562 8.66356 1.2856 8.18484 1.32093 7.79122C1.35836 7.37409 1.44155 6.95642 1.66724 6.56074C1.92498 6.10887 2.29935 5.73451 2.75122 5.47677C3.14689 5.25108 3.56456 5.16789 3.9817 5.13045C4.2996 5.10192 4.67301 5.09645 5.09541 5.09541C5.09645 4.67302 5.10192 4.2996 5.13045 3.9817C5.16789 3.56456 5.25108 3.14689 5.47676 2.75122C5.73451 2.29935 6.10887 1.92498 6.56074 1.66724C6.95642 1.44155 7.37409 1.35836 7.79122 1.32093C8.18484 1.2856 8.66356 1.28562 9.21498 1.28565ZM5.09541 7.09552C4.68397 7.09667 4.39263 7.10161 4.16046 7.12245C3.88053 7.14757 3.78516 7.18949 3.74214 7.21403C3.60139 7.29431 3.48478 7.41091 3.4045 7.55166C3.37997 7.59468 3.33804 7.69005 3.31292 7.96999C3.28659 8.26345 3.28565 8.65147 3.28565 9.25708V10.5523C3.28565 11.1579 3.28659 11.5459 3.31292 11.8394C3.33804 12.1193 3.37997 12.2147 3.4045 12.2577C3.48478 12.3985 3.60139 12.5151 3.74214 12.5954C3.78516 12.6199 3.88053 12.6618 4.16046 12.6869C4.45393 12.7133 4.84195 12.7142 5.44755 12.7142H6.74279C7.3484 12.7142 7.73641 12.7133 8.02988 12.6869C8.30981 12.6618 8.40518 12.6199 8.44821 12.5954C8.58895 12.5151 8.70556 12.3985 8.78584 12.2577C8.81038 12.2147 8.8523 12.1193 8.87742 11.8394C8.89825 11.6072 8.90319 11.3159 8.90435 10.9045C8.48219 10.9034 8.10898 10.8979 7.79122 10.8694C7.37409 10.832 6.95641 10.7488 6.56074 10.5231C6.10887 10.2654 5.73451 9.89099 5.47676 9.43912C5.25108 9.04345 5.16789 8.62578 5.13045 8.20865C5.10194 7.89089 5.09645 7.51767 5.09541 7.09552ZM7.96999 3.31292C7.69005 3.33804 7.59468 3.37997 7.55166 3.4045C7.41091 3.48478 7.29431 3.60139 7.21403 3.74214C7.18949 3.78516 7.14757 3.88053 7.12245 4.16046C7.09611 4.45393 7.09517 4.84195 7.09517 5.44755V6.74279C7.09517 7.3484 7.09611 7.73641 7.12245 8.02988C7.14757 8.30981 7.18949 8.40518 7.21403 8.4482C7.29431 8.58895 7.41091 8.70556 7.55166 8.78584C7.59468 8.81038 7.69005 8.8523 7.96999 8.87742C8.26345 8.90376 8.65147 8.9047 9.25708 8.9047H10.5523C11.1579 8.9047 11.5459 8.90376 11.8394 8.87742C12.1193 8.8523 12.2147 8.81038 12.2577 8.78584C12.3985 8.70556 12.5151 8.58895 12.5954 8.4482C12.6199 8.40518 12.6618 8.30981 12.6869 8.02988C12.7133 7.73641 12.7142 7.3484 12.7142 6.74279V5.44755C12.7142 4.84195 12.7133 4.45393 12.6869 4.16046C12.6618 3.88053 12.6199 3.78516 12.5954 3.74214C12.5151 3.60139 12.3985 3.48478 12.2577 3.4045C12.2147 3.37997 12.1193 3.33804 11.8394 3.31292C11.5459 3.28659 11.1579 3.28565 10.5523 3.28565H9.25708C8.65147 3.28565 8.26345 3.28659 7.96999 3.31292Z" + fill="#788181" + />` diff --git a/packages/ui/src/assets/svg/plus.ts b/packages/ui/src/assets/svg/plus.ts new file mode 100644 index 0000000000..795d8d36ac --- /dev/null +++ b/packages/ui/src/assets/svg/plus.ts @@ -0,0 +1,15 @@ +import { svg } from 'lit' + +export const plusSvg = svg` + ` diff --git a/packages/ui/src/assets/svg/recycle-horizontal.ts b/packages/ui/src/assets/svg/recycle-horizontal.ts new file mode 100644 index 0000000000..edf45a031c --- /dev/null +++ b/packages/ui/src/assets/svg/recycle-horizontal.ts @@ -0,0 +1,11 @@ +import { svg } from 'lit' + +export const recycleHorizontalSvg = svg` + ` diff --git a/packages/ui/src/assets/svg/send.ts b/packages/ui/src/assets/svg/send.ts new file mode 100644 index 0000000000..40d0f92c27 --- /dev/null +++ b/packages/ui/src/assets/svg/send.ts @@ -0,0 +1,17 @@ +import { svg } from 'lit' + +export const sendSvg = svg` + + ` diff --git a/packages/ui/src/assets/svg/swapHorizontalMedium.ts b/packages/ui/src/assets/svg/swapHorizontalMedium.ts new file mode 100644 index 0000000000..a2038773e3 --- /dev/null +++ b/packages/ui/src/assets/svg/swapHorizontalMedium.ts @@ -0,0 +1,18 @@ +import { svg } from 'lit' + +export const swapHorizontalMediumSvg = svg` + + + +` diff --git a/packages/ui/src/assets/visual/coinbase.ts b/packages/ui/src/assets/visual/coinbase.ts new file mode 100644 index 0000000000..f31fb16ec8 --- /dev/null +++ b/packages/ui/src/assets/visual/coinbase.ts @@ -0,0 +1,13 @@ +import { svg } from 'lit' + +export const coinbaseSvg = svg` + + + + + + + + + +` diff --git a/packages/ui/src/assets/visual/moonpay.ts b/packages/ui/src/assets/visual/moonpay.ts new file mode 100644 index 0000000000..60bb0c7167 --- /dev/null +++ b/packages/ui/src/assets/visual/moonpay.ts @@ -0,0 +1,24 @@ +import { svg } from 'lit' + +export const moonpaySvg = svg` + + + + + + + + + + +` diff --git a/packages/ui/src/assets/visual/onramp-card.ts b/packages/ui/src/assets/visual/onramp-card.ts new file mode 100644 index 0000000000..9d11f0a919 --- /dev/null +++ b/packages/ui/src/assets/visual/onramp-card.ts @@ -0,0 +1,16 @@ +import { svg } from 'lit' + +export const onrampCardSvg = svg` + + + + + + + + + + + + +` diff --git a/packages/ui/src/assets/visual/paypal.ts b/packages/ui/src/assets/visual/paypal.ts new file mode 100644 index 0000000000..89179de59b --- /dev/null +++ b/packages/ui/src/assets/visual/paypal.ts @@ -0,0 +1,32 @@ +import { svg } from 'lit' + +export const paypalSvg = svg` + + + + + + + + + + + + +` diff --git a/packages/ui/src/assets/visual/stripe.ts b/packages/ui/src/assets/visual/stripe.ts new file mode 100644 index 0000000000..d6c9a2751f --- /dev/null +++ b/packages/ui/src/assets/visual/stripe.ts @@ -0,0 +1,26 @@ +import { svg } from 'lit' + +export const stripeSvg = svg` + + + + + + + + + + +` diff --git a/packages/ui/src/components/wui-card/styles.ts b/packages/ui/src/components/wui-card/styles.ts index 1534fcd421..ad09da6609 100644 --- a/packages/ui/src/components/wui-card/styles.ts +++ b/packages/ui/src/components/wui-card/styles.ts @@ -4,7 +4,7 @@ export default css` :host { display: block; border-radius: clamp(0px, var(--wui-border-radius-l), 44px); - border: 1px solid var(--wui-gray-glass-005); + box-shadow: 0 0 0 1px var(--wui-gray-glass-005); background-color: var(--wui-color-modal-bg); overflow: hidden; } diff --git a/packages/ui/src/components/wui-icon/index.ts b/packages/ui/src/components/wui-icon/index.ts index e03b98b1cf..24bddd5db7 100644 --- a/packages/ui/src/components/wui-icon/index.ts +++ b/packages/ui/src/components/wui-icon/index.ts @@ -8,6 +8,7 @@ import styles from './styles.js' // -- Svg's-------------------------------- // import { allWalletsSvg } from '../../assets/svg/all-wallets.js' +import { arrowBottomCircleSvg } from '../../assets/svg/arrow-bottom-circle.js' import { appStoreSvg } from '../../assets/svg/app-store.js' import { appleSvg } from '../../assets/svg/apple.js' import { arrowBottomSvg } from '../../assets/svg/arrow-bottom.js' @@ -26,6 +27,7 @@ import { closeSvg } from '../../assets/svg/close.js' import { coinPlaceholderSvg } from '../../assets/svg/coinPlaceholder.js' import { compassSvg } from '../../assets/svg/compass.js' import { copySvg } from '../../assets/svg/copy.js' +import { addSvg } from '../../assets/svg/add.js' import { cursorSvg } from '../../assets/svg/cursor.js' import { desktopSvg } from '../../assets/svg/desktop.js' import { disconnectSvg } from '../../assets/svg/disconnect.js' @@ -48,8 +50,10 @@ import { playStoreSvg } from '../../assets/svg/play-store.js' import { qrCodeIcon } from '../../assets/svg/qr-code.js' import { refreshSvg } from '../../assets/svg/refresh.js' import { searchSvg } from '../../assets/svg/search.js' +import { sendSvg } from '../../assets/svg/send.js' import { swapHorizontalSvg } from '../../assets/svg/swapHorizontal.js' import { swapHorizontalBoldSvg } from '../../assets/svg/swapHorizontalBold.js' +import { swapHorizontalMediumSvg } from '../../assets/svg/swapHorizontalMedium.js' import { swapVerticalSvg } from '../../assets/svg/swapVertical.js' import { telegramSvg } from '../../assets/svg/telegram.js' import { twitchSvg } from '../../assets/svg/twitch.js' @@ -61,24 +65,32 @@ import { walletPlaceholderSvg } from '../../assets/svg/wallet-placeholder.js' import { walletSvg } from '../../assets/svg/wallet.js' import { walletConnectSvg } from '../../assets/svg/walletconnect.js' import { warningCircleSvg } from '../../assets/svg/warning-circle.js' +import { recycleHorizontalSvg } from '../../assets/svg/recycle-horizontal.js' +import { bankSvg } from '../../assets/svg/bank.js' +import { cardSvg } from '../../assets/svg/card.js' +import { plusSvg } from '../../assets/svg/plus.js' import { alphaSvg } from '../../assets/svg/alpha.js' const svgOptions: Record> = { + add: addSvg, allWallets: allWalletsSvg, + arrowBottomCircle: arrowBottomCircleSvg, alpha: alphaSvg, appStore: appStoreSvg, - chromeStore: chromeStoreSvg, apple: appleSvg, arrowBottom: arrowBottomSvg, arrowLeft: arrowLeftSvg, arrowRight: arrowRightSvg, arrowTop: arrowTopSvg, + bank: bankSvg, browser: browserSvg, + card: cardSvg, checkmark: checkmarkSvg, chevronBottom: chevronBottomSvg, chevronLeft: chevronLeftSvg, chevronRight: chevronRightSvg, chevronTop: chevronTopSvg, + chromeStore: chromeStoreSvg, clock: clockSvg, close: closeSvg, compass: compassSvg, @@ -103,10 +115,14 @@ const svgOptions: Record> = { nftPlaceholder: nftPlaceholderSvg, off: offSvg, playStore: playStoreSvg, + plus: plusSvg, qrCode: qrCodeIcon, + recycleHorizontal: recycleHorizontalSvg, refresh: refreshSvg, search: searchSvg, + send: sendSvg, swapHorizontal: swapHorizontalSvg, + swapHorizontalMedium: swapHorizontalMediumSvg, swapHorizontalBold: swapHorizontalBoldSvg, swapVertical: swapVerticalSvg, telegram: telegramSvg, diff --git a/packages/ui/src/components/wui-text/styles.ts b/packages/ui/src/components/wui-text/styles.ts index 52121e64ed..7dcac5ca8c 100644 --- a/packages/ui/src/components/wui-text/styles.ts +++ b/packages/ui/src/components/wui-text/styles.ts @@ -2,7 +2,7 @@ import { css } from 'lit' export default css` :host { - display: flex !important; + display: inline-flex !important; } slot { @@ -21,6 +21,11 @@ export default css` color: var(--local-color); } + .wui-font-medium-title-600 { + font-size: var(--wui-font-size-medium-title); + letter-spacing: var(--wui-letter-spacing-medium-title); + } + .wui-font-large-500, .wui-font-large-600, .wui-font-large-700 { @@ -28,6 +33,13 @@ export default css` letter-spacing: var(--wui-letter-spacing-large); } + .wui-font-2xl-500, + .wui-font-2xl-600, + .wui-font-2xl-700 { + font-size: var(--wui-font-size-2xl); + letter-spacing: var(--wui-letter-spacing-2xl); + } + .wui-font-paragraph-500, .wui-font-paragraph-600, .wui-font-paragraph-700 { @@ -42,6 +54,7 @@ export default css` letter-spacing: var(--wui-letter-spacing-small); } + .wui-font-tiny-400, .wui-font-tiny-500, .wui-font-tiny-600 { font-size: var(--wui-font-size-tiny); @@ -55,6 +68,7 @@ export default css` text-transform: uppercase; } + .wui-font-tiny-400, .wui-font-small-400, .wui-font-paragraph-400 { font-weight: var(--wui-font-weight-light); @@ -66,6 +80,7 @@ export default css` font-weight: var(--wui-font-weight-bold); } + .wui-font-medium-title-600, .wui-font-large-600, .wui-font-paragraph-600, .wui-font-small-600, diff --git a/packages/ui/src/components/wui-visual/index.ts b/packages/ui/src/components/wui-visual/index.ts index 41fc15b145..3cccfe16e7 100644 --- a/packages/ui/src/components/wui-visual/index.ts +++ b/packages/ui/src/components/wui-visual/index.ts @@ -15,8 +15,13 @@ import { nounSvg } from '../../assets/visual/noun.js' import { profileSvg } from '../../assets/visual/profile.js' import { systemSvg } from '../../assets/visual/system.js' import { resetStyles } from '../../utils/ThemeUtil.js' -import type { VisualType } from '../../utils/TypeUtil.js' +import type { VisualSize, VisualType } from '../../utils/TypeUtil.js' import { customElement } from '../../utils/WebComponentsUtil.js' +import { coinbaseSvg } from '../../assets/visual/coinbase.js' +import { moonpaySvg } from '../../assets/visual/moonpay.js' +import { stripeSvg } from '../../assets/visual/stripe.js' +import { paypalSvg } from '../../assets/visual/paypal.js' +import { onrampCardSvg } from '../../assets/visual/onramp-card.js' import styles from './styles.js' // -- Svg's-------------------------------- // @@ -33,7 +38,12 @@ const svgOptions: Record> = { nft: nftSvg, noun: nounSvg, profile: profileSvg, - system: systemSvg + system: systemSvg, + coinbase: coinbaseSvg, + onrampCard: onrampCardSvg, + moonpay: moonpaySvg, + stripe: stripeSvg, + paypal: paypalSvg } @customElement('wui-visual') @@ -43,8 +53,14 @@ export class WuiVisual extends LitElement { // -- State & Properties -------------------------------- // @property() public name: VisualType = 'browser' + @property() public size: VisualSize = 'md' + // -- Render -------------------------------------------- // public override render() { + this.style.cssText = ` + --local-size: var(--wui-visual-size-${this.size}); + ` + return html`${svgOptions[this.name]}` } } diff --git a/packages/ui/src/components/wui-visual/styles.ts b/packages/ui/src/components/wui-visual/styles.ts index 901edecba3..bde7d32fd4 100644 --- a/packages/ui/src/components/wui-visual/styles.ts +++ b/packages/ui/src/components/wui-visual/styles.ts @@ -3,7 +3,12 @@ import { css } from 'lit' export default css` :host { display: block; - width: 55px; - height: 55px; + width: var(--local-size); + height: var(--local-size); + } + + :host svg { + width: 100%; + height: 100%; } ` diff --git a/packages/ui/src/composites/wui-button/styles.ts b/packages/ui/src/composites/wui-button/styles.ts index 48fc1f37ca..1ad95758db 100644 --- a/packages/ui/src/composites/wui-button/styles.ts +++ b/packages/ui/src/composites/wui-button/styles.ts @@ -20,12 +20,12 @@ export default css` padding: var(--wui-spacing-xxs) var(--wui-spacing-s); } - button[data-size='sm'][data-icon-left='true'] { + button[data-size='sm'][data-icon-left='true'][data-icon-right='false'] { padding: var(--wui-spacing-xxs) var(--wui-spacing-s) var(--wui-spacing-xxs) var(--wui-spacing-xs); } - button[data-size='sm'][data-icon-right='true'] { + button[data-size='sm'][data-icon-right='true'][data-icon-left='false'] { padding: var(--wui-spacing-xxs) var(--wui-spacing-xs) var(--wui-spacing-xxs) var(--wui-spacing-s); } @@ -44,11 +44,11 @@ export default css` padding: 8.2px var(--wui-spacing-l) 9px var(--wui-spacing-l); } - button[data-size='md'][data-icon-left='true'] { + button[data-size='md'][data-icon-left='true'][data-icon-right='false'] { padding: 8.2px var(--wui-spacing-l) 9px var(--wui-spacing-s); } - button[data-size='md'][data-icon-right='true'] { + button[data-size='md'][data-icon-right='true'][data-icon-left='false'] { padding: 8.2px var(--wui-spacing-s) 9px var(--wui-spacing-l); } diff --git a/packages/ui/src/composites/wui-icon-link/index.ts b/packages/ui/src/composites/wui-icon-link/index.ts index 853777ed9f..4282217d6f 100644 --- a/packages/ui/src/composites/wui-icon-link/index.ts +++ b/packages/ui/src/composites/wui-icon-link/index.ts @@ -21,6 +21,14 @@ export class WuiIconLink extends LitElement { // -- Render -------------------------------------------- // public override render() { + const borderRadius = this.size === 'lg' ? '--wui-border-radius-xs' : '--wui-border-radius-xxs' + const padding = this.size === 'lg' ? '--wui-spacing-1xs' : '--wui-spacing-2xs' + + this.style.cssText = ` + --local-border-radius: var(${borderRadius}); + --local-padding: var(${padding}); +` + return html` + ` + } + + // -- Private ------------------------------------------- // + private networksTemplate() { + const networks = NetworkController.getRequestedCaipNetworks() + const slicedNetworks = networks?.slice(0, 5) + + return html` + + ${slicedNetworks?.map( + network => html` + + + + ` + )} + + ` + } +} + +declare global { + interface HTMLElementTagNameMap { + 'wui-onramp-provider-item': WuiOnRampProviderItem + } +} diff --git a/packages/ui/src/composites/wui-onramp-provider-item/styles.ts b/packages/ui/src/composites/wui-onramp-provider-item/styles.ts new file mode 100644 index 0000000000..134f4335b9 --- /dev/null +++ b/packages/ui/src/composites/wui-onramp-provider-item/styles.ts @@ -0,0 +1,56 @@ +import { css } from 'lit' + +export default css` + button { + padding: var(--wui-spacing-s); + border-radius: var(--wui-border-radius-xs); + background-color: var(--wui-gray-glass-002); + width: 100%; + display: flex; + align-items: center; + justify-content: flex-start; + gap: var(--wui-spacing-s); + transition: background-color 0.2s linear; + } + + button:hover { + background-color: var(--wui-gray-glass-005); + } + + .provider-image { + width: var(--wui-spacing-3xl); + min-width: var(--wui-spacing-3xl); + height: var(--wui-spacing-3xl); + border-radius: calc(var(--wui-border-radius-xs) - calc(var(--wui-spacing-s) / 2)); + position: relative; + overflow: hidden; + } + + .provider-image::after { + content: ''; + display: block; + width: 100%; + height: 100%; + position: absolute; + inset: 0; + border-radius: calc(var(--wui-border-radius-xs) - calc(var(--wui-spacing-s) / 2)); + box-shadow: inset 0 0 0 1px var(--wui-gray-glass-005); + } + + .network-icon { + width: var(--wui-spacing-m); + height: var(--wui-spacing-m); + border-radius: calc(var(--wui-spacing-m) / 2); + overflow: hidden; + box-shadow: + 0 0 0 3px var(--wui-gray-glass-002), + 0 0 0 3px var(--wui-color-modal-bg); + transition: box-shadow 0.2s linear; + } + + button:hover .network-icon { + box-shadow: + 0 0 0 3px var(--wui-gray-glass-005), + 0 0 0 3px var(--wui-color-modal-bg); + } +` diff --git a/packages/ui/src/composites/wui-snackbar/styles.ts b/packages/ui/src/composites/wui-snackbar/styles.ts index 4ceb66ec13..f7aa1e429a 100644 --- a/packages/ui/src/composites/wui-snackbar/styles.ts +++ b/packages/ui/src/composites/wui-snackbar/styles.ts @@ -8,6 +8,8 @@ export default css` padding: var(--wui-spacing-xs) var(--wui-spacing-m) var(--wui-spacing-xs) var(--wui-spacing-xs); border-radius: var(--wui-border-radius-3xl); border: 1px solid var(--wui-gray-glass-005); + box-sizing: border-box; + max-height: 40px; background-color: var(--wui-color-bg-175); box-shadow: 0px 14px 64px -4px rgba(0, 0, 0, 0.15), diff --git a/packages/ui/src/composites/wui-transaction-list-item/index.ts b/packages/ui/src/composites/wui-transaction-list-item/index.ts index 4b8c1eb38c..f5d0387738 100644 --- a/packages/ui/src/composites/wui-transaction-list-item/index.ts +++ b/packages/ui/src/composites/wui-transaction-list-item/index.ts @@ -28,6 +28,12 @@ export class WuiTransactionListItem extends LitElement { @property({ type: Array }) public images: TransactionImage[] = [] + @property() public price: TransactionImage[] = [] + + @property() public amount: TransactionImage[] = [] + + @property() public symbol: TransactionImage[] = [] + // -- Render -------------------------------------------- // public override render() { return html` @@ -41,7 +47,7 @@ export class WuiTransactionListItem extends LitElement { > - ${TransactionTypePastTense[this.type]} + ${TransactionTypePastTense[this.type] || this.type} ${this.templateDescription()} ${this.templateSecondDescription()} diff --git a/packages/ui/src/composites/wui-transaction-list-item/styles.ts b/packages/ui/src/composites/wui-transaction-list-item/styles.ts index d8d2168a8e..7e8caf0999 100644 --- a/packages/ui/src/composites/wui-transaction-list-item/styles.ts +++ b/packages/ui/src/composites/wui-transaction-list-item/styles.ts @@ -4,7 +4,7 @@ export default css` :host > wui-flex:first-child { align-items: center; column-gap: var(--wui-spacing-s); - padding: 6.5px var(--wui-spacing-l) 6.5px var(--wui-spacing-xs); + padding: 6.5px var(--wui-spacing-xs) 6.5px var(--wui-spacing-xs); width: 100%; } diff --git a/packages/ui/src/utils/JSXTypeUtil.ts b/packages/ui/src/utils/JSXTypeUtil.ts index a22fbf267a..ff0b90329e 100644 --- a/packages/ui/src/utils/JSXTypeUtil.ts +++ b/packages/ui/src/utils/JSXTypeUtil.ts @@ -45,8 +45,10 @@ import type { WuiWalletImage } from '../composites/wui-wallet-image/index.js' import type { WuiNoticeCard } from '../composites/wui-notice-card/index.js' import type { WuiListAccordion } from '../composites/wui-list-accordion/index.js' import type { WuiListContent } from '../composites/wui-list-content/index.js' -import type { WuiListWalletTransaction } from '../composites/wui-list-wallet-transaction/index.js' import type { WuiListNetwork } from '../composites/wui-list-network/index.js' +import type { WuiListWalletTransaction } from '../composites/wui-list-wallet-transaction/index.js' +import type { WuiOnRampActivityItem } from '../composites/wui-onramp-activity-item/index.js' +import type { WuiOnRampProviderItem } from '../composites/wui-onramp-provider-item/index.js' import type { WuiFlex } from '../layout/wui-flex/index.js' import type { WuiGrid } from '../layout/wui-grid/index.js' @@ -107,8 +109,10 @@ declare global { 'wui-notice-card': CustomElement 'wui-list-accordion': CustomElement 'wui-list-content': CustomElement - 'wui-list-wallet-transaction': CustomElement 'wui-list-network': CustomElement + 'wui-list-wallet-transaction': CustomElement + 'wui-onramp-activity-item': CustomElement + 'wui-onramp-provider-item': CustomElement } } } diff --git a/packages/ui/src/utils/ThemeUtil.ts b/packages/ui/src/utils/ThemeUtil.ts index e7f3252063..a056ec0ced 100644 --- a/packages/ui/src/utils/ThemeUtil.ts +++ b/packages/ui/src/utils/ThemeUtil.ts @@ -67,6 +67,8 @@ function createRootStyles(themeVariables?: ThemeVariables) { --wui-font-size-small: calc(var(--w3m-font-size-master) * 1.4); --wui-font-size-paragraph: calc(var(--w3m-font-size-master) * 1.6); --wui-font-size-large: calc(var(--w3m-font-size-master) * 2); + --wui-font-size-medium-title: calc(var(--w3m-font-size-master) * 2.4); + --wui-font-size-2xl: calc(var(--w3m-font-size-master) * 4); --wui-border-radius-5xs: var(--w3m-border-radius-master); --wui-border-radius-4xs: calc(var(--w3m-border-radius-master) * 1.5); @@ -83,6 +85,8 @@ function createRootStyles(themeVariables?: ThemeVariables) { --wui-font-weight-medium: 600; --wui-font-weight-bold: 700; + --wui-letter-spacing-2xl: -1.6px; + --wui-letter-spacing-medium-title: -0.96px; --wui-letter-spacing-large: -0.8px; --wui-letter-spacing-paragraph: -0.64px; --wui-letter-spacing-small: -0.56px; @@ -127,6 +131,11 @@ function createRootStyles(themeVariables?: ThemeVariables) { --wui-wallet-image-size-md: 56px; --wui-wallet-image-size-lg: 80px; + --wui-visual-size-size-inherit: inherit; + --wui-visual-size-sm: 40px; + --wui-visual-size-md: 55px; + --wui-visual-size-lg: 80px; + --wui-box-size-md: 100px; --wui-box-size-lg: 120px; diff --git a/packages/ui/src/utils/TransactionUtil.ts b/packages/ui/src/utils/TransactionUtil.ts index bf47a16d03..cc415d47d9 100644 --- a/packages/ui/src/utils/TransactionUtil.ts +++ b/packages/ui/src/utils/TransactionUtil.ts @@ -75,12 +75,13 @@ export const TransactionUtil = { }, getTransactionDescriptions(transaction: Transaction) { - const type = transaction.metadata?.operationType as TransactionType + const type = transaction?.metadata?.operationType as TransactionType - const transfers = transaction.transfers - const haveTransfer = transaction.transfers?.length > 0 - const haveMultipleTransfers = transaction.transfers?.length > 1 - const isFungible = haveTransfer && transfers?.every(transfer => Boolean(transfer.fungible_info)) + const transfers = transaction?.transfers + const haveTransfer = transaction?.transfers?.length > 0 + const haveMultipleTransfers = transaction?.transfers?.length > 1 + const isFungible = + haveTransfer && transfers?.every(transfer => Boolean(transfer?.fungible_info)) const [firstTransfer, secondTransfer] = transfers let firstDescription = this.getTransferDescription(firstTransfer) @@ -91,13 +92,13 @@ export const TransactionUtil = { if (isSendOrReceive && isFungible) { firstDescription = UiHelperUtil.getTruncateString({ - string: transaction.metadata.sentFrom, + string: transaction?.metadata.sentFrom, charsStart: 4, charsEnd: 6, truncate: 'middle' }) secondDescription = UiHelperUtil.getTruncateString({ - string: transaction.metadata.sentTo, + string: transaction?.metadata.sentTo, charsStart: 4, charsEnd: 6, truncate: 'middle' diff --git a/packages/ui/src/utils/TypeUtil.ts b/packages/ui/src/utils/TypeUtil.ts index a9357042c3..ae36018c5d 100644 --- a/packages/ui/src/utils/TypeUtil.ts +++ b/packages/ui/src/utils/TypeUtil.ts @@ -15,6 +15,7 @@ export type TextType = | 'large-500' | 'large-600' | 'large-700' + | 'medium-title-600' | 'micro-600' | 'micro-700' | 'paragraph-400' @@ -24,8 +25,10 @@ export type TextType = | 'small-400' | 'small-500' | 'small-600' + | 'tiny-400' | 'tiny-500' | 'tiny-600' + | '2xl-500' export type TextAlign = 'center' | 'left' | 'right' @@ -87,7 +90,9 @@ export type GridContentType = export type GridItemsType = 'center' | 'end' | 'start' | 'stretch' export type IconType = + | 'add' | 'allWallets' + | 'arrowBottomCircle' | 'alpha' | 'appStore' | 'chromeStore' @@ -96,7 +101,9 @@ export type IconType = | 'arrowLeft' | 'arrowRight' | 'arrowTop' + | 'bank' | 'browser' + | 'card' | 'checkmark' | 'chevronBottom' | 'chevronLeft' @@ -126,11 +133,15 @@ export type IconType = | 'nftPlaceholder' | 'off' | 'playStore' + | 'plus' | 'qrCode' + | 'recycleHorizontal' | 'refresh' | 'search' + | 'send' | 'swapHorizontal' | 'swapHorizontalBold' + | 'swapHorizontalMedium' | 'swapVertical' | 'telegram' | 'twitch' @@ -145,6 +156,7 @@ export type IconType = export type VisualType = | 'browser' + | 'coinbase' | 'dao' | 'defi' | 'defiAlt' @@ -155,8 +167,14 @@ export type VisualType = | 'network' | 'nft' | 'noun' + | 'onrampCard' | 'profile' | 'system' + | 'moonpay' + | 'stripe' + | 'paypal' + +export type VisualSize = 'sm' | 'md' | 'lg' export type LogoType = | 'apple' diff --git a/packages/wagmi/src/client.ts b/packages/wagmi/src/client.ts index 68c2b521f7..e1a5319d7d 100644 --- a/packages/wagmi/src/client.ts +++ b/packages/wagmi/src/client.ts @@ -191,7 +191,7 @@ export class Web3Modal extends Web3ModalScaffold { onChange: connectors => this.syncConnectors(connectors) }) watchAccount(this.wagmiConfig, { - onChange: accountData => this.syncAccount({ ...accountData, config: wagmiConfig }) + onChange: accountData => this.syncAccount({ ...accountData }) }) } @@ -231,11 +231,7 @@ export class Web3Modal extends Web3ModalScaffold { this.setRequestedCaipNetworks(requestedCaipNetworks ?? []) } - private async syncAccount({ - address, - isConnected, - chainId - }: GetAccountReturnType & { config: Config }) { + private async syncAccount({ address, isConnected, chainId }: GetAccountReturnType) { this.resetAccount() // TOD0: Check with Sven. Now network is synced when acc is synced. this.syncNetwork() diff --git a/packages/wagmi/src/connectors/EmailConnector.ts b/packages/wagmi/src/connectors/EmailConnector.ts index 68ebe29b09..afda81cb9f 100644 --- a/packages/wagmi/src/connectors/EmailConnector.ts +++ b/packages/wagmi/src/connectors/EmailConnector.ts @@ -45,19 +45,16 @@ export function emailConnector(parameters: EmailParameters) { } } }, - async disconnect() { const provider = await this.getProvider() await provider.disconnect() }, - async getAccounts() { const provider = await this.getProvider() const { address } = await provider.connect() return [address as Address] }, - async getProvider() { if (!this.provider) { this.provider = new W3mFrameProvider(parameters.options.projectId) @@ -65,14 +62,12 @@ export function emailConnector(parameters: EmailParameters) { return Promise.resolve(this.provider) }, - async getChainId() { const provider: W3mFrameProvider = await this.getProvider() const { chainId } = await provider.getChainId() return chainId }, - async isAuthorized() { const provider = await this.getProvider() const { isConnected } = await provider.isConnected() @@ -98,7 +93,6 @@ export function emailConnector(parameters: EmailParameters) { throw error } }, - onAccountsChanged(accounts) { if (accounts.length === 0) { this.onDisconnect() @@ -106,12 +100,10 @@ export function emailConnector(parameters: EmailParameters) { config.emitter.emit('change', { accounts: accounts.map(getAddress) }) } }, - onChainChanged(chain) { const chainId = normalizeChainId(chain) config.emitter.emit('change', { chainId }) }, - async onConnect(connectInfo) { const chainId = normalizeChainId(connectInfo.chainId) const accounts = await this.getAccounts() diff --git a/tsconfig.json b/tsconfig.json index ba699e62e0..714f02c714 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -26,5 +26,5 @@ "sourceMap": true, "incremental": true }, - "exclude": ["node_modules", "dist", "types", "out", ".next", ".turbo"] + "exclude": ["node_modules", "dist", "types", "out", ".next", ".turbo", "coverage"] }