diff --git a/assets/account-center-example.png b/assets/account-center-example.png new file mode 100644 index 000000000..ffdeb6373 Binary files /dev/null and b/assets/account-center-example.png differ diff --git a/docs/src/lib/assets/account-center-example.png b/docs/src/lib/assets/account-center-example.png new file mode 100644 index 000000000..ffdeb6373 Binary files /dev/null and b/docs/src/lib/assets/account-center-example.png differ diff --git a/docs/src/lib/components/FeaturesSection.svelte b/docs/src/lib/components/FeaturesSection.svelte index 6d0c8b73d..36ec45d36 100644 --- a/docs/src/lib/components/FeaturesSection.svelte +++ b/docs/src/lib/components/FeaturesSection.svelte @@ -23,7 +23,7 @@ @@ -47,7 +47,7 @@
diff --git a/docs/src/lib/components/HeroSection.svelte b/docs/src/lib/components/HeroSection.svelte index e3e077be5..3386f9f73 100644 --- a/docs/src/lib/components/HeroSection.svelte +++ b/docs/src/lib/components/HeroSection.svelte @@ -8,7 +8,7 @@
-
{'Web3-Onboard'}
+
{'Web3 Onboard'}
{'Open-source, framework-agnostic JavaScript library to onboard users to web3 apps. Help your users transact with ease by enabling wallet connection, real-time transaction states, and more.'}
diff --git a/docs/src/lib/components/TestimonialSection.svelte b/docs/src/lib/components/TestimonialSection.svelte index 7b6d9d1f0..3979c2065 100644 --- a/docs/src/lib/components/TestimonialSection.svelte +++ b/docs/src/lib/components/TestimonialSection.svelte @@ -4,7 +4,7 @@

- {"Who's using web3-onboard?"} + {"Who's using Web3 Onboard?"}

Web3-Onboard-users diff --git a/docs/src/lib/components/ThemeCustomizer.svelte b/docs/src/lib/components/ThemeCustomizer.svelte index 14bf3f82c..0702c4f6b 100644 --- a/docs/src/lib/components/ThemeCustomizer.svelte +++ b/docs/src/lib/components/ThemeCustomizer.svelte @@ -274,7 +274,7 @@
- Enter your website url or drag and drop a screenshot to preview web3-onboard on your site + Enter your website url or drag and drop a screenshot to preview Web3 Onboard on your site
diff --git a/docs/src/lib/components/ThemingSection.svelte b/docs/src/lib/components/ThemingSection.svelte index 8c03122b8..6561bc3d7 100644 --- a/docs/src/lib/components/ThemingSection.svelte +++ b/docs/src/lib/components/ThemingSection.svelte @@ -14,8 +14,8 @@
diff --git a/docs/src/routes/docs/[...1]overview/[...1]introduction.md b/docs/src/routes/docs/[...1]overview/[...1]introduction.md index 88576669a..2dc04d99b 100644 --- a/docs/src/routes/docs/[...1]overview/[...1]introduction.md +++ b/docs/src/routes/docs/[...1]overview/[...1]introduction.md @@ -38,6 +38,8 @@ web3-onboard supports all EVM networks. Supporting a new network is simply a mat - Ethereum - Polygon - Base Goerli +- Goerli +- Sepolia - Arbitrum - Optimism - Avalanche @@ -47,9 +49,7 @@ web3-onboard supports all EVM networks. Supporting a new network is simply a mat - Gnosis Chain - Harmony One - Moonriver -- Goerli -- Sepolia -- Any other EVM network +- All other EVM network ### [Optional] Use an API key to fetch real time transaction data, balances & gas diff --git a/docs/src/routes/docs/[...3]modules/core.md b/docs/src/routes/docs/[...3]modules/core.md index 4a283d802..660aa9648 100644 --- a/docs/src/routes/docs/[...3]modules/core.md +++ b/docs/src/routes/docs/[...3]modules/core.md @@ -1,4 +1,5 @@ + +
diff --git a/examples/with-vanilla-js/package.json b/examples/with-vanilla-js/package.json index 88bcef593..1651146ef 100644 --- a/examples/with-vanilla-js/package.json +++ b/examples/with-vanilla-js/package.json @@ -10,10 +10,21 @@ "author": "", "license": "ISC", "devDependencies": { + "assert": "^2.0.0", + "buffer": "^6.0.3", + "crypto-browserify": "^3.12.0", "css-loader": "^6.7.3", + "https-browserify": "^1.0.0", + "os-browserify": "^0.3.0", + "path-browserify": "^1.0.1", + "process": "^0.11.10", + "stream-browserify": "^3.0.0", + "stream-http": "^3.2.0", "style-loader": "^3.3.2", + "util": "^0.12.5", "webpack": "^5.79.0", - "webpack-cli": "^5.0.1" + "webpack-cli": "^5.0.2", + "webpack-dev-server": "^4.13.3" }, "dependencies": { "@web3-onboard/coinbase": "^2.2.2", diff --git a/examples/with-vanilla-js/src/index.js b/examples/with-vanilla-js/src/index.js index 88f8dbf1c..f7ff6987d 100644 --- a/examples/with-vanilla-js/src/index.js +++ b/examples/with-vanilla-js/src/index.js @@ -28,9 +28,9 @@ const addConnectedInfo = connectedAccount => { $connect.addEventListener('click', async _ => { const wallets = await connect() - const connectedAccount = wallets[0].accounts[0] - label = wallets[0].label if (wallets[0]) { + const connectedAccount = wallets[0].accounts[0] + label = wallets[0].label addConnectedInfo(connectedAccount) $wallet.classList.remove('hidden') $disconnected.classList.add('hidden') diff --git a/examples/with-vanilla-js/webpack.config.js b/examples/with-vanilla-js/webpack.config.js index 95fed1288..7be6b77cb 100644 --- a/examples/with-vanilla-js/webpack.config.js +++ b/examples/with-vanilla-js/webpack.config.js @@ -1,4 +1,5 @@ const path = require('path') +const webpack = require('webpack') module.exports = { entry: './src/index.js', @@ -7,6 +8,25 @@ module.exports = { path: path.resolve(__dirname, 'dist'), clean: true }, + resolve: { + fallback: { + path: require.resolve('path-browserify') + }, + alias: { + assert: 'assert', + buffer: 'buffer', + crypto: 'crypto-browserify', + http: 'stream-http', + https: 'https-browserify', + os: 'os-browserify/browser', + process: 'process/browser', + stream: 'stream-browserify', + util: 'util' + } + }, + experiments: { + asyncWebAssembly: true + }, module: { rules: [ { @@ -21,6 +41,12 @@ module.exports = { } ] }, + plugins: [ + new webpack.ProvidePlugin({ + process: 'process/browser', + Buffer: ['buffer', 'Buffer'] + }) + ], devServer: { historyApiFallback: true, static: { directory: path.join(__dirname, '/') }, diff --git a/package.json b/package.json index 15c035227..12308cb97 100644 --- a/package.json +++ b/package.json @@ -60,7 +60,8 @@ "build": "yarn wsrun --serial build", "type-check": "yarn wsrun type-check", "file-check": "yarn install --check-files", - "check-all": "yarn build && yarn file-check && yarn type-check" + "check-all": "yarn build && yarn file-check && yarn type-check", + "test-playwright": "cd test && yarn && yarn playwright test" }, "devDependencies": { "prettier": "^2.4.1", diff --git a/packages/cede-store/README.md b/packages/cede-store/README.md index 4e5220665..9ec42a486 100644 --- a/packages/cede-store/README.md +++ b/packages/cede-store/README.md @@ -1,15 +1,10 @@ # @web3-onboard/cede-store -## Wallet module for connecting cede.store Wallet SDK to web3-onboard +CEX module for connecting cede.store through web3-onboard. Check out the [cede.store Wallet Developer Docs](https://docs.cede.store) for more information. -cede.store is a non-custodial browser extension designed to store CEX (centralized exchange) API keys and to sign CEX requests from the client-side. It allows users to manage their cryptos in their CEX through a unified interface. - -Any dApp can integrate cede.store in order to track and/or manage a user's CEX assets. In this way, we offer the dApp a way to monitor and manage a user's CEX assets while remaining non-custodial and maintaining the same user experience as any DeFi browser wallet. As cede.store is not a traditional 1193 wallet behavior is a little different from other wallets that connect through web3-onboard in that there is no on-chain user address to interact with and there isn't a specific chain associated. With this behavior dapp devs will need to handle accordingly and differently from traditional 1193 wallets. The dapp dev can expect the connect account to not be shown as a hex value (or at all) and the chain to always be `0x0` when a user connects with cede.store for that specific wallet account. -See [cede.store Wallet Developer Docs](https://docs.cede.store) - ### Install `npm i @web3-onboard/cede-store` diff --git a/packages/cede-store/package.json b/packages/cede-store/package.json index 3435dcb72..ba8117d89 100644 --- a/packages/cede-store/package.json +++ b/packages/cede-store/package.json @@ -1,6 +1,6 @@ { "name": "@web3-onboard/cede-store", - "version": "2.0.0", + "version": "2.0.1-alpha.2", "description": "cede.store SDK wallet module for connecting to Web3-Onboard. Web3-Onboard makes it simple to connect Ethereum hardware and software wallets to your dapp. Features standardised spec compliant web3 providers for all supported wallets, framework agnostic modern javascript UI with code splitting, CSS customization, multi-chain and multi-account support, reactive wallet state subscriptions and real-time transaction state change notifications.", "keywords": [ "Ethereum", @@ -70,6 +70,6 @@ }, "dependencies": { "@cedelabs/providers": "^0.0.7", - "@web3-onboard/common": "^2.3.0-alpha.1" + "@web3-onboard/common": "^2.3.2-alpha.2" } } diff --git a/packages/coinbase/package.json b/packages/coinbase/package.json index 12a35fb3d..3c2c24c54 100644 --- a/packages/coinbase/package.json +++ b/packages/coinbase/package.json @@ -1,6 +1,6 @@ { "name": "@web3-onboard/coinbase", - "version": "2.2.2", + "version": "2.2.3-alpha.2", "description": "Coinbase SDK wallet module for connecting to Web3-Onboard. Web3-Onboard makes it simple to connect Ethereum hardware and software wallets to your dapp. Features standardised spec compliant web3 providers for all supported wallets, framework agnostic modern javascript UI with code splitting, CSS customization, multi-chain and multi-account support, reactive wallet state subscriptions and real-time transaction state change notifications.", "keywords": [ "Ethereum", @@ -59,6 +59,6 @@ }, "dependencies": { "@coinbase/wallet-sdk": "^3.6.0", - "@web3-onboard/common": "^2.3.1" + "@web3-onboard/common": "^2.3.2-alpha.2" } } diff --git a/packages/common/package.json b/packages/common/package.json index 7bb2fa48f..4b1253ea6 100644 --- a/packages/common/package.json +++ b/packages/common/package.json @@ -1,6 +1,6 @@ { "name": "@web3-onboard/common", - "version": "2.3.1", + "version": "2.3.2-alpha.2", "description": "Web3-Onboard makes it simple to connect Ethereum hardware and software wallets to your dapp. Features standardised spec compliant web3 providers for all supported wallets, framework agnostic modern javascript UI with code splitting, CSS customization, multi-chain and multi-account support, reactive wallet state subscriptions and real-time transaction state change notifications.", "keywords": [ "Ethereum", diff --git a/packages/common/src/types.ts b/packages/common/src/types.ts index 6bcbb5390..95b746734 100644 --- a/packages/common/src/types.ts +++ b/packages/common/src/types.ts @@ -404,6 +404,12 @@ export interface Chain { label?: string /* Recommended to include. The native token symbol, eg ETH, BNB, MATIC */ token?: TokenSymbol + /** + * An optional array of tokens (max of 5) to be available to the dapp in the + * app state object per wallet within the wallet account and displayed + * in Account Center (if enabled) + */ + secondaryTokens?: SecondaryTokens[] /** * The color used to represent the chain and * will be used as a background for the icon @@ -419,6 +425,19 @@ export interface Chain { blockExplorerUrl?: string } +export interface SecondaryTokens { + /** + * Required - The onchain address of the token associated + * with the chain it is entered under + */ + address: string + /** + * An optional svg or url string for the icon of the token. + * If an svg is used ensure the height/width is set to 100% + */ + icon?: string +} + export type ChainWithDecimalId = Omit & { id: DecimalChainId } export type TokenSymbol = string // eg ETH diff --git a/packages/common/src/validation.ts b/packages/common/src/validation.ts index 5b29b612c..a3be7c208 100644 --- a/packages/common/src/validation.ts +++ b/packages/common/src/validation.ts @@ -31,12 +31,21 @@ export const providerConnectionInfoValidation = Joi.object({ timeout: Joi.number() }) +const secondaryTokenValidation = Joi.object({ + address: Joi.string().required(), + icon: Joi.string().optional() +}) + export const chainValidation = Joi.object({ namespace: chainNamespaceValidation, id: chainIdValidation.required(), rpcUrl: Joi.string(), label: Joi.string(), token: Joi.string(), + secondaryTokens: Joi.array() + .max(5) + .items(secondaryTokenValidation) + .optional(), icon: Joi.string(), color: Joi.string(), publicRpcUrl: Joi.string(), diff --git a/packages/core/README.md b/packages/core/README.md index 75cfb9d11..078d6cd71 100644 --- a/packages/core/README.md +++ b/packages/core/README.md @@ -74,7 +74,7 @@ type InitOptions = { accountCenter?: AccountCenterOptions /** * Opt in to Blocknative value add services (transaction updates) by providing - * your Blocknative API key, head to https://explorer.blocknative.com/account to sign + * your Blocknative API key, head to https://explorer.blocknative.com/account to sign * up for free */ apiKey?: string @@ -132,6 +132,19 @@ type Chain = { icon?: string // the icon to represent the chain publicRpcUrl?: string // an optional public RPC used when adding a new chain config to the wallet blockExplorerUrl?: string // also used when adding a new config to the wallet + secondaryTokens?: SecondaryTokens[] // An optional array of tokens (max of 5) to be available to the dapp in the app state object per wallet within the wallet account and displayed in Account Center (if enabled) +} +interface SecondaryTokens { + /** + * Required - The onchain address of the token associated + * with the chain it is entered under + */ + address: string + /** + * An optional svg or url string for the icon of the token. + * If an svg is used ensure the height/width is set to 100% + */ + icon?: string } ``` @@ -257,18 +270,18 @@ type i18nOptions = Record To see a list of all of the text values that can be internationalized or replaced, check out the [default en file](src/i18n/en.json). Onboard is using the [ICU syntax](https://formatjs.io/docs/core-concepts/icu-syntax/) for formatting under the hood. -For example, to update the connect interface language for Metamask, while giving a different message for other wallets, you can include the following: +For example, to update the connect interface language for Metamask, while giving a different message for other wallets, you can include the following: ```typescript i18n: { - en: { - connect: { - connectingWallet: { - paragraph: "{wallet, select, MetaMask {{wallet} can only present one account, so connect just the one account you want.} other {Please connect to all of your accounts in {wallet}.}}" - } - } + en: { + connect: { + connectingWallet: { + paragraph: '{wallet, select, MetaMask {{wallet} can only present one account, so connect just the one account you want.} other {Please connect to all of your accounts in {wallet}.}}' } } + } +} ``` MetaMask message: @@ -277,10 +290,9 @@ MetaMask message: All other wallets: -Default Message- with no i18n override: +Default Message- with no i18n override: - **`containerElements`** An object mapping for W3O components with the key being the DOM element to mount the specified component to. This defines the DOM container element for svelte to attach the component. @@ -304,6 +316,8 @@ type ContainerElements = { **`accountCenter`** An object that defines whether the account center UI (default and minimal) is enabled and it's position on the screen. Currently the account center is enabled for both desktop and mobile devices. +Account Center UI Component + ```typescript type AccountCenter = { enabled: boolean @@ -384,7 +398,7 @@ If notifications are enabled, they can be fielded and handled through the onboar ```javascript const wallets = onboard.state.select('notifications') -const { unsubscribe } = wallets.subscribe((update) => +const { unsubscribe } = wallets.subscribe(update => console.log('transaction notifications: ', update) ) @@ -393,12 +407,12 @@ unsubscribe() ``` ##### **Notifications as Toast Messages** -The Notifications messages can also be used to send fully customized Dapp toast messages and updated. Check out the [customNotifications API docs for examples and code snippets](#customnotification) +The Notifications messages can also be used to send fully customized Dapp toast messages and updated. Check out the [customNotifications API docs for examples and code snippets](#customnotification) ```javascript const wallets = onboard.state.select('notifications') -const { unsubscribe } = wallets.subscribe((update) => +const { unsubscribe } = wallets.subscribe(update => console.log('transaction notifications: ', update) ) @@ -419,7 +433,9 @@ type Notify = { * Or return false to disable notification for this event * Or return undefined for a default notification */ - transactionHandler?: (event: EthereumTransactionData) => TransactionHandlerReturn + transactionHandler?: ( + event: EthereumTransactionData + ) => TransactionHandlerReturn position: CommonPositions } @@ -610,6 +626,7 @@ const onboard = Onboard({ } }) ``` + --- ## Connecting a Wallet @@ -942,10 +959,9 @@ The `customNotification` method also returns a `dismiss` method that is called w | `link` | string | Adds a link to the transaction hash | | `onClick` | function | onClick handler for the notification element | - **`preflightNotifications`** -Notify can be used to deliver standard notifications along with preflight updates by passing a `PreflightNotificationsOptions` object to the `preflightNotifications` API action. +Notify can be used to deliver standard notifications along with preflight updates by passing a `PreflightNotificationsOptions` object to the `preflightNotifications` API action. Web3-Onboard UI Components @@ -962,6 +978,7 @@ Preflight event types include: This API call will return a promise that resolves to the transaction hash (if `sendTransaction` resolves the transaction hash and is successful), the internal notification id (if no `sendTransaction` function is provided) or return nothing if an error occurs or `sendTransaction` is not provided or doesn't resolve to a string. Example: + ```typescript copy const balanceValue = Object.values(balance)[0] // if using ethers v6 this is: @@ -975,13 +992,13 @@ const txDetails = { } const sendTransaction = () => { - return signer.sendTransaction(txDetails).then((tx) => tx.hash) + return signer.sendTransaction(txDetails).then(tx => tx.hash) } -const gasPrice = () => ethersProvider.getGasPrice().then((res) => res.toString()) +const gasPrice = () => ethersProvider.getGasPrice().then(res => res.toString()) const estimateGas = () => { - return ethersProvider.estimateGas(txDetails).then((res) => res.toString()) + return ethersProvider.estimateGas(txDetails).then(res => res.toString()) } const transactionHash = await onboard.state.actions.preflightNotifications({ sendTransaction, diff --git a/packages/core/package.json b/packages/core/package.json index f87e6a5f3..e6527ce71 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@web3-onboard/core", - "version": "2.17.0", + "version": "2.18.0-alpha.2", "description": "Web3-Onboard makes it simple to connect Ethereum hardware and software wallets to your dapp. Features standardized spec compliant web3 providers for all supported wallets, framework agnostic modern javascript UI with code splitting, CSS customization, multi-chain and multi-account support, reactive wallet state subscriptions and real-time transaction state change notifications.", "keywords": [ "Ethereum", @@ -85,7 +85,7 @@ "typescript": "^4.5.5" }, "dependencies": { - "@web3-onboard/common": "^2.3.1", + "@web3-onboard/common": "^2.3.2-alpha.2", "bignumber.js": "^9.0.0", "bnc-sdk": "^4.6.7", "bowser": "^2.11.0", diff --git a/packages/core/src/chain.ts b/packages/core/src/chain.ts index 4868cd22a..700079791 100644 --- a/packages/core/src/chain.ts +++ b/packages/core/src/chain.ts @@ -36,7 +36,9 @@ async function setChain(options: { // validate that chainId has been added to chains const chain = chains.find( - ({ namespace, id }) => namespace === chainNamespace && id === chainIdHex + ({ namespace, id }) => + namespace === chainNamespace && + id.toLowerCase() === chainIdHex.toLowerCase() ) if (!chain) { diff --git a/packages/core/src/i18n/en.json b/packages/core/src/i18n/en.json index df2740cc7..2652484d0 100644 --- a/packages/core/src/i18n/en.json +++ b/packages/core/src/i18n/en.json @@ -67,7 +67,6 @@ "gettingStartedGuide": "Getting Started Guide", "smartContracts": "Smart Contract(s)", "explore": "Explore", - "backToApp": "Back to dapp", "poweredBy": "powered by", "addAccount": "Add Account", "setPrimaryAccount": "Set Primary Account", diff --git a/packages/core/src/icons/blocknative-icon.ts b/packages/core/src/icons/blocknative-icon.ts index a85040b7c..91ca1cb81 100644 --- a/packages/core/src/icons/blocknative-icon.ts +++ b/packages/core/src/icons/blocknative-icon.ts @@ -22,5 +22,4 @@ export default ` - ` diff --git a/packages/core/src/provider.ts b/packages/core/src/provider.ts index 1cf107c2f..3f5e018f2 100644 --- a/packages/core/src/provider.ts +++ b/packages/core/src/provider.ts @@ -32,6 +32,7 @@ import type { } from './types.js' import type { Uns } from '@web3-onboard/unstoppable-resolution' +import { updateSecondaryTokens } from './update-balances' export const ethersProviders: { [key: string]: providers.StaticJsonRpcProvider @@ -192,9 +193,8 @@ export function trackWallet( const { wallets, chains } = state.get() - const { chains: walletChains, accounts } = wallets.find( - wallet => wallet.label === label - ) + const primaryWallet = wallets.find(wallet => wallet.label === label) + const { chains: walletChains, accounts } = primaryWallet const [connectedWalletChain] = walletChains @@ -204,6 +204,11 @@ export function trackWallet( ) const balanceProm = getBalance(address, chain) + const secondaryTokenBal = updateSecondaryTokens( + primaryWallet, + address, + chain + ) const account = accounts.find(account => account.address === address) const ensProm = @@ -222,14 +227,15 @@ export function trackWallet( Promise.resolve(address), balanceProm, ensProm, - unsProm + unsProm, + secondaryTokenBal ]) }) ) .subscribe(res => { if (!res) return - const [address, balance, ens, uns] = res - updateAccount(label, address, { balance, ens, uns }) + const [address, balance, ens, uns, secondaryTokens] = res + updateAccount(label, address, { balance, ens, uns, secondaryTokens }) }) const chainChanged$ = listenChainChanged({ provider, disconnected$ }).pipe( @@ -298,7 +304,8 @@ export function trackWallet( .pipe( switchMap(async chainId => { const { wallets, chains } = state.get() - const { accounts } = wallets.find(wallet => wallet.label === label) + const primaryWallet = wallets.find(wallet => wallet.label === label) + const { accounts } = primaryWallet const chain = chains.find( ({ namespace, id }) => namespace === 'evm' && id === chainId @@ -308,6 +315,12 @@ export function trackWallet( accounts.map(async ({ address }) => { const balanceProm = getBalance(address, chain) + const secondaryTokenBal = updateSecondaryTokens( + primaryWallet, + address, + chain + ) + const ensProm = validEnsChain(chainId) ? getEns(address, chain) : Promise.resolve(null) @@ -316,17 +329,19 @@ export function trackWallet( ? getUns(address, chain) : Promise.resolve(null) - const [balance, ens, uns] = await Promise.all([ + const [balance, ens, uns, secondaryTokens] = await Promise.all([ balanceProm, ensProm, - unsProm + unsProm, + secondaryTokenBal ]) return { address, balance, ens, - uns + uns, + secondaryTokens } }) ) diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index ebe325be5..9b71717a6 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -142,10 +142,17 @@ export type Account = { ens: Ens | null uns: Uns | null balance: Balances | null + secondaryTokens?: SecondaryTokenBalances[] | null } export type Balances = Record | null +export interface SecondaryTokenBalances { + name: TokenSymbol + balance: string + icon?: string +} + export interface Ens { name: string avatar: Avatar | null diff --git a/packages/core/src/update-balances.ts b/packages/core/src/update-balances.ts index 6433455e3..1c1d9792e 100644 --- a/packages/core/src/update-balances.ts +++ b/packages/core/src/update-balances.ts @@ -1,6 +1,9 @@ import { state } from './store/index.js' import { getBalance } from './provider.js' import { updateAllWallets } from './store/actions.js' +import { ethers } from 'ethers' +import { AccountAddress, Chain, weiToEth } from '@web3-onboard/common' +import type { SecondaryTokenBalances, WalletState } from './types' async function updateBalances(addresses?: string[]): Promise { const { wallets, chains } = state.get() @@ -10,6 +13,11 @@ async function updateBalances(addresses?: string[]): Promise { const updatedAccounts = await Promise.all( wallet.accounts.map(async account => { + const secondaryTokens = await updateSecondaryTokens( + wallet, + account.address, + chain + ) // if no provided addresses, we want to update all balances // otherwise check if address is in addresses array if ( @@ -19,16 +27,67 @@ async function updateBalances(addresses?: string[]): Promise { ) ) { const updatedBalance = await getBalance(account.address, chain) - return { ...account, balance: updatedBalance } + return { ...account, balance: updatedBalance, secondaryTokens } } - return account + return { ...account, secondaryTokens } }) ) return { ...wallet, accounts: updatedAccounts } }) ) - updateAllWallets(updatedWallets) } +export const updateSecondaryTokens = async ( + wallet: WalletState, + account: AccountAddress, + chain: Chain +): Promise => { + const chainRPC = chain.rpcUrl + if (!chain.secondaryTokens || !chain.secondaryTokens.length || !chainRPC) + return + const ethersProvider = new ethers.providers.Web3Provider( + wallet.provider, + 'any' + ) + const signer = ethersProvider.getSigner() + + const abi = [ + { + inputs: [{ name: 'owner', type: 'address' }], + name: 'balanceOf', + outputs: [{ name: '', type: 'uint256' }], + stateMutability: 'view', + type: 'function' + }, + { + inputs: [], + name: 'symbol', + outputs: [{ name: '', type: 'string' }], + stateMutability: 'view', + type: 'function' + } + ] + const updatedBalances = await Promise.all( + chain.secondaryTokens.map(async token => { + try { + const swapContract = new ethers.Contract(token.address, abi, signer) + const bigNumBalance = await swapContract.balanceOf(account) + const tokenName = await swapContract.symbol() + return { + name: tokenName, + balance: weiToEth(bigNumBalance.toHexString()), + icon: token.icon + } + } catch (error) { + console.error( + `There was an error fetching balance and/or symbol + for token contract: ${token.address} - ${error}` + ) + } + }) + ) + return updatedBalances +} + export default updateBalances diff --git a/packages/core/src/validation.ts b/packages/core/src/validation.ts index d3591633e..3ec78a1ca 100644 --- a/packages/core/src/validation.ts +++ b/packages/core/src/validation.ts @@ -61,11 +61,20 @@ const balance = Joi.any().allow( null ) +const secondaryTokens = Joi.any().allow( + Joi.object({ + balance: Joi.string().required(), + icon: Joi.string() + }), + null +) + const account = Joi.object({ address: Joi.string().required(), ens, uns, - balance + balance, + secondaryTokens }) const chains = Joi.array() @@ -242,13 +251,19 @@ const disconnectOptions = Joi.object({ label: Joi.string().required() }).required() +const secondaryTokenValidation = Joi.object({ + address: Joi.string().required(), + icon: Joi.string().optional() +}) + const setChainOptions = Joi.object({ chainId: chainIdValidation.required(), chainNamespace: chainNamespaceValidation, wallet: Joi.string(), rpcUrl: Joi.string(), label: Joi.string(), - token: Joi.string() + token: Joi.string(), + secondaryTokens: Joi.array().max(5).items(secondaryTokenValidation).optional() }) const customNotificationUpdate = Joi.object({ diff --git a/packages/core/src/views/account-center/Maximized.svelte b/packages/core/src/views/account-center/Maximized.svelte index e4e98e124..f31a3823b 100644 --- a/packages/core/src/views/account-center/Maximized.svelte +++ b/packages/core/src/views/account-center/Maximized.svelte @@ -11,14 +11,18 @@ import disconnect from '../../disconnect.js' import { state } from '../../store/index.js' import { getDefaultChainStyles, unrecognizedChainStyle } from '../../utils.js' - import { NetworkSelector, SuccessStatusIcon, WalletAppBadge } from '../shared/index.js' + import { + NetworkSelector, + SuccessStatusIcon, + WalletAppBadge + } from '../shared/index.js' import caretLightIcon from '../../icons/caret-light.js' import warningIcon from '../../icons/warning.js' import questionIcon from '../../icons/question.js' import { poweredByBlocknative } from '../../icons/index.js' - import { updateAccountCenter } from '../../store/actions.js' import DisconnectAllConfirm from './DisconnectAllConfirm.svelte' import { configuration } from '../../configuration.js' + import SecondaryTokenTable from './SecondaryTokenTable.svelte' function disconnectAllWallets() { $wallets$.forEach(({ label }) => disconnect({ label })) @@ -31,6 +35,10 @@ $: [primaryWallet] = $wallets$ $: [connectedChain] = primaryWallet ? primaryWallet.chains : [] + $: secondaryTokens = + primaryWallet && + primaryWallet.accounts.length && + primaryWallet.accounts[0].secondaryTokens $: validAppChain = appChains.find(({ id, namespace }) => connectedChain @@ -60,7 +68,10 @@ overflow: hidden; pointer-events: auto; border: 1px solid transparent; - background: var(--account-center-maximized-upper-background, var(--background-color)); + background: var( + --account-center-maximized-upper-background, + var(--background-color) + ); border-color: var(--border-color); border-radius: var(--account-center-border-radius, var(--border-radius)); } @@ -81,13 +92,16 @@ } .actions { - color: var(--account-center-maximized-upper-action-color, var(--action-color)); + color: var( + --account-center-maximized-upper-action-color, + var(--action-color) + ); padding-left: 2px; } .action-container { - padding: 4px 12px 4px 8px; - border-radius: 8px; + padding: 0.25rem 12px 0.25rem 0.5rem; + border-radius: 0.5rem; transition: background-color 150ms ease-in-out; } @@ -162,12 +176,34 @@ .app-info-container { color: var(--text-color, var(--gray-700)); - background: var(--account-center-maximized-info-section-background-color, - var(--account-center-maximized-info-section, var(--background-color, #FFF)) + background: var( + --account-center-maximized-info-section-background-color, + var( + --account-center-maximized-info-section, + var(--background-color, #fff) + ) ); border-top: 1px solid var(--border-color); border-radius: var(--account-center-border-radius, inherit); - padding: 12px; + display: flex; + flex-direction: column; + align-items: flex-start; + padding: 0px; + } + + .app-info-header { + width: 100%; + flex-direction: column; + align-items: flex-start; + padding: 0.75rem; + gap: 0.5rem; + border-bottom: 1px solid var(--border-color); + } + .app-icon-name { + display: flex; + align-items: center; + flex-direction: row; + gap: 0.75rem; } .app-name { @@ -183,42 +219,41 @@ font-size: var(--onboard-font-size-7, var(--font-size-7)); line-height: var(--onboard-font-line-height-3, var(--font-line-height-3)); color: var(--account-center-maximized-app-info-color, inherit); + display: flex; + flex-direction: row; + align-items: flex-start; + padding: 0px 0.25rem; + gap: 1rem; } .app-info { + width: 100%; font-size: var(--onboard-font-size-7, var(--font-size-7)); line-height: var(--onboard-font-line-height-3, var(--font-line-height-3)); color: var(--account-center-maximized-app-info-color, inherit); + border-bottom: 1px solid var(--border-color); + display: flex; + flex-direction: column; + align-items: flex-start; + padding: 0.5rem 1rem; + gap: 0.25rem; } .app-info-heading { - font-weight: 600; - margin-top: var(--onboard-spacing-5, var(--spacing-5)); - margin-bottom: var(--onboard-spacing-7, var(--spacing-7)); + font-weight: 700; color: var(--account-center-maximized-app-info-color, inherit); } - a { - font-weight: 600; - } - - .mt7 { - margin-top: var(--onboard-spacing-7, var(--spacing-7)); - } - - .ml4 { - margin-left: var(--onboard-spacing-4, var(--spacing-4)); + .w100 { + width: 100%; } - .app-button { - font-family: var(--account-center-app-btn-font-family, inherit); - margin-top: var(--onboard-spacing-5, var(--spacing-5)); - color: var(--account-center-app-btn-text-color, var(--background-color, #FFF)); - background: var(--account-center-app-btn-background, var(--action-color)); + a { + font-weight: 700; } .powered-by-container { - margin-top: 12px; color: var(--text-color); + padding: 0.75rem; } @@ -355,94 +390,91 @@
-
- -
- -
- -
-
- {(appMetadata && appMetadata.name) || 'App Name'} + {#if appMetadata} +
+ +
+ +
+ {(appMetadata && appMetadata.name) || 'App Name'} +
+
{(appMetadata && appMetadata.description) || 'This app has not added a description.'}
-
- - {#if appMetadata && (appMetadata.gettingStartedGuide || appMetadata.explore)} -
-

- {$_('accountCenter.appInfo', { - default: en.accountCenter.appInfo - })} -

- - {#if appMetadata.gettingStartedGuide} -
-
- {$_('accountCenter.learnMore', { - default: en.accountCenter.learnMore - })} -
- - {$_('accountCenter.gettingStartedGuide', { - default: en.accountCenter.gettingStartedGuide - })} - + + {#if appMetadata.gettingStartedGuide || appMetadata.explore} +
+
+ {$_('accountCenter.appInfo', { + default: en.accountCenter.appInfo + })}
- {/if} - - {#if appMetadata.explore} -
-
- {$_('accountCenter.smartContracts', { - default: en.accountCenter.smartContracts - })} + + {#if appMetadata.gettingStartedGuide} +
+
+ {$_('accountCenter.learnMore', { + default: en.accountCenter.learnMore + })} +
+ + {$_('accountCenter.gettingStartedGuide', { + default: en.accountCenter.gettingStartedGuide + })} +
- - {$_('accountCenter.explore', { - default: en.accountCenter.explore - })} - -
- {/if} -
+ {/if} + + {#if appMetadata.explore} +
+
+ {$_('accountCenter.smartContracts', { + default: en.accountCenter.smartContracts + })} +
+ + {$_('accountCenter.explore', { + default: en.accountCenter.explore + })} + +
+ {/if} +
+ {/if} {/if} - - - - {@html poweredByBlocknative} - + {#if secondaryTokens && secondaryTokens.length} + + {/if} +
diff --git a/packages/core/src/views/account-center/Minimized.svelte b/packages/core/src/views/account-center/Minimized.svelte index 8f18d2a07..26ff1ea77 100644 --- a/packages/core/src/views/account-center/Minimized.svelte +++ b/packages/core/src/views/account-center/Minimized.svelte @@ -197,8 +197,8 @@
{#if firstAddressBalance}
- {firstAddressBalance.length > 8 - ? firstAddressBalance.slice(0, 8) + {firstAddressBalance.length > 7 + ? firstAddressBalance.slice(0, 7) : firstAddressBalance} {firstAddressAsset}
diff --git a/packages/core/src/views/account-center/SecondaryTokenTable.svelte b/packages/core/src/views/account-center/SecondaryTokenTable.svelte new file mode 100644 index 000000000..15b86f62e --- /dev/null +++ b/packages/core/src/views/account-center/SecondaryTokenTable.svelte @@ -0,0 +1,101 @@ + + + + +
+ + + + + + + + {#each secondaryTokens as token} + {#if token && token.name && token.balance} + + + + + {/if} + {/each} + +
Token Balances:
+
+ {#if token.icon} + {#await token.icon then iconLoaded} +
+ {#if isSVG(iconLoaded)} + + {@html iconLoaded} + {:else} + + logo + {/if} +
+ {/await} + {:else} +
+ {/if} + {token.name.toUpperCase()} +
+
+ {token.balance.length > 7 + ? token.balance.slice(0, 7) + : token.balance} +
+
diff --git a/packages/core/src/views/connect/Index.svelte b/packages/core/src/views/connect/Index.svelte index 1824607c3..c3d954775 100644 --- a/packages/core/src/views/connect/Index.svelte +++ b/packages/core/src/views/connect/Index.svelte @@ -53,6 +53,7 @@ WalletState, WalletWithLoadingIcon } from '../../types.js' + import { updateSecondaryTokens } from '../../update-balances' export let autoSelect: ConnectOptions['autoSelect'] @@ -330,7 +331,7 @@ ) const { address } = accounts[0] - let { balance, ens, uns } = accounts[0] + let { balance, ens, uns, secondaryTokens } = accounts[0] if (balance === null) { getBalance(address, appChain).then(balance => { @@ -339,6 +340,19 @@ }) }) } + if ( + !secondaryTokens && + Array.isArray(appChain.secondaryTokens) && + appChain.secondaryTokens.length + ) { + updateSecondaryTokens(selectedWallet, address, appChain).then( + secondaryTokens => { + updateAccount(selectedWallet.label, address, { + secondaryTokens + }) + } + ) + } if (ens === null && validEnsChain(connectedWalletChain.id)) { getEns(address, appChain).then(ens => { diff --git a/packages/dcent/package.json b/packages/dcent/package.json index b9e56fd18..3458df221 100644 --- a/packages/dcent/package.json +++ b/packages/dcent/package.json @@ -1,6 +1,6 @@ { "name": "@web3-onboard/dcent", - "version": "2.2.5", + "version": "2.2.6-alpha.2", "description": "D'CENT wallet module for connecting to Web3-Onboard. Web3-Onboard makes it simple to connect Ethereum hardware and software wallets to your dapp. Features standardised spec compliant web3 providers for all supported wallets, framework agnostic modern javascript UI with code splitting, CSS customization, multi-chain and multi-account support, reactive wallet state subscriptions and real-time transaction state change notifications.", "keywords": [ "Ethereum", @@ -56,8 +56,8 @@ "typescript": "^4.5.5" }, "dependencies": { - "@web3-onboard/common": "^2.3.1", - "@web3-onboard/hw-common": "^2.2.1", + "@web3-onboard/common": "^2.3.2-alpha.2", + "@web3-onboard/hw-common": "^2.2.2-alpha.2", "@ethereumjs/tx": "^3.4.0", "@ethersproject/providers": "^5.5.0", "eth-dcent-keyring": "^0.2.2" diff --git a/packages/demo/package.json b/packages/demo/package.json index 6c44aec0d..bd1e84321 100644 --- a/packages/demo/package.json +++ b/packages/demo/package.json @@ -23,36 +23,35 @@ "webpack-dev-server": "4.7.4" }, "dependencies": { - "@web3-onboard/core": "^2.17.0", - "@web3-onboard/coinbase": "^2.2.2", - "@web3-onboard/transaction-preview": "^2.0.6", - "@web3-onboard/dcent": "^2.2.5", - "@web3-onboard/frontier": "^2.0.2", - "@web3-onboard/fortmatic": "^2.0.17", - "@web3-onboard/gas": "^2.1.6", - "@web3-onboard/gnosis": "^2.1.8", - "@web3-onboard/keepkey": "^2.3.5", - "@web3-onboard/keystone": "^2.3.5", - "@web3-onboard/ledger": "^2.4.4", - "@web3-onboard/infinity-wallet": "^2.0.2", - "@web3-onboard/injected-wallets": "^2.8.4", - "@web3-onboard/magic": "^2.1.5", - "@web3-onboard/phantom": "^2.0.0-alpha.4", - "@web3-onboard/portis": "^2.1.5", - "@web3-onboard/sequence": "^2.0.6", - "@web3-onboard/trezor": "^2.4.0", - "@web3-onboard/trust": "^2.0.2", - "@web3-onboard/torus": "^2.2.3", - "@web3-onboard/taho": "^2.0.2", - "@web3-onboard/unstoppable-resolution": "^2.0.0", - "@web3-onboard/web3auth": "^2.2.1", - "@web3-onboard/walletconnect": "^2.3.6", - "@web3-onboard/enkrypt": "^2.0.2", - "@web3-onboard/mew-wallet": "^2.0.1", - "@web3-onboard/xdefi": "^2.0.2", - "@web3-onboard/uauth": "^2.0.3", - "@web3-onboard/zeal": "^2.0.2", - "@web3-onboard/cede-store": "^2.0.0-alpha.1", + "@web3-onboard/core": "^2.18.0-alpha.2", + "@web3-onboard/coinbase": "^2.2.3-alpha.2", + "@web3-onboard/transaction-preview": "^2.0.7-alpha.2", + "@web3-onboard/dcent": "^2.2.6-alpha.2", + "@web3-onboard/frontier": "^2.0.3-alpha.2", + "@web3-onboard/fortmatic": "^2.0.18-alpha.2", + "@web3-onboard/gas": "^2.1.7-alpha.2", + "@web3-onboard/gnosis": "^2.1.9-alpha.2", + "@web3-onboard/keepkey": "^2.3.6-alpha.2", + "@web3-onboard/keystone": "^2.3.6-alpha.2", + "@web3-onboard/ledger": "^2.4.5-alpha.2", + "@web3-onboard/infinity-wallet": "^2.0.3-alpha.2", + "@web3-onboard/injected-wallets": "^2.8.5-alpha.2", + "@web3-onboard/magic": "^2.1.6-alpha.2", + "@web3-onboard/phantom": "^2.0.1-alpha.2", + "@web3-onboard/portis": "^2.1.6-alpha.2", + "@web3-onboard/sequence": "^2.0.7-alpha.2", + "@web3-onboard/trezor": "^2.4.1-alpha.2", + "@web3-onboard/trust": "^2.0.3-alpha.2", + "@web3-onboard/torus": "^2.2.4-alpha.2", + "@web3-onboard/taho": "^2.0.3-alpha.2", + "@web3-onboard/web3auth": "^2.2.2-alpha.2", + "@web3-onboard/walletconnect": "^2.3.7-alpha.3", + "@web3-onboard/enkrypt": "^2.0.3-alpha.2", + "@web3-onboard/mew-wallet": "^2.0.2-alpha.2", + "@web3-onboard/xdefi": "^2.0.3-alpha.2", + "@web3-onboard/uauth": "^2.0.4-alpha.2", + "@web3-onboard/zeal": "^2.0.3-alpha.2", + "@web3-onboard/cede-store": "^2.0.1-alpha.2", "vconsole": "^3.9.5" }, "license": "MIT", diff --git a/packages/demo/src/App.svelte b/packages/demo/src/App.svelte index b28eca0ce..e0374a864 100644 --- a/packages/demo/src/App.svelte +++ b/packages/demo/src/App.svelte @@ -15,7 +15,6 @@ import magicModule from '@web3-onboard/magic' import web3authModule from '@web3-onboard/web3auth' import gas from '@web3-onboard/gas' - import unstoppableResolution from '@web3-onboard/unstoppable-resolution' import dcentModule from '@web3-onboard/dcent' import sequenceModule from '@web3-onboard/sequence' import tallyHoModule from '@web3-onboard/tallyho' @@ -38,6 +37,7 @@ import { ethers } from 'ethers' import { share } from 'rxjs/operators' import VConsole from 'vconsole' + import blocknativeIcon from './blocknative-icon.js' if (window.innerWidth < 700) { new VConsole() @@ -110,7 +110,14 @@ handleUri: uri => console.log(uri), projectId: 'f6bd6e2911b56f5ac3bc8b2d0e2d7ad5', qrcodeModalOptions: { - mobileLinks: ['rainbow', 'metamask', 'argent', 'trust', 'imtoken', 'pillar'] + mobileLinks: [ + 'rainbow', + 'metamask', + 'argent', + 'trust', + 'imtoken', + 'pillar' + ] } }) const portis = portisModule({ @@ -201,11 +208,23 @@ ], transactionPreview, gas, - unstoppableResolution, chains: [ { id: '0x1', token: 'ETH', + secondaryTokens: [ + { + address: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', + icon: ` + + + ` + }, + { + address: '0x111111111117dc0aa78b770fa6a738034120c302', + icon: `https://avatars.githubusercontent.com/u/43341157` + } + ], label: 'Ethereum', rpcUrl: `https://mainnet.infura.io/v3/${infura_key}` }, @@ -231,7 +250,17 @@ id: '0x38', token: 'BNB', label: 'Binance', - rpcUrl: 'https://bsc-dataseed.binance.org/' + rpcUrl: 'https://bsc-dataseed.binance.org/', + secondaryTokens: [ + { + address: '0x4d61577d8fd2208a0afb814ea089fdeae19ed202', + icon: `https://assets.coingecko.com/coins/images/15363/small/vfox2.png?1629870083` + }, + { + address: '0xde2f075f6f14eb9d96755b24e416a53e736ca363', + icon: `https://assets.coingecko.com/coins/images/13423/small/frax_share.png?1608478989` + } + ] }, { id: '0x89', @@ -259,7 +288,7 @@ }, appMetadata: { name: 'Blocknative', - // icon: blocknativeIcon, + icon: blocknativeIcon, // logo: blocknativeLogo, description: 'Demo app for Onboard V2', recommendedInjectedWallets: [ @@ -287,7 +316,8 @@ en: { connect: { connectingWallet: { - paragraph: "{wallet, select, MetaMask {{wallet} can only present one account, so connect just the one account you want.} other {Please connect to all of your accounts in {wallet}.}}" + paragraph: + '{wallet, select, MetaMask {{wallet} can only present one account, so connect just the one account you want.} other {Please connect to all of your accounts in {wallet}.}}' } } } @@ -639,7 +669,7 @@ -
@@ -648,13 +678,15 @@
{#if $wallets$} {#each $wallets$ as { icon, label, accounts, chains, provider, instance }} -
+
{@html icon}
- {label} + {label}
-
Chains: {JSON.stringify(chains, null, 2)}
+
+ Chains: {JSON.stringify(chains, null, 2)} +
{#each accounts as { address, ens, uns, balance }}