diff --git a/.changeset/forty-zoos-double.md b/.changeset/forty-zoos-double.md new file mode 100644 index 00000000..c3d8e7e5 --- /dev/null +++ b/.changeset/forty-zoos-double.md @@ -0,0 +1,20 @@ +--- +'@reown/appkit-scaffold-utils-react-native': major +'@reown/appkit-bitcoin-react-native': major +'@reown/appkit-ethers5-react-native': major +'@reown/appkit-react-native': major +'@reown/appkit-common-react-native': major +'@reown/appkit-ethers-react-native': major +'@reown/appkit-solana-react-native': major +'@reown/appkit-wagmi-react-native': major +'@reown/appkit-core-react-native': major +'@reown/appkit-siwe-react-native': major +'@reown/appkit-ui-react-native': major +'@reown/appkit-auth-ethers-react-native': major +'@reown/appkit-auth-wagmi-react-native': major +'@reown/appkit-coinbase-ethers-react-native': major +'@reown/appkit-coinbase-wagmi-react-native': major +'@reown/appkit-wallet-react-native': major +--- + +feat: added multichain support diff --git a/.changeset/slimy-apricots-complain.md b/.changeset/slimy-apricots-complain.md deleted file mode 100644 index 28137489..00000000 --- a/.changeset/slimy-apricots-complain.md +++ /dev/null @@ -1,18 +0,0 @@ ---- -'@reown/appkit-scaffold-react-native': minor -'@reown/appkit-common-react-native': minor -'@reown/appkit-core-react-native': minor -'@reown/appkit-siwe-react-native': minor -'@reown/appkit-ui-react-native': minor -'@reown/appkit-auth-ethers-react-native': minor -'@reown/appkit-auth-wagmi-react-native': minor -'@reown/appkit-coinbase-ethers-react-native': minor -'@reown/appkit-coinbase-wagmi-react-native': minor -'@reown/appkit-ethers-react-native': minor -'@reown/appkit-ethers5-react-native': minor -'@reown/appkit-scaffold-utils-react-native': minor -'@reown/appkit-wagmi-react-native': minor -'@reown/appkit-wallet-react-native': minor ---- - -feat: added onramp feature diff --git a/.github/scripts/publish-initial-versions.js b/.github/scripts/publish-initial-versions.js new file mode 100644 index 00000000..e7f2b6ac --- /dev/null +++ b/.github/scripts/publish-initial-versions.js @@ -0,0 +1,170 @@ +const { execSync, spawnSync } = require('child_process'); +const fs = require('fs'); +const path = require('path'); + +// Helper function to run commands and handle errors +function runCommand(command, args, options) { + console.log( + `Executing: ${command} ${args.join(' ')} ${options && options.cwd ? `in ${options.cwd}` : ''}` + ); + const result = spawnSync(command, args, { stdio: 'inherit', ...options }); + if (result.error) { + console.error(`Error executing ${command}:`, result.error); + throw result.error; + } + if (result.status !== 0) { + const message = `Command failed: ${command} ${args.join(' ')} exited with status ${ + result.status + }`; + console.error(message); + throw new Error(message); + } + return result; +} + +console.log('Starting initial package publishing process...'); + +let packagesToPublish = []; +const rootDir = process.cwd(); + +const packagesToExclude = ['@apps/native', '@apps/gallery', 'appkit-react-native']; + +try { + // Get workspace info using yarn workspaces list --json + // Yarn v1 outputs newline-delimited JSON objects + const rawOutput = execSync('yarn workspaces list --json', { encoding: 'utf8' }); + const lines = rawOutput + .trim() + .split('\n') + .filter(line => line.trim() !== ''); + const workspacePackages = lines.map(line => JSON.parse(line)); + + for (const pkgData of workspacePackages) { + console.log(`[DEBUG] Processing workspace entry: ${JSON.stringify(pkgData)}`); + + // Skip the root package (identified by location '.') or any package without a defined location + if (pkgData.location === '.' || !pkgData.location) { + console.log( + `[DEBUG] Skipping root or undefined location package: ${pkgData.name} at ${pkgData.location}` + ); + continue; + } + + // Skip excluded packages + if (packagesToExclude.includes(pkgData.name)) { + console.log(`Skipping excluded package: ${pkgData.name}`); + continue; + } + + const pkgName = pkgData.name; + const pkgDir = path.resolve(rootDir, pkgData.location); + + // Check if package exists on npm + console.log(`Checking NPM status for ${pkgName}...`); + const npmViewResult = spawnSync('npm', ['view', pkgName, 'version'], { encoding: 'utf8' }); + + // If npm view exits with 0 and has output, package exists. + // Otherwise (non-zero exit or empty output), it likely doesn't. + if (npmViewResult.status === 0 && npmViewResult.stdout && npmViewResult.stdout.trim() !== '') { + console.log( + `Package ${pkgName} (version: ${npmViewResult.stdout.trim()}) already exists on NPM. Skipping initial publish.` + ); + } else { + console.log( + `Package ${pkgName} does not appear to exist on NPM or has no published versions.` + ); + if (fs.existsSync(path.join(pkgDir, 'package.json'))) { + const packageJsonContent = fs.readFileSync(path.join(pkgDir, 'package.json'), 'utf8'); + const parsedPackageJson = JSON.parse(packageJsonContent); + console.log( + `[DEBUG] package.json for ${pkgName}: private=${parsedPackageJson.private}, version=${parsedPackageJson.version}` + ); // Added for debugging + packagesToPublish.push({ name: pkgName, dir: pkgDir }); + } else { + console.warn(`Skipping ${pkgName}: package.json not found in ${pkgDir}`); + } + } + } +} catch (error) { + console.error('Error processing workspace info or checking NPM status:', error.message); + process.exit(1); // Critical error, exit +} + +if (packagesToPublish.length === 0) { + console.log('No new packages to publish initially.'); +} else { + console.log( + `Found ${packagesToPublish.length} new package(s) to publish initially: ${packagesToPublish + .map(p => p.name) + .join(', ')}` + ); + + // Conditionally run changeset:prepublish if there are packages to publish + if (packagesToPublish.length > 0) { + console.log('New packages found. Running changeset:prepublish to build packages...'); + try { + runCommand('yarn', ['run', 'changeset:prepublish']); // Assumes it runs from rootDir + console.log('changeset:prepublish completed successfully.'); + } catch (prepublishError) { + console.error('Failed to run changeset:prepublish:', prepublishError.message); + process.exit(1); // Exit if build fails, as publishing would also fail + } + } +} + +let hasPublishErrors = false; +for (const pkg of packagesToPublish) { + console.log(`[DEBUG] Attempting to publish from list: ${JSON.stringify(pkg)}`); // Added for debugging + console.log(`Attempting to publish ${pkg.name} from ${pkg.dir} with alpha tag...`); + const packageJsonPath = path.join(pkg.dir, 'package.json'); + let originalPackageJson = ''; + try { + originalPackageJson = fs.readFileSync(packageJsonPath, 'utf8'); + const parsedPackageJson = JSON.parse(originalPackageJson); + + if (parsedPackageJson.private === true) { + console.log(`Package ${pkg.name} is private, skipping initial publish.`); + continue; // Skip to the next package + } + + console.log(`Temporarily setting version of ${pkg.name} to 0.0.1 for initial publish.`); + parsedPackageJson.version = '0.0.1'; + fs.writeFileSync(packageJsonPath, JSON.stringify(parsedPackageJson, null, 2)); + + runCommand('yarn', ['npm', 'publish', '--access', 'public', '--tag', 'alpha'], { + cwd: pkg.dir + }); + // console.log( + // `DRY RUN: Would publish ${pkg.name} from ${pkg.dir} with version 0.0.1 and alpha tag.` + // ); + // console.log( + // `DRY RUN: Command would be: yarn npm publish --access public --tag alpha (in ${pkg.dir})` + // ); + } catch (publishError) { + // runCommand already logs error details if it's from there + console.error(`Failed to publish ${pkg.name}: ${publishError.message}`); + hasPublishErrors = true; // Mark that an error occurred but continue trying other packages + } finally { + // Restore original package.json + if (originalPackageJson) { + console.log(`Restoring original package.json for ${pkg.name}.`); + try { + fs.writeFileSync(packageJsonPath, originalPackageJson); + } catch (restoreError) { + console.error( + `CRITICAL: Failed to restore original package.json for ${pkg.name}: ${restoreError.message}` + ); + // This is a more critical error, as it leaves the repo in a modified state. + // Depending on desired behavior, you might want to ensure this error is highly visible + // or even causes the entire workflow to fail more loudly. + hasPublishErrors = true; // Ensure the overall process is marked as failed. + } + } + } +} + +console.log('Initial package publishing process finished.'); +if (hasPublishErrors) { + console.error('One or more packages failed during initial publishing.'); + process.exit(1); // Exit with error if any package failed to publish +} diff --git a/.github/workflows/changesets.yml b/.github/workflows/changesets.yml index 64bb4382..d3eddd70 100644 --- a/.github/workflows/changesets.yml +++ b/.github/workflows/changesets.yml @@ -21,7 +21,7 @@ jobs: - name: Checkout Repo uses: actions/checkout@v4 - - name: + - name: Setup Environment uses: ./.github/actions/setup - name: Create Release Pull Request or Publish to NPM diff --git a/.github/workflows/snapshot.yml b/.github/workflows/snapshot.yml index b8d1ab43..cca20af5 100644 --- a/.github/workflows/snapshot.yml +++ b/.github/workflows/snapshot.yml @@ -19,6 +19,15 @@ jobs: - name: Setup uses: ./.github/actions/setup + - name: Publish Initial Versions for New Packages + continue-on-error: false + env: + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + YARN_NPM_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + run: | + npm config set "//registry.npmjs.org/:_authToken" "$NPM_TOKEN" + node .github/scripts/publish-initial-versions.js + - name: Publish Snapshots continue-on-error: false env: diff --git a/apps/native/App.tsx b/apps/native/App.tsx index 12815a5f..06afa12a 100644 --- a/apps/native/App.tsx +++ b/apps/native/App.tsx @@ -1,30 +1,47 @@ -import { Platform, SafeAreaView, StyleSheet, useColorScheme } from 'react-native'; +import { SafeAreaView, StyleSheet, useColorScheme } from 'react-native'; import { StatusBar } from 'expo-status-bar'; import * as Clipboard from 'expo-clipboard'; import '@walletconnect/react-native-compat'; -import { WagmiProvider } from 'wagmi'; +// import { WagmiProvider } from 'wagmi'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import Toast from 'react-native-toast-message'; +// import { +// // AppKit, +// // AppKitButton, +// // NetworkButton, +// // createAppKit, +// WagmiAdapter, +// defaultWagmiConfig +// } from '@reown/appkit-wagmi-react-native'; + import { + AppKitProvider, + createAppKit, AppKit, AppKitButton, NetworkButton, - createAppKit, - defaultWagmiConfig -} from '@reown/appkit-wagmi-react-native'; - -import { authConnector } from '@reown/appkit-auth-wagmi-react-native'; -import { Text } from '@reown/appkit-ui-react-native'; + solana, + bitcoin +} from '@reown/appkit-react-native'; -import { siweConfig } from './src/utils/SiweUtils'; +// import { authConnector } from '@reown/appkit-auth-wagmi-react-native'; +import { Button, Text } from '@reown/appkit-ui-react-native'; -import { AccountView } from './src/views/AccountView'; -import { ActionsView } from './src/views/ActionsView'; -import { getCustomWallets } from './src/utils/misc'; -import { chains } from './src/utils/WagmiUtils'; +// import { siweConfig } from './src/utils/SiweUtils'; +// import { AccountView } from './src/views/AccountView'; +// import { chains } from './src/utils/WagmiUtils'; import { OpenButton } from './src/components/OpenButton'; import { DisconnectButton } from './src/components/DisconnectButton'; +// import { EthersAdapter } from '@reown/appkit-ethers-react-native'; +import { SolanaAdapter } from '@reown/appkit-solana-react-native'; +import { BitcoinAdapter } from '@reown/appkit-bitcoin-react-native'; +import { WagmiAdapter } from '@reown/appkit-wagmi-react-native'; +import { mainnet, polygon, avalanche } from 'viem/chains'; +import { ActionsView } from './src/views/ActionsView'; +import { WalletInfoView } from './src/views/WalletInfoView'; +import { EventsView } from './src/views/EventsView'; +import { WagmiProvider } from 'wagmi'; const projectId = process.env.EXPO_PUBLIC_PROJECT_ID ?? ''; @@ -45,70 +62,78 @@ const clipboardClient = { } }; -const auth = authConnector({ projectId, metadata }); +const queryClient = new QueryClient(); -const extraConnectors = Platform.select({ - ios: [auth], - android: [auth], - default: [] -}); +// const ethersAdapter = new EthersAdapter({ +// projectId +// }); -const wagmiConfig = defaultWagmiConfig({ - chains, +const wagmiAdapter = new WagmiAdapter({ projectId, - metadata, - extraConnectors + networks: [mainnet, polygon, avalanche] }); -const queryClient = new QueryClient(); +const solanaAdapter = new SolanaAdapter({ + projectId +}); -const customWallets = getCustomWallets(); +const bitcoinAdapter = new BitcoinAdapter({ + projectId +}); -createAppKit({ +const appKit = createAppKit({ projectId, - wagmiConfig, - siweConfig, - clipboardClient, - customWallets, - enableAnalytics: true, + adapters: [wagmiAdapter, solanaAdapter, bitcoinAdapter], metadata, + networks: [mainnet, polygon, avalanche, bitcoin, solana], + defaultChain: polygon, + clipboardClient, debug: true, - features: { - email: true, - socials: ['x', 'discord', 'apple'], - emailShowWallets: true, - swaps: true - // onramp: true - } + enableAnalytics: true + // siweConfig, + // features: { + // email: true, + // socials: ['x', 'discord', 'apple'], + // emailShowWallets: true, + // swaps: true, + // onramp: true + // } }); export default function Native() { const isDarkMode = useColorScheme() === 'dark'; return ( - - - - - - AppKit for React Native - - - - - - - - - - - + + + + + + + AppKit Multichain for React Native + + + + + + {/* */} + + + + + + + + + ); } @@ -127,9 +152,15 @@ const styles = StyleSheet.create({ marginBottom: 20 }, title: { - marginBottom: 30 + marginBottom: 10 }, button: { - marginVertical: 6 + marginVertical: 16 + }, + walletInfo: { + marginBottom: 10 + }, + events: { + marginTop: 30 } }); diff --git a/apps/native/app.json b/apps/native/app.json index af128602..95e1fe6d 100644 --- a/apps/native/app.json +++ b/apps/native/app.json @@ -20,12 +20,8 @@ "policy": "appVersion" }, "owner": "nacho.reown", - "assetBundlePatterns": [ - "**/*" - ], - "plugins": [ - "./expo-plugins/installed-wallets.js" - ], + "assetBundlePatterns": ["**/*"], + "plugins": ["./expo-plugins/installed-wallets.js"], "ios": { "buildNumber": "1", "bundleIdentifier": "com.walletconnect.web3modal.rnsdk", @@ -92,4 +88,4 @@ } } } -} \ No newline at end of file +} diff --git a/apps/native/babel.config.js b/apps/native/babel.config.js index 425262ac..cb69af52 100644 --- a/apps/native/babel.config.js +++ b/apps/native/babel.config.js @@ -1,11 +1,14 @@ const path = require('path'); const uipack = require('../../packages/ui/package.json'); const corepack = require('../../packages/core/package.json'); -const scaffoldpack = require('../../packages/scaffold/package.json'); const wagmipack = require('../../packages/wagmi/package.json'); +const etherspack = require('../../packages/ethers/package.json'); +const bitcoinpack = require('../../packages/bitcoin/package.json'); +const solanapack = require('../../packages/solana/package.json'); const authpack = require('../../packages/auth-wagmi/package.json'); const commonpack = require('../../packages/common/package.json'); const siwepack = require('../../packages/siwe/package.json'); +const appkitpack = require('../../packages/appkit/package.json'); module.exports = function (api) { api.cache(true); @@ -21,15 +24,14 @@ module.exports = function (api) { // For development, we want to alias the packages to the source [uipack.name]: path.join(__dirname, '../../packages/ui', uipack.source), [corepack.name]: path.join(__dirname, '../../packages/core', corepack.source), - [scaffoldpack.name]: path.join( - __dirname, - '../../packages/scaffold', - scaffoldpack.source - ), + [etherspack.name]: path.join(__dirname, '../../packages/ethers', etherspack.source), + [bitcoinpack.name]: path.join(__dirname, '../../packages/bitcoin', bitcoinpack.source), + [solanapack.name]: path.join(__dirname, '../../packages/solana', solanapack.source), [wagmipack.name]: path.join(__dirname, '../../packages/wagmi', wagmipack.source), [authpack.name]: path.join(__dirname, '../../packages/auth-wagmi', authpack.source), [commonpack.name]: path.join(__dirname, '../../packages/common', commonpack.source), - [siwepack.name]: path.join(__dirname, '../../packages/siwe', siwepack.source) + [siwepack.name]: path.join(__dirname, '../../packages/siwe', siwepack.source), + [appkitpack.name]: path.join(__dirname, '../../packages/appkit', appkitpack.source) } } ] diff --git a/apps/native/package.json b/apps/native/package.json index 50627158..6f02164e 100644 --- a/apps/native/package.json +++ b/apps/native/package.json @@ -19,15 +19,22 @@ "build:web": "expo export -p web" }, "dependencies": { + "@bitcoinerlab/secp256k1": "1.2.0", "@expo/metro-runtime": "~4.0.1", "@react-native-async-storage/async-storage": "2.1.2", "@react-native-community/netinfo": "11.4.1", "@reown/appkit-auth-wagmi-react-native": "1.2.3", + "@reown/appkit-bitcoin-react-native": "1.2.3", + "@reown/appkit-ethers-react-native": "1.2.3", + "@reown/appkit-react-native": "1.2.3", + "@reown/appkit-solana-react-native": "1.2.3", "@reown/appkit-wagmi-react-native": "1.2.3", "@tanstack/query-async-storage-persister": "^5.40.0", "@tanstack/react-query": "5.56.2", "@tanstack/react-query-persist-client": "5.56.2", - "@walletconnect/react-native-compat": "2.19.1", + "@walletconnect/react-native-compat": "2.20.2", + "bitcoinjs-lib": "7.0.0-rc.0", + "ethers": "6.13.5", "expo": "^52.0.38", "expo-application": "~6.0.2", "expo-clipboard": "~7.0.1", @@ -43,8 +50,8 @@ "react-native-web": "~0.19.13", "react-native-webview": "13.12.5", "uuid": "^11.1.0", - "viem": "2.23.10", - "wagmi": "2.14.13" + "viem": "2.28.3", + "wagmi": "2.15.1" }, "devDependencies": { "@babel/core": "^7.24.0", @@ -55,5 +62,9 @@ "babel-plugin-module-resolver": "^5.0.0", "gh-pages": "^6.2.0", "typescript": "~5.3.3" + }, + "resolutions": { + "@walletconnect/ethereum-provider": "2.20.2", + "@walletconnect/universal-provider": "2.20.2" } } diff --git a/apps/native/src/components/OpenButton.tsx b/apps/native/src/components/OpenButton.tsx index c79b2ed9..868fe6d0 100644 --- a/apps/native/src/components/OpenButton.tsx +++ b/apps/native/src/components/OpenButton.tsx @@ -1,6 +1,6 @@ import { StyleSheet } from 'react-native'; import { Button } from '@reown/appkit-ui-react-native'; -import { useAppKit } from '@reown/appkit-wagmi-react-native'; +import { useAppKit } from '@reown/appkit-react-native'; import { useAccount } from 'wagmi'; export function OpenButton() { diff --git a/apps/native/src/utils/BitcoinUtil.ts b/apps/native/src/utils/BitcoinUtil.ts new file mode 100644 index 00000000..7a3718bf --- /dev/null +++ b/apps/native/src/utils/BitcoinUtil.ts @@ -0,0 +1,206 @@ +import ecc from '@bitcoinerlab/secp256k1'; +import * as bitcoin from 'bitcoinjs-lib'; + +import * as bitcoinPSBTUtils from 'bitcoinjs-lib/src/cjs/psbt/psbtutils'; + +import type { CaipNetworkId } from '@reown/appkit'; +import { bitcoinTestnet as bitcoinTestnetNetwork } from '@reown/appkit-react-native'; + +bitcoin.initEccLib(ecc); + +export type SignPSBTResponse = { + /** + * The signed PSBT, string base64 encoded + */ + psbt: string; + /** + * The `string` transaction id of the broadcasted transaction or `undefined` if not broadcasted + */ + txid?: string; +}; + +type SignPSBTParams = { + /** + * The PSBT to be signed, string base64 encoded + */ + psbt: string; + signInputs: { + /** + * The address whose private key to use for signing. + */ + address: string; + /** + * Specifies which input to sign + */ + index: number; + /** + * Specifies which part(s) of the transaction the signature commits to + */ + sighashTypes: number[]; + }[]; + /** + * If `true`, the PSBT will be broadcasted after signing. Default is `false`. + */ + broadcast?: boolean; +}; + +export const BitcoinUtil = { + createSignPSBTParams(params: BitcoinUtil.CreateSignPSBTParams): SignPSBTParams { + const network = this.getBitcoinNetwork(params.caipNetworkId); + const payment = this.getPaymentByAddress(params.senderAddress, network); + const psbt = new bitcoin.Psbt({ network }); + + if (!payment.output) { + throw new Error('Invalid payment output'); + } + + const change = this.calculateChange(params.utxos, params.amount, params.feeRate); + + if (change < 0) { + throw new Error('Insufficient funds'); + } else if (change > 0) { + psbt.addOutput({ + address: params.senderAddress, + value: BigInt(change) + }); + } + + for (const utxo of params.utxos) { + psbt.addInput({ + hash: utxo.txid, + index: utxo.vout, + witnessUtxo: { + script: payment.output, + value: BigInt(utxo.value) + } + }); + } + + psbt.addOutput({ + address: params.recipientAddress, + value: BigInt(params.amount) + }); + + if (params.memo) { + const data = Buffer.from(params.memo, 'utf8'); + const embed = bitcoin.payments.embed({ data: [data] }); + + if (!embed.output) { + throw new Error('Invalid embed output'); + } + + psbt.addOutput({ + script: embed.output, + value: BigInt(0) + }); + } + + return { + psbt: psbt.toBase64(), + signInputs: [], + broadcast: false + }; + }, + + async getUTXOs(address: string, networkId: CaipNetworkId): Promise { + const isTestnet = this.isTestnet(networkId); + // Make chain dynamic + + const response = await fetch( + `https://mempool.space${isTestnet ? '/testnet' : ''}/api/address/${address}/utxo` + ); + + return await response.json(); + }, + + async getFeeRate() { + const defaultFeeRate = 2; + try { + const response = await fetch('https://mempool.space/api/v1/fees/recommended'); + if (response.ok) { + const data = await response.json(); + + if (data?.fastestFee) { + return parseInt(data.fastestFee, 10); + } + } + } catch (e) { + // eslint-disable-next-line no-console + console.error('Error fetching fee rate', e); + } + + return defaultFeeRate; + }, + + calculateChange(utxos: BitcoinUtil.UTXO[], amount: number, feeRate: number): number { + const inputSum = utxos.reduce((sum, utxo) => sum + utxo.value, 0); + /** + * 10 bytes: This is an estimated fixed overhead for the transaction. + * 148 bytes: This is the average size of each input (UTXO). + * 34 bytes: This is the size of each output. + * The multiplication by 2 indicates that there are usually two outputs in a typical transaction (one for the recipient and one for change) + */ + const estimatedSize = 10 + 148 * utxos.length + 34 * 2; + const fee = estimatedSize * feeRate; + const change = inputSum - amount - fee; + + return change; + }, + + isTestnet(networkId: CaipNetworkId): boolean { + return networkId === bitcoinTestnetNetwork.caipNetworkId; + }, + + getBitcoinNetwork(networkId: CaipNetworkId): bitcoin.Network { + return this.isTestnet(networkId) ? bitcoin.networks.testnet : bitcoin.networks.bitcoin; + }, + + getPaymentByAddress( + address: string, + network: bitcoin.networks.Network + ): bitcoin.payments.Payment { + const output = bitcoin.address.toOutputScript(address, network); + + if (bitcoinPSBTUtils.isP2MS(output)) { + return bitcoin.payments.p2ms({ output, network }); + } else if (bitcoinPSBTUtils.isP2PK(output)) { + return bitcoin.payments.p2pk({ output, network }); + } else if (bitcoinPSBTUtils.isP2PKH(output)) { + return bitcoin.payments.p2pkh({ output, network }); + } else if (bitcoinPSBTUtils.isP2WPKH(output)) { + return bitcoin.payments.p2wpkh({ output, network }); + } else if (bitcoinPSBTUtils.isP2WSHScript(output)) { + return bitcoin.payments.p2wsh({ output, network }); + } else if (bitcoinPSBTUtils.isP2SHScript(output)) { + return bitcoin.payments.p2sh({ output, network }); + } else if (bitcoinPSBTUtils.isP2TR(output)) { + return bitcoin.payments.p2tr({ output, network }); + } + + throw new Error('Unsupported payment type'); + } +}; + +export namespace BitcoinUtil { + export type CreateSignPSBTParams = { + senderAddress: string; + recipientAddress: string; + caipNetworkId: CaipNetworkId; + amount: number; + utxos: UTXO[]; + feeRate: number; + memo?: string; + }; + + export type UTXO = { + txid: string; + vout: number; + value: number; + status: { + confirmed: boolean; + block_height: number; + block_hash: string; + block_time: number; + }; + }; +} diff --git a/apps/native/src/views/ActionsView.tsx b/apps/native/src/views/ActionsView.tsx index ba284e2b..9b436f04 100644 --- a/apps/native/src/views/ActionsView.tsx +++ b/apps/native/src/views/ActionsView.tsx @@ -1,76 +1,32 @@ -import { Button, Text, FlexView } from '@reown/appkit-ui-react-native'; import { StyleSheet } from 'react-native'; -import { useSignMessage, useAccount, useSendTransaction, useEstimateGas } from 'wagmi'; -import { Hex, parseEther } from 'viem'; -import { SendTransactionData, SignMessageData } from 'wagmi/query'; -import { ToastUtils } from '../utils/ToastUtils'; +import { FlexView } from '@reown/appkit-ui-react-native'; +import { useAccount } from '@reown/appkit-react-native'; -export function ActionsView() { - const { isConnected } = useAccount(); - - const onSignSuccess = (data: SignMessageData) => { - ToastUtils.showSuccessToast('Signature successful', data); - }; - - const onSignError = (error: Error) => { - ToastUtils.showErrorToast('Signature failed', error.message); - }; - - const onSendSuccess = (data: SendTransactionData) => { - ToastUtils.showSuccessToast('Transaction successful', data); - }; - - const onSendError = (error: Error) => { - ToastUtils.showErrorToast('Transaction failed', error.message); - }; +// import { EthersActionsView } from './EthersActionsView'; +import { SolanaActionsView } from './SolanaActionsView'; +import { BitcoinActionsView } from './BitcoinActionsView'; +import { WagmiActionsView } from './WagmiActionsView'; - const { isPending, signMessage } = useSignMessage({ - mutation: { - onSuccess: onSignSuccess, - onError: onSignError - } - }); - const TX = { - to: '0x704457b418E9Fb723e1Bc0cB98106a6B8Cf87689' as Hex, // Test wallet - value: parseEther('0.001'), - data: '0x' as Hex - }; - - const { data: gas, isError: isGasError } = useEstimateGas(TX); - - const { - isPending: isSending, - - sendTransaction - } = useSendTransaction({ - mutation: { - onSuccess: onSendSuccess, - onError: onSendError - } - }); +export function ActionsView() { + const isConnected = true; + const { chainId } = useAccount(); return isConnected ? ( - Wagmi Actions - - {isGasError && Error estimating gas} - - {isSending && Check Wallet} + {chainId?.startsWith('eip155') ? ( + + ) : chainId?.startsWith('solana') ? ( + + ) : chainId?.startsWith('bip122') ? ( + + ) : null} ) : null; } const styles = StyleSheet.create({ container: { - marginTop: 16, + marginVertical: 16, gap: 8 } }); diff --git a/apps/native/src/views/BitcoinActionsView.tsx b/apps/native/src/views/BitcoinActionsView.tsx new file mode 100644 index 00000000..104917a2 --- /dev/null +++ b/apps/native/src/views/BitcoinActionsView.tsx @@ -0,0 +1,126 @@ +import { StyleSheet } from 'react-native'; +import { Button, Text, FlexView } from '@reown/appkit-ui-react-native'; +import { useAccount, useProvider } from '@reown/appkit-react-native'; + +import { ToastUtils } from '../utils/ToastUtils'; +import { BitcoinUtil, SignPSBTResponse } from '../utils/BitcoinUtil'; + +export function BitcoinActionsView() { + const isConnected = true; + const { address, chainId } = useAccount(); + const provider = useProvider('bip122'); + + const onSignSuccess = (data: string) => { + ToastUtils.showSuccessToast('Sign successful', data); + }; + + const onSignError = (error: Error) => { + ToastUtils.showErrorToast('Sign failed', error.message); + }; + + const signMessage = async () => { + try { + if (!provider) { + ToastUtils.showErrorToast('Sign failed', 'No provider found'); + + return; + } + + if (!address) { + ToastUtils.showErrorToast('Sign failed', 'No address found'); + + return; + } + + const message = 'Hello from AppKit Bitcoin'; + + const { signature } = (await provider.request( + { + method: 'signMessage', + params: { message, account: address, address, protocol: 'ecdsa' } + }, + chainId + )) as { address: string; signature: string }; + + const formattedSignature = Buffer.from(signature, 'hex').toString('base64'); + + onSignSuccess(formattedSignature); + } catch (error) { + onSignError(error as Error); + } + }; + + const signPsbt = async () => { + try { + if (!provider) { + ToastUtils.showErrorToast('Sign failed', 'No provider found'); + + return; + } + + if (!address) { + ToastUtils.showErrorToast('Sign failed', 'No address found'); + + return; + } + + if (chainId?.split(':')[0] !== 'bip122') { + ToastUtils.showErrorToast('Sign failed', 'The selected chain is not bip122'); + + return; + } + + const utxos = await BitcoinUtil.getUTXOs(address, chainId as `bip122:${string}`); + const feeRate = await BitcoinUtil.getFeeRate(); + + const params = BitcoinUtil.createSignPSBTParams({ + amount: 1500, + feeRate, + caipNetworkId: chainId as `bip122:${string}`, + recipientAddress: address, + senderAddress: address, + utxos + }); + + params.broadcast = false; + + const response = (await provider.request( + { + method: 'signPsbt', + params: { + account: address, + psbt: params.psbt, + signInputs: params.signInputs, + broadcast: params.broadcast + } + }, + chainId + )) as SignPSBTResponse; + + onSignSuccess(`${response.psbt}-${response.txid}`); + } catch (error) { + // eslint-disable-next-line no-console + console.log('error', error); + onSignError(error as Error); + } + }; + + return isConnected ? ( + + Bitcoin Actions + + + + ) : null; +} + +const styles = StyleSheet.create({ + container: { + marginVertical: 16, + gap: 8 + } +}); diff --git a/apps/native/src/views/EthersActionsView.tsx b/apps/native/src/views/EthersActionsView.tsx new file mode 100644 index 00000000..c0e60963 --- /dev/null +++ b/apps/native/src/views/EthersActionsView.tsx @@ -0,0 +1,69 @@ +import { StyleSheet } from 'react-native'; +import { Button, Text, FlexView } from '@reown/appkit-ui-react-native'; +import { useAccount, useProvider } from '@reown/appkit-react-native'; +import { hexlify, isHexString, toUtf8Bytes } from 'ethers'; + +import { ToastUtils } from '../utils/ToastUtils'; + +export function EthersActionsView() { + const isConnected = true; + const { address, chainId } = useAccount(); + const provider = useProvider('eip155'); + + const onSignSuccess = (data: any) => { + ToastUtils.showSuccessToast('Sign successful', data); + }; + + const onSignError = (error: Error) => { + ToastUtils.showErrorToast('Sign failed', error.message); + }; + + const signMessage = async () => { + try { + if (!provider) { + ToastUtils.showErrorToast('Sign failed', 'No provider found'); + + return; + } + + if (!address) { + ToastUtils.showErrorToast('Sign failed', 'No address found'); + + return; + } + + const message = 'Hello from AppKit Ethers'; + const hexMessage = isHexString(message) ? message : hexlify(toUtf8Bytes(message)); + + const signature = await provider.request( + { + method: 'personal_sign', + params: [hexMessage, address] + }, + chainId + ); + + onSignSuccess(signature); + } catch (error) { + // eslint-disable-next-line no-console + console.log('error', error); + onSignError(error as Error); + } + }; + + return isConnected ? ( + + EVM Actions + + + ) : null; +} + +const styles = StyleSheet.create({ + container: { + marginVertical: 16, + gap: 8 + } +}); diff --git a/apps/native/src/views/EventsView.tsx b/apps/native/src/views/EventsView.tsx new file mode 100644 index 00000000..ffc06f4f --- /dev/null +++ b/apps/native/src/views/EventsView.tsx @@ -0,0 +1,35 @@ +import { useAppKitEvents, useAppKitEventSubscription, useAppKit } from '@reown/appkit-react-native'; +import { FlexView, Text } from '@reown/appkit-ui-react-native'; +import { useState } from 'react'; +import { type ViewStyle, type StyleProp, StyleSheet } from 'react-native'; + +interface Props { + style?: StyleProp; +} + +export function EventsView({ style }: Props) { + const { data } = useAppKitEvents(); + const { isOpen } = useAppKit(); + const [eventCount, setEventCount] = useState(0); + + useAppKitEventSubscription('MODAL_OPEN', () => { + setEventCount(prev => prev + 1); + }); + + return data ? ( + + + Events + + Last event: {data?.event} + Modal open count: {eventCount} + Modal is open: {isOpen ? 'Yes' : 'No'} + + ) : null; +} + +const styles = StyleSheet.create({ + title: { + marginBottom: 6 + } +}); diff --git a/apps/native/src/views/SolanaActionsView.tsx b/apps/native/src/views/SolanaActionsView.tsx new file mode 100644 index 00000000..5258fb03 --- /dev/null +++ b/apps/native/src/views/SolanaActionsView.tsx @@ -0,0 +1,70 @@ +import { StyleSheet } from 'react-native'; +import { Button, Text, FlexView } from '@reown/appkit-ui-react-native'; +import { useAccount, useProvider } from '@reown/appkit-react-native'; +import base58 from 'bs58'; + +import { ToastUtils } from '../utils/ToastUtils'; + +export function SolanaActionsView() { + const isConnected = true; + const { address, chainId } = useAccount(); + const provider = useProvider('solana'); + + const onSignSuccess = (data: any) => { + ToastUtils.showSuccessToast('Sign successful', data); + }; + + const onSignError = (error: Error) => { + ToastUtils.showErrorToast('Sign failed', error.message); + }; + + const signMessage = async () => { + try { + if (!provider) { + ToastUtils.showErrorToast('Sign failed', 'No provider found'); + + return; + } + + if (!address) { + ToastUtils.showErrorToast('Sign failed', 'No address found'); + + return; + } + const encodedMessage = new TextEncoder().encode('Hello from AppKit Solana'); + + const params = { + message: base58.encode(encodedMessage), + pubkey: address + }; + + const { signature } = (await provider.request( + { + method: 'solana_signMessage', + params + }, + chainId + )) as { address: string; signature: string }; + + onSignSuccess(signature); + } catch (error) { + onSignError(error as Error); + } + }; + + return isConnected ? ( + + Solana Actions + + + ) : null; +} + +const styles = StyleSheet.create({ + container: { + marginVertical: 16, + gap: 8 + } +}); diff --git a/apps/native/src/views/WagmiActionsView.tsx b/apps/native/src/views/WagmiActionsView.tsx new file mode 100644 index 00000000..cd0884b1 --- /dev/null +++ b/apps/native/src/views/WagmiActionsView.tsx @@ -0,0 +1,72 @@ +import { Button, Text, FlexView } from '@reown/appkit-ui-react-native'; +import { StyleSheet } from 'react-native'; +import { useSignMessage, useAccount, useSendTransaction, useEstimateGas } from 'wagmi'; +import { Hex, parseEther } from 'viem'; +import { SendTransactionData, SignMessageData } from 'wagmi/query'; +import { ToastUtils } from '../utils/ToastUtils'; + +export function WagmiActionsView() { + const { isConnected } = useAccount(); + + const onSignSuccess = (data: SignMessageData) => { + ToastUtils.showSuccessToast('Signature successful', data); + }; + + const onSignError = (error: Error) => { + ToastUtils.showErrorToast('Signature failed', error.message); + }; + + const onSendSuccess = (data: SendTransactionData) => { + ToastUtils.showSuccessToast('Transaction successful', data); + }; + + const onSendError = (error: Error) => { + ToastUtils.showErrorToast('Transaction failed', error.message); + }; + + const { isPending, signMessage } = useSignMessage({ + mutation: { + onSuccess: onSignSuccess, + onError: onSignError + } + }); + const TX = { + to: '0x704457b418E9Fb723e1Bc0cB98106a6B8Cf87689' as Hex, // Test wallet + value: parseEther('0.001'), + data: '0x' as Hex + }; + + const { data: gas, isError: isGasError } = useEstimateGas(TX); + + const { isPending: isSending, sendTransaction } = useSendTransaction({ + mutation: { + onSuccess: onSendSuccess, + onError: onSendError + } + }); + + return isConnected ? ( + + Wagmi Actions + + {isGasError && Error estimating gas} + + {isSending && Check Wallet} + + ) : null; +} + +const styles = StyleSheet.create({ + container: { + marginTop: 16, + gap: 8 + } +}); diff --git a/apps/native/src/views/WalletInfoView.tsx b/apps/native/src/views/WalletInfoView.tsx new file mode 100644 index 00000000..a59c28e3 --- /dev/null +++ b/apps/native/src/views/WalletInfoView.tsx @@ -0,0 +1,37 @@ +import { Image, StyleSheet, StyleProp, ViewStyle } from 'react-native'; +import { useWalletInfo } from '@reown/appkit-react-native'; +import { FlexView, Text } from '@reown/appkit-ui-react-native'; + +interface Props { + style?: StyleProp; +} + +export function WalletInfoView({ style }: Props) { + const { walletInfo } = useWalletInfo(); + + return walletInfo ? ( + + + Connected to + + + {walletInfo?.icons?.[0] && ( + + )} + {walletInfo?.name && {walletInfo?.name}} + + + ) : null; +} + +const styles = StyleSheet.create({ + label: { + marginBottom: 2 + }, + logo: { + width: 20, + height: 20, + borderRadius: 5, + marginRight: 4 + } +}); diff --git a/package.json b/package.json index 1b5efac7..54fba8f5 100644 --- a/package.json +++ b/package.json @@ -4,19 +4,21 @@ "private": true, "workspaces": [ "packages/core", + "packages/appkit", "packages/ui", "packages/common", "packages/wallet", "packages/scaffold-utils", - "packages/scaffold", "packages/siwe", - "packages/wagmi", "packages/coinbase-wagmi", "packages/auth-wagmi", "packages/auth-ethers", "packages/coinbase-ethers", "packages/ethers5", "packages/ethers", + "packages/solana", + "packages/bitcoin", + "packages/wagmi", "apps/*" ], "scripts": { @@ -56,7 +58,7 @@ "@types/jest": "29.5.7", "@types/qrcode": "1.5.5", "@types/react": "18.2.79", - "@walletconnect/react-native-compat": "2.19.1", + "@walletconnect/react-native-compat": "2.20.2", "babel-jest": "^29.7.0", "eslint": "^8.46.0", "eslint-plugin-ft-flow": "2.0.3", @@ -77,8 +79,8 @@ "tsconfig": "*", "turbo": "2.1.1", "typescript": "5.2.2", - "viem": "2.23.10", - "wagmi": "2.14.13" + "viem": "2.28.3", + "wagmi": "2.15.1" }, "packageManager": "yarn@4.0.2", "resolutions": { @@ -91,6 +93,8 @@ "esbuild": "0.25.0", "postcss": "8.4.31", "cookie": "0.7.0", - "ip": "^2.0.1" + "ip": "^2.0.1", + "@walletconnect/ethereum-provider": "2.20.2", + "@walletconnect/universal-provider": "2.20.2" } } diff --git a/packages/scaffold/.eslintrc.json b/packages/appkit/.eslintrc.json similarity index 100% rename from packages/scaffold/.eslintrc.json rename to packages/appkit/.eslintrc.json diff --git a/packages/scaffold/.npmignore b/packages/appkit/.npmignore similarity index 100% rename from packages/scaffold/.npmignore rename to packages/appkit/.npmignore diff --git a/packages/scaffold/CHANGELOG.md b/packages/appkit/CHANGELOG.md similarity index 100% rename from packages/scaffold/CHANGELOG.md rename to packages/appkit/CHANGELOG.md diff --git a/packages/scaffold/package.json b/packages/appkit/package.json similarity index 90% rename from packages/scaffold/package.json rename to packages/appkit/package.json index bc6da720..516a1ce4 100644 --- a/packages/scaffold/package.json +++ b/packages/appkit/package.json @@ -1,5 +1,5 @@ { - "name": "@reown/appkit-scaffold-react-native", + "name": "@reown/appkit-react-native", "version": "1.2.3", "main": "lib/commonjs/index.js", "types": "lib/typescript/index.d.ts", @@ -40,7 +40,9 @@ "@reown/appkit-common-react-native": "1.2.3", "@reown/appkit-core-react-native": "1.2.3", "@reown/appkit-siwe-react-native": "1.2.3", - "@reown/appkit-ui-react-native": "1.2.3" + "@reown/appkit-ui-react-native": "1.2.3", + "@walletconnect/universal-provider": "2.20.2", + "valtio": "^1.13.2" }, "peerDependencies": { "react": ">=17", diff --git a/packages/scaffold/readme.md b/packages/appkit/readme.md similarity index 100% rename from packages/scaffold/readme.md rename to packages/appkit/readme.md diff --git a/packages/appkit/src/AppKit.ts b/packages/appkit/src/AppKit.ts new file mode 100644 index 00000000..1d292a24 --- /dev/null +++ b/packages/appkit/src/AppKit.ts @@ -0,0 +1,445 @@ +import { + AccountController, + EventsController, + ModalController, + ConnectionsController, + OptionsController, + RouterController, + TransactionsController, + type Metadata, + StorageUtil, + type OptionsControllerState, + ThemeController, + ConnectionController +} from '@reown/appkit-core-react-native'; + +import type { + WalletConnector, + BlockchainAdapter, + ProposalNamespaces, + New_ConnectorType, + Namespaces, + CaipNetworkId, + AppKitNetwork, + Provider, + ThemeVariables, + ThemeMode, + WalletInfo +} from '@reown/appkit-common-react-native'; + +import { WalletConnectConnector } from './connectors/WalletConnectConnector'; +import { WcHelpersUtil } from './utils/HelpersUtil'; +import { NetworkUtil } from './utils/NetworkUtil'; +import { SIWEController, type AppKitSIWEClient } from '@reown/appkit-siwe-react-native'; +import type { OpenOptions } from './client'; + +interface AppKitConfig { + projectId: string; + metadata: Metadata; + adapters: BlockchainAdapter[]; + networks: AppKitNetwork[]; + extraConnectors?: WalletConnector[]; + clipboardClient?: OptionsControllerState['clipboardClient']; + includeWalletIds?: OptionsControllerState['includeWalletIds']; + excludeWalletIds?: OptionsControllerState['excludeWalletIds']; + featuredWalletIds?: OptionsControllerState['featuredWalletIds']; + customWallets?: OptionsControllerState['customWallets']; + tokens?: OptionsControllerState['tokens']; //TODO: check if needed in OptionsController + enableAnalytics?: OptionsControllerState['enableAnalytics']; + debug?: OptionsControllerState['debug']; + themeMode?: ThemeMode; + themeVariables?: ThemeVariables; + siweConfig?: AppKitSIWEClient; + defaultChain?: AppKitNetwork; + // features?: Features; + // chainImages?: Record; //TODO: rename to networkImages +} + +export class AppKit { + private projectId: string; + private metadata: Metadata; + private adapters: BlockchainAdapter[]; + private networks: AppKitNetwork[]; + private namespaces: ProposalNamespaces; + private config: AppKitConfig; + private extraConnectors: WalletConnector[]; + + constructor(config: AppKitConfig) { + this.projectId = config.projectId; + this.metadata = config.metadata; + this.adapters = config.adapters; + this.networks = NetworkUtil.formatNetworks(config.networks, this.projectId); //TODO: check this + this.namespaces = WcHelpersUtil.createNamespaces(config.networks) as ProposalNamespaces; + this.config = config; + this.extraConnectors = config.extraConnectors || []; + + this.initControllers(config); + this.initConnectors(); + } + + /** + * Handles the full connection flow for a given connector type. + * @param type - The type of connector to use. + * @param requestedNamespaces - Optional specific namespaces to request. + */ + async connect(type: New_ConnectorType, requestedNamespaces?: ProposalNamespaces): Promise { + try { + const connector = await this.createConnector(type); + const defaultChain = NetworkUtil.getDefaultChainId(this.config.defaultChain); + + const approvedNamespaces = await connector.connect({ + namespaces: requestedNamespaces ?? this.namespaces, + defaultChain + }); + + const walletInfo = connector.getWalletInfo(); + + if (!approvedNamespaces || Object.keys(approvedNamespaces).length === 0) { + throw new Error('Connection cancelled or failed: No approved namespaces returned.'); + } + + // Setup adapters and subscribe to adapter events + const approvedAdapters = this.setupAdaptersAndSubscribe( + connector, + Object.keys(approvedNamespaces) + ); + + // Check if any compatible adapters were found for the *approved* namespaces + if (approvedAdapters.length === 0) { + //TODO: handle case where devs want to connect to a namespace that has no adapters. Could use the provider directly. + throw new Error('No compatible adapters found for the approved namespaces'); + } + + // Store the connection details for the successfully connected adapters + this.storeConnectionDetails(approvedAdapters, approvedNamespaces, walletInfo); + + // Store connector type and namespaces in storage + await StorageUtil.setConnectedConnectors({ + type: connector.type, + namespaces: Object.keys(approvedNamespaces) + }); + + this.syncAccounts(approvedAdapters); + + //TODO: Replace this + AccountController.setIsConnected(true); + } catch (error) { + console.warn('Connection failed:', error); + throw error; + } + } + + /** + * Disconnects from a given namespace. + * @param namespace - The namespace to disconnect from. + * @param isInternal - Whether the disconnect is internal (i.e. from the AppKit) or external (i.e. from wallet side). + */ + async disconnect(namespace?: string, isInternal?: boolean): Promise { + try { + const activeNamespace = namespace ?? ConnectionsController.state.activeNamespace; + + if (!activeNamespace) { + return; + } + + const connection = ConnectionsController.state.connections[activeNamespace]; + const connectorType = connection?.adapter?.connector?.type; + + await ConnectionsController.disconnect(activeNamespace, isInternal); + + if (connectorType) { + await StorageUtil.removeConnectedConnectors(connectorType); + } + + ModalController.close(); + + AccountController.setIsConnected(false); // Might need adjustment based on multi-connection logic + RouterController.reset('Connect'); + TransactionsController.resetTransactions(); + ConnectionController.disconnect(); + + EventsController.sendEvent({ + type: 'track', + event: 'DISCONNECT_SUCCESS' + }); + } catch (error) { + EventsController.sendEvent({ + type: 'track', + event: 'DISCONNECT_ERROR' + }); + } + } + + /** + * Returns the provider for a given namespace. + * @param namespace - The namespace to get the provider for. + * @returns The provider for the given namespace. + */ + getProvider(namespace?: string): T | null { + const activeNamespace = namespace ?? ConnectionsController.state.activeNamespace; + if (!activeNamespace) return null; + + const connection = ConnectionsController.state.connections[activeNamespace]; + if (!connection || !connection.adapter || !connection.adapter.connector) return null; + + return connection.adapter.connector.getProvider() as T; + } + + getNetworks() { + return this.networks; + } + + async switchNetwork(network: AppKitNetwork): Promise { + const adapter = this.getAdapterByNamespace(network.chainNamespace); + if (!adapter) throw new Error('No active adapter'); + + await adapter.switchNetwork(network); + + EventsController.sendEvent({ + type: 'track', + event: 'SWITCH_NETWORK', + properties: { + network: network.id + } + }); + + ConnectionsController.setActiveChain( + adapter.getSupportedNamespace(), + `${adapter.getSupportedNamespace()}:${network.id}` as CaipNetworkId + ); + + if (ConnectionsController.state.activeNamespace !== (network.chainNamespace ?? 'eip155')) { + ConnectionsController.setActiveNamespace(network.chainNamespace ?? 'eip155'); + } + } + + open(options?: OpenOptions) { + ModalController.open(options); + } + + close() { + ModalController.close(); + } + + private async createConnector(type: New_ConnectorType): Promise { + // Check if an extra connector was provided by the developer + const CustomConnector = this.extraConnectors.find( + connector => connector.constructor.name.toLowerCase() === type.toLowerCase() + ); + + if (CustomConnector) { + return CustomConnector; + } + + // Default to WalletConnectConnector if no custom connector matches + return WalletConnectConnector.create({ projectId: this.projectId, metadata: this.metadata }); + } + + //TODO: reuse logic with connect method + /** + * Initializes connectors based on stored connection data. + * This attempts to restore previous sessions. + */ + private async initConnectors() { + const connectedConnectors = await StorageUtil.getConnectedConnectors(); // Fetch stored connectors + if (connectedConnectors.length > 0) { + ModalController.setLoading(true); + + for (const connected of connectedConnectors) { + try { + const connector = await this.createConnector(connected.type); + + const namespaces = connector.getNamespaces(); + const walletInfo = connector.getWalletInfo(); + + if (namespaces && Object.keys(namespaces).length > 0) { + // Ensure namespaces is not empty + // Setup adapters and subscribe to events + const initializedAdapters = this.setupAdaptersAndSubscribe( + connector, + Object.keys(namespaces) + ); + + // If adapters were successfully initialized, store the connection details + if (initializedAdapters.length > 0) { + this.storeConnectionDetails(initializedAdapters, namespaces, walletInfo); + } + + this.syncAccounts(initializedAdapters); + + AccountController.setIsConnected(true); + } + } catch (error) { + // Use console.warn for non-critical initialization failures + console.warn(`Failed to initialize connector type ${connected.type}:`, error); + await StorageUtil.removeConnectedConnectors(connected.type); + } + } + ModalController.setLoading(false); + } + } + + private setupAdaptersAndSubscribe( + connector: WalletConnector, + namespaces: string[] + ): BlockchainAdapter[] { + const adapters = this.adapters.filter(adapter => + namespaces.includes(adapter.getSupportedNamespace()) + ); + + if (adapters.length === 0) { + // Log or handle cases where no adapters match + console.warn(`No compatible adapters found for namespaces: ${namespaces.join(', ')}`); + + return []; + } + + adapters.forEach(adapter => { + adapter.setConnector(connector); + this.subscribeToAdapterEvents(adapter); + }); + + return adapters; + } + + private getAdapterByNamespace(namespace: string = 'eip155'): BlockchainAdapter | null { + const namespaceConnection = ConnectionsController.state.connections[namespace]; + + return namespaceConnection?.adapter ?? null; + } + + private async syncAccounts(adapters: BlockchainAdapter[]) { + // Get account balances + adapters.map(adapter => { + const namespace = adapter.getSupportedNamespace(); + const connection = ConnectionsController.state.connections[namespace]; + + if (!connection) return; + + const network = this.networks.find( + n => n.id?.toString() === connection?.activeChain?.split(':')[1] + ); + + const address = + adapter.getAccounts()?.find(a => a.startsWith(connection?.activeChain)) ?? + adapter.getAccounts()?.[0]; + + adapter.getBalance({ address, network, tokens: this.config.tokens }); + }); + } + + private storeConnectionDetails( + adapters: BlockchainAdapter[], + approvedNamespaces: Namespaces, + wallet?: WalletInfo + ) { + adapters.forEach(async adapter => { + const namespace = adapter.getSupportedNamespace(); + const namespaceDetails = approvedNamespaces[namespace]; + if (!namespaceDetails) return; // Should not happen if filtering is correct + + const accounts = namespaceDetails.accounts ?? []; + const chains = namespaceDetails.chains ?? []; + const activeChain = adapter?.connector?.getChainId(namespace); + + ConnectionsController.storeConnection({ + namespace, + adapter, + accounts, + chains, + activeChain, + wallet + }); + }); + + const updateActiveNamespace = !Object.keys(approvedNamespaces).find( + n => n === ConnectionsController.state.activeNamespace + ); + + // If the active namespace is not in the approved namespaces or is undefined, set the first connected adapter's namespace as active + if (updateActiveNamespace && adapters[0]) { + ConnectionsController.setActiveNamespace(adapters[0].getSupportedNamespace()); + } + } + + private subscribeToAdapterEvents(adapter: BlockchainAdapter): void { + adapter.on('accountsChanged', ({ accounts, namespace }) => { + //eslint-disable-next-line no-console + console.log('accountsChanged', accounts, namespace); + //TODO: check this + }); + + adapter.on('chainChanged', ({ chainId, namespace }) => { + const chain = `${namespace}:${chainId}` as CaipNetworkId; + ConnectionsController.setActiveChain(namespace, chain); + + const network = this.networks.find(n => n.id?.toString() === chainId); + if (network) { + adapter.getBalance({ + network, + tokens: this.config.tokens + }); + } + }); + + adapter.on('disconnect', ({ namespace }) => { + // console.log('AppKit disconnect namespace', namespace); + this.disconnect(namespace, false); + }); + + adapter.on('balanceChanged', ({ namespace, address, balance }) => { + ConnectionsController.updateBalance(namespace, address, balance); + }); + } + + private async initControllers(options: AppKitConfig) { + await this.initAsyncValues(options); + + OptionsController.setProjectId(options.projectId); + OptionsController.setMetadata(options.metadata); + OptionsController.setIncludeWalletIds(options.includeWalletIds); + OptionsController.setExcludeWalletIds(options.excludeWalletIds); + OptionsController.setFeaturedWalletIds(options.featuredWalletIds); + OptionsController.setTokens(options.tokens); + OptionsController.setCustomWallets(options.customWallets); + OptionsController.setEnableAnalytics(options.enableAnalytics); + OptionsController.setDebug(options.debug); + // OptionsController.setFeatures(options.features); + + ThemeController.setThemeMode(options.themeMode); + ThemeController.setThemeVariables(options.themeVariables); + + //TODO: function to get sdk version based on adapters + // OptionsController.setSdkVersion(options._sdkVersion); + + if (options.clipboardClient) { + OptionsController.setClipboardClient(options.clipboardClient); + } + + ConnectionsController.setNetworks(options.networks); + + if (options.siweConfig) { + SIWEController.setSIWEClient(options.siweConfig); + } + + // if ( + // (options.features?.onramp === true || options.features?.onramp === undefined) && + // (options.metadata?.redirect?.universal || options.metadata?.redirect?.native) + // ) { + // OptionsController.setIsOnRampEnabled(true); + // } + } + + private async initAsyncValues(options: AppKitConfig) { + const activeNamespace = await StorageUtil.getActiveNamespace(); + if (activeNamespace) { + ConnectionsController.setActiveNamespace(activeNamespace); + } else if (options.defaultChain) { + ConnectionsController.setActiveNamespace(options.defaultChain?.chainNamespace ?? 'eip155'); + } + } +} + +export function createAppKit(config: AppKitConfig): AppKit { + return new AppKit(config); +} diff --git a/packages/appkit/src/AppKitContext.tsx b/packages/appkit/src/AppKitContext.tsx new file mode 100644 index 00000000..21f8358b --- /dev/null +++ b/packages/appkit/src/AppKitContext.tsx @@ -0,0 +1,38 @@ +import React, { createContext, useContext, type ReactNode } from 'react'; +import { AppKit } from './AppKit'; + +interface AppKitContextType { + appKit: AppKit | null; +} + +export const AppKitContext = createContext({ appKit: null }); + +interface AppKitProviderProps { + children: ReactNode; + instance: AppKit; +} + +export const AppKitProvider: React.FC = ({ children, instance }) => { + return {children}; +}; + +//TODO: rename this so it doesn't conflict with the useAppKit hook in the hooks folder +export const useAppKit = () => { + const context = useContext(AppKitContext); + if (context === undefined) { + throw new Error('useAppKit must be used within an AppKitProvider'); + } + if (!context.appKit) { + // This might happen if the provider is rendered before AppKit is initialized + throw new Error('AppKit instance is not yet available in context.'); + } + + return { + connect: context.appKit.connect.bind(context.appKit), + disconnect: context.appKit.disconnect.bind(context.appKit), + open: context.appKit.open.bind(context.appKit), + close: context.appKit.close.bind(context.appKit), + switchNetwork: context.appKit.switchNetwork.bind(context.appKit), + getProvider: context.appKit.getProvider.bind(context.appKit) + }; +}; diff --git a/packages/scaffold/src/client.ts b/packages/appkit/src/client.ts similarity index 99% rename from packages/scaffold/src/client.ts rename to packages/appkit/src/client.ts index 3cd6b06b..420f1cac 100644 --- a/packages/scaffold/src/client.ts +++ b/packages/appkit/src/client.ts @@ -52,7 +52,7 @@ export interface LibraryOptions { customWallets?: OptionsControllerState['customWallets']; defaultChain?: NetworkControllerState['caipNetwork']; tokens?: OptionsControllerState['tokens']; - clipboardClient?: OptionsControllerState['_clipboardClient']; + clipboardClient?: OptionsControllerState['clipboardClient']; enableAnalytics?: OptionsControllerState['enableAnalytics']; _sdkVersion: OptionsControllerState['sdkVersion']; debug?: OptionsControllerState['debug']; diff --git a/packages/scaffold/src/config/animations.ts b/packages/appkit/src/config/animations.ts similarity index 100% rename from packages/scaffold/src/config/animations.ts rename to packages/appkit/src/config/animations.ts diff --git a/packages/appkit/src/connectors/WalletConnectConnector.ts b/packages/appkit/src/connectors/WalletConnectConnector.ts new file mode 100644 index 00000000..dd6a28e4 --- /dev/null +++ b/packages/appkit/src/connectors/WalletConnectConnector.ts @@ -0,0 +1,132 @@ +import { type Metadata, ConnectionController } from '@reown/appkit-core-react-native'; +import { UniversalProvider, type IUniversalProvider } from '@walletconnect/universal-provider'; +import { + WalletConnector, + type AppKitNetwork, + type Namespaces, + type ProposalNamespaces, + type Provider, + type WalletInfo, + type ChainNamespace, + type CaipNetworkId +} from '@reown/appkit-common-react-native'; + +export class WalletConnectConnector extends WalletConnector { + private static universalProviderInstance: IUniversalProvider | null = null; + + private constructor(provider: IUniversalProvider) { + super({ type: 'walletconnect', provider: provider as Provider }); + + if (provider.session?.namespaces) { + this.namespaces = provider.session.namespaces as Namespaces; + } + + if (provider.session?.peer?.metadata) { + const metadata = provider.session?.peer.metadata; + if (metadata) { + this.wallet = { + ...metadata, + name: metadata.name, + icon: metadata.icons?.[0] + }; + } + } + } + + private static async getUniversalProvider({ + projectId, + metadata + }: { + projectId: string; + metadata: Metadata; + }): Promise { + if (!WalletConnectConnector.universalProviderInstance) { + WalletConnectConnector.universalProviderInstance = await UniversalProvider.init({ + projectId, + metadata + }); + } + + return WalletConnectConnector.universalProviderInstance; + } + + public static async create({ + projectId, + metadata + }: { + projectId: string; + metadata: Metadata; + }): Promise { + const provider = await WalletConnectConnector.getUniversalProvider({ + projectId, + metadata + }); + + return new WalletConnectConnector(provider); + } + + override disconnect(): Promise { + return this.provider.disconnect(); + } + + override async connect(opts: { namespaces: ProposalNamespaces; defaultChain?: CaipNetworkId }) { + function onUri(uri: string) { + ConnectionController.setWcUri(uri); + } + + this.provider.on('display_uri', onUri); + + const session = await (this.provider as IUniversalProvider).connect({ + namespaces: {}, + optionalNamespaces: opts.namespaces + }); + + if (opts.defaultChain) { + (this.provider as IUniversalProvider).setDefaultChain(opts.defaultChain); + } + + this.namespaces = session?.namespaces as Namespaces; + + this.provider.off('display_uri', onUri); + + return this.namespaces; + } + + override getProvider(): Provider { + return this.provider; + } + + override getNamespaces(): Namespaces { + return this.namespaces ?? {}; + } + + override switchNetwork(network: AppKitNetwork): Promise { + if (!network) throw new Error('No network provided'); + + let caipNetworkId = network.caipNetworkId ?? `eip155:${network.id}`; + + (this.provider as IUniversalProvider).setDefaultChain(caipNetworkId); + + return Promise.resolve(); + } + + override getWalletInfo(): WalletInfo | undefined { + return this.wallet; + } + + override getChainId(namespace: ChainNamespace): CaipNetworkId | undefined { + if (!this.namespaces || !this.namespaces[namespace]) { + return undefined; + } + + const chainId = (this.provider as IUniversalProvider).rpcProviders[ + namespace + ]?.getDefaultChain(); + + if (!chainId) { + return undefined; + } + + return `${namespace}:${chainId}` as CaipNetworkId; + } +} diff --git a/packages/appkit/src/hooks/useAccount.ts b/packages/appkit/src/hooks/useAccount.ts new file mode 100644 index 00000000..fb39ec6d --- /dev/null +++ b/packages/appkit/src/hooks/useAccount.ts @@ -0,0 +1,21 @@ +import { useSnapshot } from 'valtio'; +import { ConnectionsController } from '@reown/appkit-core-react-native'; +import { useAppKit } from './useAppKit'; + +export function useAccount() { + useAppKit(); // Use the hook for checks + + const { + activeAddress: address, + activeNamespace, + connections + } = useSnapshot(ConnectionsController.state); + + const connection = connections[activeNamespace ?? '']; + + return { + address: address?.split(':')[2], + isConnected: !!address, + chainId: connection?.activeChain + }; +} diff --git a/packages/appkit/src/hooks/useAppKit.ts b/packages/appkit/src/hooks/useAppKit.ts new file mode 100644 index 00000000..116d880d --- /dev/null +++ b/packages/appkit/src/hooks/useAppKit.ts @@ -0,0 +1,35 @@ +import { useContext } from 'react'; +import { useSnapshot } from 'valtio'; +import { ModalController } from '@reown/appkit-core-react-native'; + +import type { AppKit } from '../AppKit'; +import { AppKitContext } from '../AppKitContext'; + +interface UseAppKitReturn { + open: AppKit['open']; + close: AppKit['close']; + disconnect: (namespace?: string) => void; + switchNetwork: AppKit['switchNetwork']; + isOpen: boolean; +} + +export const useAppKit = (): UseAppKitReturn => { + const context = useContext(AppKitContext); + const { open } = useSnapshot(ModalController.state); + + if (context === undefined) { + throw new Error('useAppKit must be used within an AppKitProvider'); + } + if (!context.appKit) { + // This might happen if the provider is rendered before AppKit is initialized + throw new Error('AppKit instance is not yet available in context.'); + } + + return { + open: context.appKit.open.bind(context.appKit), + close: context.appKit.close.bind(context.appKit), + disconnect: (namespace?: string) => context.appKit?.disconnect.bind(context.appKit)(namespace), + switchNetwork: context.appKit.switchNetwork.bind(context.appKit), + isOpen: open + }; +}; diff --git a/packages/appkit/src/hooks/useAppKitEvents.ts b/packages/appkit/src/hooks/useAppKitEvents.ts new file mode 100644 index 00000000..e01c5d4b --- /dev/null +++ b/packages/appkit/src/hooks/useAppKitEvents.ts @@ -0,0 +1,40 @@ +import { useEffect } from 'react'; +import { useSnapshot } from 'valtio'; +import { + EventsController, + type EventName, + type EventsControllerState +} from '@reown/appkit-core-react-native'; +import { useAppKit } from './useAppKit'; + +export function useAppKitEvents(callback?: (newEvent: EventsControllerState) => void) { + useAppKit(); // Use the hook for checks + const { data, timestamp } = useSnapshot(EventsController.state); + + useEffect(() => { + const unsubscribe = EventsController.subscribe(newEvent => { + callback?.(newEvent); + }); + + return () => { + unsubscribe?.(); + }; + }, [callback]); + + return { data, timestamp }; +} + +export function useAppKitEventSubscription( + event: EventName, + callback: (newEvent: EventsControllerState) => void +) { + useAppKit(); // Use the hook for checks + + useEffect(() => { + const unsubscribe = EventsController?.subscribeEvent(event, callback); + + return () => { + unsubscribe?.(); + }; + }, [callback, event]); +} diff --git a/packages/scaffold/src/hooks/useCustomDimensions.ts b/packages/appkit/src/hooks/useCustomDimensions.ts similarity index 100% rename from packages/scaffold/src/hooks/useCustomDimensions.ts rename to packages/appkit/src/hooks/useCustomDimensions.ts diff --git a/packages/scaffold/src/hooks/useDebounceCallback.ts b/packages/appkit/src/hooks/useDebounceCallback.ts similarity index 100% rename from packages/scaffold/src/hooks/useDebounceCallback.ts rename to packages/appkit/src/hooks/useDebounceCallback.ts diff --git a/packages/scaffold/src/hooks/useKeyboard.ts b/packages/appkit/src/hooks/useKeyboard.ts similarity index 100% rename from packages/scaffold/src/hooks/useKeyboard.ts rename to packages/appkit/src/hooks/useKeyboard.ts diff --git a/packages/appkit/src/hooks/useProvider.ts b/packages/appkit/src/hooks/useProvider.ts new file mode 100644 index 00000000..6554eab4 --- /dev/null +++ b/packages/appkit/src/hooks/useProvider.ts @@ -0,0 +1,15 @@ +import { useSnapshot } from 'valtio'; +import { ConnectionsController } from '@reown/appkit-core-react-native'; +import type { Provider } from '@reown/appkit-common-react-native'; + +export function useProvider(namespace?: string): Provider | undefined { + const { connections, activeNamespace } = useSnapshot(ConnectionsController.state); + + if (!namespace || !activeNamespace) return undefined; + + const connection = connections[namespace ?? activeNamespace]; + + if (!connection) return undefined; + + return connection.adapter.connector?.getProvider(); +} diff --git a/packages/scaffold/src/hooks/useTimeout.ts b/packages/appkit/src/hooks/useTimeout.ts similarity index 100% rename from packages/scaffold/src/hooks/useTimeout.ts rename to packages/appkit/src/hooks/useTimeout.ts diff --git a/packages/appkit/src/hooks/useWalletInfo.ts b/packages/appkit/src/hooks/useWalletInfo.ts new file mode 100644 index 00000000..82742316 --- /dev/null +++ b/packages/appkit/src/hooks/useWalletInfo.ts @@ -0,0 +1,10 @@ +import { useSnapshot } from 'valtio'; +import { ConnectionsController } from '@reown/appkit-core-react-native'; +import { useAppKit } from './useAppKit'; + +export function useWalletInfo() { + useAppKit(); // Use the hook for checks + const { walletInfo } = useSnapshot(ConnectionsController.state); + + return { walletInfo }; +} diff --git a/packages/scaffold/src/index.ts b/packages/appkit/src/index.ts similarity index 60% rename from packages/scaffold/src/index.ts rename to packages/appkit/src/index.ts index 5620f103..8cce9b03 100644 --- a/packages/scaffold/src/index.ts +++ b/packages/appkit/src/index.ts @@ -19,3 +19,15 @@ export type { LibraryOptions, ScaffoldOptions } from './client'; export type * from '@reown/appkit-core-react-native'; export { CoreHelperUtil } from '@reown/appkit-core-react-native'; + +export * from './AppKit'; +export * from './networks'; +export { AppKitProvider } from './AppKitContext'; +export { WalletConnectConnector } from './connectors/WalletConnectConnector'; + +/****** Hooks *******/ +export { useAppKit } from './hooks/useAppKit'; +export { useProvider } from './hooks/useProvider'; +export { useAccount } from './hooks/useAccount'; +export { useWalletInfo } from './hooks/useWalletInfo'; +export { useAppKitEvents, useAppKitEventSubscription } from './hooks/useAppKitEvents'; diff --git a/packages/scaffold/src/modal/w3m-account-button/index.tsx b/packages/appkit/src/modal/w3m-account-button/index.tsx similarity index 69% rename from packages/scaffold/src/modal/w3m-account-button/index.tsx rename to packages/appkit/src/modal/w3m-account-button/index.tsx index 8bb37376..0370bf4b 100644 --- a/packages/scaffold/src/modal/w3m-account-button/index.tsx +++ b/packages/appkit/src/modal/w3m-account-button/index.tsx @@ -2,10 +2,10 @@ import { useSnapshot } from 'valtio'; import { AccountController, CoreHelperUtil, - NetworkController, ModalController, AssetUtil, - ThemeController + ThemeController, + ConnectionsController } from '@reown/appkit-core-react-native'; import { AccountButton as AccountButtonUI, ThemeProvider } from '@reown/appkit-ui-react-native'; @@ -20,31 +20,33 @@ export interface AccountButtonProps { } export function AccountButton({ balance, disabled, style, testID }: AccountButtonProps) { - const { - address, - balance: balanceVal, - balanceSymbol, - profileImage, - profileName - } = useSnapshot(AccountController.state); - const { caipNetwork } = useSnapshot(NetworkController.state); + const { profileImage, profileName } = useSnapshot(AccountController.state); const { themeMode, themeVariables } = useSnapshot(ThemeController.state); + const { + activeAddress: address, + activeBalance, + activeNetwork + } = useSnapshot(ConnectionsController.state); - const networkImage = AssetUtil.getNetworkImage(caipNetwork); + const networkImage = AssetUtil.getNetworkImage(activeNetwork?.id); const showBalance = balance === 'show'; return ( ModalController.open()} - address={address} + address={address?.split(':')[2] ?? ''} profileName={profileName} networkSrc={networkImage} imageHeaders={ApiController._getApiHeaders()} avatarSrc={profileImage} disabled={disabled} style={style} - balance={showBalance ? CoreHelperUtil.formatBalance(balanceVal, balanceSymbol) : ''} + balance={ + showBalance + ? CoreHelperUtil.formatBalance(activeBalance?.amount, activeBalance?.symbol) + : '' + } testID={testID} /> diff --git a/packages/scaffold/src/modal/w3m-button/index.tsx b/packages/appkit/src/modal/w3m-button/index.tsx similarity index 100% rename from packages/scaffold/src/modal/w3m-button/index.tsx rename to packages/appkit/src/modal/w3m-button/index.tsx diff --git a/packages/scaffold/src/modal/w3m-connect-button/index.tsx b/packages/appkit/src/modal/w3m-connect-button/index.tsx similarity index 100% rename from packages/scaffold/src/modal/w3m-connect-button/index.tsx rename to packages/appkit/src/modal/w3m-connect-button/index.tsx diff --git a/packages/scaffold/src/modal/w3m-modal/index.tsx b/packages/appkit/src/modal/w3m-modal/index.tsx similarity index 98% rename from packages/scaffold/src/modal/w3m-modal/index.tsx rename to packages/appkit/src/modal/w3m-modal/index.tsx index 1d8d29d7..631bc2fa 100644 --- a/packages/scaffold/src/modal/w3m-modal/index.tsx +++ b/packages/appkit/src/modal/w3m-modal/index.tsx @@ -14,11 +14,11 @@ import { OptionsController, RouterController, TransactionsController, - type CaipAddress, type AppKitFrameProvider, WebviewController, ThemeController } from '@reown/appkit-core-react-native'; +import type { CaipAddress } from '@reown/appkit-common-react-native'; import { SIWEController } from '@reown/appkit-siwe-react-native'; import { AppKitRouter } from '../w3m-router'; diff --git a/packages/scaffold/src/modal/w3m-modal/styles.ts b/packages/appkit/src/modal/w3m-modal/styles.ts similarity index 100% rename from packages/scaffold/src/modal/w3m-modal/styles.ts rename to packages/appkit/src/modal/w3m-modal/styles.ts diff --git a/packages/scaffold/src/modal/w3m-network-button/index.tsx b/packages/appkit/src/modal/w3m-network-button/index.tsx similarity index 83% rename from packages/scaffold/src/modal/w3m-network-button/index.tsx rename to packages/appkit/src/modal/w3m-network-button/index.tsx index 353a1804..1c19e828 100644 --- a/packages/scaffold/src/modal/w3m-network-button/index.tsx +++ b/packages/appkit/src/modal/w3m-network-button/index.tsx @@ -4,9 +4,9 @@ import { AccountController, ApiController, AssetUtil, + ConnectionsController, EventsController, ModalController, - NetworkController, ThemeController } from '@reown/appkit-core-react-native'; import { NetworkButton as NetworkButtonUI, ThemeProvider } from '@reown/appkit-ui-react-native'; @@ -18,7 +18,7 @@ export interface NetworkButtonProps { export function NetworkButton({ disabled, style }: NetworkButtonProps) { const { isConnected } = useSnapshot(AccountController.state); - const { caipNetwork } = useSnapshot(NetworkController.state); + const { activeNetwork } = useSnapshot(ConnectionsController.state); const { loading } = useSnapshot(ModalController.state); const { themeMode, themeVariables } = useSnapshot(ThemeController.state); @@ -33,7 +33,7 @@ export function NetworkButton({ disabled, style }: NetworkButtonProps) { return ( - {caipNetwork?.name ?? (isConnected ? 'Unknown Network' : 'Select Network')} + {activeNetwork?.name ?? (isConnected ? 'Unknown Network' : 'Select Network')} ); diff --git a/packages/scaffold/src/modal/w3m-router/index.tsx b/packages/appkit/src/modal/w3m-router/index.tsx similarity index 100% rename from packages/scaffold/src/modal/w3m-router/index.tsx rename to packages/appkit/src/modal/w3m-router/index.tsx diff --git a/packages/appkit/src/networks/bitcoin.ts b/packages/appkit/src/networks/bitcoin.ts new file mode 100644 index 00000000..327bb85f --- /dev/null +++ b/packages/appkit/src/networks/bitcoin.ts @@ -0,0 +1,32 @@ +import type { AppKitNetwork } from '@reown/appkit-common-react-native'; + +export const bitcoin: AppKitNetwork = { + id: '000000000019d6689c085ae165831e93', + caipNetworkId: 'bip122:000000000019d6689c085ae165831e93', + chainNamespace: 'bip122', + name: 'Bitcoin', + nativeCurrency: { + name: 'Bitcoin', + symbol: 'BTC', + decimals: 8 + }, + rpcUrls: { + default: { http: ['https://rpc.walletconnect.org/v1'] } + } +}; + +export const bitcoinTestnet: AppKitNetwork = { + id: '000000000933ea01ad0ee984209779ba', + caipNetworkId: 'bip122:000000000933ea01ad0ee984209779ba', + chainNamespace: 'bip122', + name: 'Bitcoin Testnet', + nativeCurrency: { + name: 'Bitcoin', + symbol: 'BTC', + decimals: 8 + }, + rpcUrls: { + default: { http: ['https://rpc.walletconnect.org/v1'] } + }, + testnet: true +}; diff --git a/packages/appkit/src/networks/index.ts b/packages/appkit/src/networks/index.ts new file mode 100644 index 00000000..5f2e141a --- /dev/null +++ b/packages/appkit/src/networks/index.ts @@ -0,0 +1,6 @@ +// -- Networks --------------------------------------------------------------- +export * from './solana'; +export * from './bitcoin'; + +// -- Types --------------------------------------------------------------- +export type { AppKitNetwork } from '@reown/appkit-common-react-native'; diff --git a/packages/appkit/src/networks/solana.ts b/packages/appkit/src/networks/solana.ts new file mode 100644 index 00000000..39a2e32f --- /dev/null +++ b/packages/appkit/src/networks/solana.ts @@ -0,0 +1,44 @@ +import type { AppKitNetwork } from '@reown/appkit-common-react-native'; + +export const solana: AppKitNetwork = { + id: '5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp', + name: 'Solana', + nativeCurrency: { name: 'Solana', symbol: 'SOL', decimals: 9 }, + rpcUrls: { + default: { + http: ['https://rpc.walletconnect.org/v1'] + } + }, + blockExplorers: { default: { name: 'Solscan', url: 'https://solscan.io' } }, + chainNamespace: 'solana', + caipNetworkId: 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp', + deprecatedCaipNetworkId: 'solana:4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZ', + testnet: false +}; + +export const solanaDevnet: AppKitNetwork = { + id: 'EtWTRABZaYq6iMfeYKouRu166VU2xqa1', + name: 'Solana Devnet', + nativeCurrency: { name: 'Solana', symbol: 'SOL', decimals: 9 }, + rpcUrls: { + default: { http: ['https://rpc.walletconnect.org/v1'] } + }, + blockExplorers: { default: { name: 'Solscan', url: 'https://solscan.io' } }, + chainNamespace: 'solana', + caipNetworkId: 'solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1', + deprecatedCaipNetworkId: 'solana:8E9rvCKLFQia2Y35HXjjpWzj8weVo44K', + testnet: true +}; + +export const solanaTestnet = { + id: '4uhcVJyU9pJkvQyS88uRDiswHXSCkY3z', + name: 'Solana Testnet', + nativeCurrency: { name: 'Solana', symbol: 'SOL', decimals: 9 }, + rpcUrls: { + default: { http: ['https://rpc.walletconnect.org/v1'] } + }, + blockExplorers: { default: { name: 'Solscan', url: 'https://solscan.io' } }, + chainNamespace: 'solana', + caipNetworkId: 'solana:4uhcVJyU9pJkvQyS88uRDiswHXSCkY3z', + testnet: true +}; diff --git a/packages/scaffold/src/partials/w3m-account-activity/index.tsx b/packages/appkit/src/partials/w3m-account-activity/index.tsx similarity index 85% rename from packages/scaffold/src/partials/w3m-account-activity/index.tsx rename to packages/appkit/src/partials/w3m-account-activity/index.tsx index 3ec7ee05..dbbfa357 100644 --- a/packages/scaffold/src/partials/w3m-account-activity/index.tsx +++ b/packages/appkit/src/partials/w3m-account-activity/index.tsx @@ -14,8 +14,8 @@ import { type Transaction, type TransactionImage } from '@reown/appkit-common-re import { AccountController, AssetUtil, + ConnectionsController, EventsController, - NetworkController, OptionsController, TransactionsController } from '@reown/appkit-core-react-native'; @@ -32,16 +32,18 @@ export function AccountActivity({ style }: Props) { const [refreshing, setRefreshing] = useState(false); const [initialLoad, setInitialLoad] = useState(true); const { loading, transactions, next } = useSnapshot(TransactionsController.state); - const { caipNetwork } = useSnapshot(NetworkController.state); - const networkImage = AssetUtil.getNetworkImage(caipNetwork); + const { activeNetwork, activeNamespace } = useSnapshot(ConnectionsController.state); + const networkImage = AssetUtil.getNetworkImage(activeNetwork?.id); + const isSupported = activeNamespace && ['eip155', 'solana'].includes(activeNamespace); const handleLoadMore = () => { - TransactionsController.fetchTransactions(AccountController.state.address); + const address = ConnectionsController.state.activeAddress?.split(':')[2]; + TransactionsController.fetchTransactions(address); EventsController.sendEvent({ type: 'track', event: 'LOAD_MORE_TRANSACTIONS', properties: { - address: AccountController.state.address, + address, projectId: OptionsController.state.projectId, cursor: TransactionsController.state.next, isSmartAccount: AccountController.state.preferredAccountType === 'smartAccount' @@ -51,7 +53,8 @@ export function AccountActivity({ style }: Props) { const onRefresh = useCallback(async () => { setRefreshing(true); - await TransactionsController.fetchTransactions(AccountController.state.address, true); + const address = ConnectionsController.state.activeAddress?.split(':')[2]; + await TransactionsController.fetchTransactions(address, true); setRefreshing(false); }, []); @@ -61,7 +64,8 @@ export function AccountActivity({ style }: Props) { useEffect(() => { if (!TransactionsController.state.transactions.length) { - TransactionsController.fetchTransactions(AccountController.state.address, true); + const address = ConnectionsController.state.activeAddress?.split(':')[2]; + TransactionsController.fetchTransactions(address, true); } // Set initial load to false after first fetch const timer = setTimeout(() => setInitialLoad(false), 100); @@ -78,6 +82,17 @@ export function AccountActivity({ style }: Props) { ); } + if (!isSupported) { + return ( + + ); + } + // Only show placeholder when we're not in initial load or loading state if (!Object.keys(transactionsByYear).length && !loading && !initialLoad) { return ( diff --git a/packages/scaffold/src/partials/w3m-account-activity/styles.ts b/packages/appkit/src/partials/w3m-account-activity/styles.ts similarity index 100% rename from packages/scaffold/src/partials/w3m-account-activity/styles.ts rename to packages/appkit/src/partials/w3m-account-activity/styles.ts diff --git a/packages/scaffold/src/partials/w3m-account-activity/utils.ts b/packages/appkit/src/partials/w3m-account-activity/utils.ts similarity index 82% rename from packages/scaffold/src/partials/w3m-account-activity/utils.ts rename to packages/appkit/src/partials/w3m-account-activity/utils.ts index be865523..90eb4d11 100644 --- a/packages/scaffold/src/partials/w3m-account-activity/utils.ts +++ b/packages/appkit/src/partials/w3m-account-activity/utils.ts @@ -1,6 +1,5 @@ import { DateUtil, type Transaction } from '@reown/appkit-common-react-native'; -import { TransactionUtil } from '@reown/appkit-ui-react-native'; -import type { TransactionType } from '@reown/appkit-ui-react-native/lib/typescript/utils/TypesUtil'; +import { TransactionUtil, type TransactionType } from '@reown/appkit-ui-react-native'; export function getTransactionListItemProps(transaction: Transaction) { const date = DateUtil.formatDate(transaction?.metadata?.minedAt); diff --git a/packages/scaffold/src/partials/w3m-account-tokens/index.tsx b/packages/appkit/src/partials/w3m-account-tokens/index.tsx similarity index 93% rename from packages/scaffold/src/partials/w3m-account-tokens/index.tsx rename to packages/appkit/src/partials/w3m-account-tokens/index.tsx index 26db07f9..6f989f24 100644 --- a/packages/scaffold/src/partials/w3m-account-tokens/index.tsx +++ b/packages/appkit/src/partials/w3m-account-tokens/index.tsx @@ -10,7 +10,7 @@ import { useSnapshot } from 'valtio'; import { AccountController, AssetUtil, - NetworkController, + ConnectionsController, RouterController } from '@reown/appkit-core-react-native'; import { @@ -30,8 +30,8 @@ export function AccountTokens({ style }: Props) { const Theme = useTheme(); const [refreshing, setRefreshing] = useState(false); const { tokenBalance } = useSnapshot(AccountController.state); - const { caipNetwork } = useSnapshot(NetworkController.state); - const networkImage = AssetUtil.getNetworkImage(caipNetwork); + const { activeNetwork } = useSnapshot(ConnectionsController.state); + const networkImage = AssetUtil.getNetworkImage(activeNetwork?.id); const onRefresh = useCallback(async () => { setRefreshing(true); diff --git a/packages/scaffold/src/partials/w3m-account-wallet-features/index.tsx b/packages/appkit/src/partials/w3m-account-wallet-features/index.tsx similarity index 100% rename from packages/scaffold/src/partials/w3m-account-wallet-features/index.tsx rename to packages/appkit/src/partials/w3m-account-wallet-features/index.tsx diff --git a/packages/scaffold/src/partials/w3m-account-wallet-features/styles.ts b/packages/appkit/src/partials/w3m-account-wallet-features/styles.ts similarity index 100% rename from packages/scaffold/src/partials/w3m-account-wallet-features/styles.ts rename to packages/appkit/src/partials/w3m-account-wallet-features/styles.ts diff --git a/packages/scaffold/src/partials/w3m-all-wallets-list/index.tsx b/packages/appkit/src/partials/w3m-all-wallets-list/index.tsx similarity index 100% rename from packages/scaffold/src/partials/w3m-all-wallets-list/index.tsx rename to packages/appkit/src/partials/w3m-all-wallets-list/index.tsx diff --git a/packages/scaffold/src/partials/w3m-all-wallets-list/styles.ts b/packages/appkit/src/partials/w3m-all-wallets-list/styles.ts similarity index 100% rename from packages/scaffold/src/partials/w3m-all-wallets-list/styles.ts rename to packages/appkit/src/partials/w3m-all-wallets-list/styles.ts diff --git a/packages/scaffold/src/partials/w3m-all-wallets-search/index.tsx b/packages/appkit/src/partials/w3m-all-wallets-search/index.tsx similarity index 100% rename from packages/scaffold/src/partials/w3m-all-wallets-search/index.tsx rename to packages/appkit/src/partials/w3m-all-wallets-search/index.tsx diff --git a/packages/scaffold/src/partials/w3m-all-wallets-search/styles.ts b/packages/appkit/src/partials/w3m-all-wallets-search/styles.ts similarity index 100% rename from packages/scaffold/src/partials/w3m-all-wallets-search/styles.ts rename to packages/appkit/src/partials/w3m-all-wallets-search/styles.ts diff --git a/packages/scaffold/src/partials/w3m-connecting-body/index.tsx b/packages/appkit/src/partials/w3m-connecting-body/index.tsx similarity index 100% rename from packages/scaffold/src/partials/w3m-connecting-body/index.tsx rename to packages/appkit/src/partials/w3m-connecting-body/index.tsx diff --git a/packages/scaffold/src/partials/w3m-connecting-body/utils.ts b/packages/appkit/src/partials/w3m-connecting-body/utils.ts similarity index 100% rename from packages/scaffold/src/partials/w3m-connecting-body/utils.ts rename to packages/appkit/src/partials/w3m-connecting-body/utils.ts diff --git a/packages/scaffold/src/partials/w3m-connecting-header/index.tsx b/packages/appkit/src/partials/w3m-connecting-header/index.tsx similarity index 100% rename from packages/scaffold/src/partials/w3m-connecting-header/index.tsx rename to packages/appkit/src/partials/w3m-connecting-header/index.tsx diff --git a/packages/scaffold/src/partials/w3m-connecting-mobile/components/StoreLink.tsx b/packages/appkit/src/partials/w3m-connecting-mobile/components/StoreLink.tsx similarity index 100% rename from packages/scaffold/src/partials/w3m-connecting-mobile/components/StoreLink.tsx rename to packages/appkit/src/partials/w3m-connecting-mobile/components/StoreLink.tsx diff --git a/packages/scaffold/src/partials/w3m-connecting-mobile/index.tsx b/packages/appkit/src/partials/w3m-connecting-mobile/index.tsx similarity index 100% rename from packages/scaffold/src/partials/w3m-connecting-mobile/index.tsx rename to packages/appkit/src/partials/w3m-connecting-mobile/index.tsx diff --git a/packages/scaffold/src/partials/w3m-connecting-mobile/styles.ts b/packages/appkit/src/partials/w3m-connecting-mobile/styles.ts similarity index 100% rename from packages/scaffold/src/partials/w3m-connecting-mobile/styles.ts rename to packages/appkit/src/partials/w3m-connecting-mobile/styles.ts diff --git a/packages/scaffold/src/partials/w3m-connecting-qrcode/index.tsx b/packages/appkit/src/partials/w3m-connecting-qrcode/index.tsx similarity index 100% rename from packages/scaffold/src/partials/w3m-connecting-qrcode/index.tsx rename to packages/appkit/src/partials/w3m-connecting-qrcode/index.tsx diff --git a/packages/scaffold/src/partials/w3m-connecting-qrcode/styles.ts b/packages/appkit/src/partials/w3m-connecting-qrcode/styles.ts similarity index 100% rename from packages/scaffold/src/partials/w3m-connecting-qrcode/styles.ts rename to packages/appkit/src/partials/w3m-connecting-qrcode/styles.ts diff --git a/packages/scaffold/src/partials/w3m-connecting-web/index.tsx b/packages/appkit/src/partials/w3m-connecting-web/index.tsx similarity index 100% rename from packages/scaffold/src/partials/w3m-connecting-web/index.tsx rename to packages/appkit/src/partials/w3m-connecting-web/index.tsx diff --git a/packages/scaffold/src/partials/w3m-connecting-web/styles.ts b/packages/appkit/src/partials/w3m-connecting-web/styles.ts similarity index 100% rename from packages/scaffold/src/partials/w3m-connecting-web/styles.ts rename to packages/appkit/src/partials/w3m-connecting-web/styles.ts diff --git a/packages/scaffold/src/partials/w3m-header/index.tsx b/packages/appkit/src/partials/w3m-header/index.tsx similarity index 100% rename from packages/scaffold/src/partials/w3m-header/index.tsx rename to packages/appkit/src/partials/w3m-header/index.tsx diff --git a/packages/scaffold/src/partials/w3m-header/styles.ts b/packages/appkit/src/partials/w3m-header/styles.ts similarity index 100% rename from packages/scaffold/src/partials/w3m-header/styles.ts rename to packages/appkit/src/partials/w3m-header/styles.ts diff --git a/packages/scaffold/src/partials/w3m-information-modal/index.tsx b/packages/appkit/src/partials/w3m-information-modal/index.tsx similarity index 100% rename from packages/scaffold/src/partials/w3m-information-modal/index.tsx rename to packages/appkit/src/partials/w3m-information-modal/index.tsx diff --git a/packages/scaffold/src/partials/w3m-information-modal/styles.ts b/packages/appkit/src/partials/w3m-information-modal/styles.ts similarity index 100% rename from packages/scaffold/src/partials/w3m-information-modal/styles.ts rename to packages/appkit/src/partials/w3m-information-modal/styles.ts diff --git a/packages/scaffold/src/partials/w3m-otp-code/index.tsx b/packages/appkit/src/partials/w3m-otp-code/index.tsx similarity index 100% rename from packages/scaffold/src/partials/w3m-otp-code/index.tsx rename to packages/appkit/src/partials/w3m-otp-code/index.tsx diff --git a/packages/scaffold/src/partials/w3m-otp-code/styles.ts b/packages/appkit/src/partials/w3m-otp-code/styles.ts similarity index 100% rename from packages/scaffold/src/partials/w3m-otp-code/styles.ts rename to packages/appkit/src/partials/w3m-otp-code/styles.ts diff --git a/packages/scaffold/src/partials/w3m-placeholder/index.tsx b/packages/appkit/src/partials/w3m-placeholder/index.tsx similarity index 100% rename from packages/scaffold/src/partials/w3m-placeholder/index.tsx rename to packages/appkit/src/partials/w3m-placeholder/index.tsx diff --git a/packages/scaffold/src/partials/w3m-selector-modal/index.tsx b/packages/appkit/src/partials/w3m-selector-modal/index.tsx similarity index 93% rename from packages/scaffold/src/partials/w3m-selector-modal/index.tsx rename to packages/appkit/src/partials/w3m-selector-modal/index.tsx index 37c8c94e..bfb4d1dd 100644 --- a/packages/scaffold/src/partials/w3m-selector-modal/index.tsx +++ b/packages/appkit/src/partials/w3m-selector-modal/index.tsx @@ -13,7 +13,7 @@ import { useTheme } from '@reown/appkit-ui-react-native'; import styles from './styles'; -import { AssetUtil, NetworkController } from '@reown/appkit-core-react-native'; +import { AssetUtil, ConnectionsController } from '@reown/appkit-core-react-native'; interface SelectorModalProps { title?: string; @@ -45,8 +45,8 @@ export function SelectorModal({ showNetwork }: SelectorModalProps) { const Theme = useTheme(); - const { caipNetwork } = useSnapshot(NetworkController.state); - const networkImage = AssetUtil.getNetworkImage(caipNetwork); + const { activeNetwork } = useSnapshot(ConnectionsController.state); + const networkImage = AssetUtil.getNetworkImage(activeNetwork?.id); const renderSeparator = () => { return ; diff --git a/packages/scaffold/src/partials/w3m-selector-modal/styles.ts b/packages/appkit/src/partials/w3m-selector-modal/styles.ts similarity index 100% rename from packages/scaffold/src/partials/w3m-selector-modal/styles.ts rename to packages/appkit/src/partials/w3m-selector-modal/styles.ts diff --git a/packages/scaffold/src/partials/w3m-send-input-address/index.tsx b/packages/appkit/src/partials/w3m-send-input-address/index.tsx similarity index 100% rename from packages/scaffold/src/partials/w3m-send-input-address/index.tsx rename to packages/appkit/src/partials/w3m-send-input-address/index.tsx diff --git a/packages/scaffold/src/partials/w3m-send-input-address/styles.ts b/packages/appkit/src/partials/w3m-send-input-address/styles.ts similarity index 100% rename from packages/scaffold/src/partials/w3m-send-input-address/styles.ts rename to packages/appkit/src/partials/w3m-send-input-address/styles.ts diff --git a/packages/scaffold/src/partials/w3m-send-input-token/index.tsx b/packages/appkit/src/partials/w3m-send-input-token/index.tsx similarity index 100% rename from packages/scaffold/src/partials/w3m-send-input-token/index.tsx rename to packages/appkit/src/partials/w3m-send-input-token/index.tsx diff --git a/packages/scaffold/src/partials/w3m-send-input-token/styles.ts b/packages/appkit/src/partials/w3m-send-input-token/styles.ts similarity index 100% rename from packages/scaffold/src/partials/w3m-send-input-token/styles.ts rename to packages/appkit/src/partials/w3m-send-input-token/styles.ts diff --git a/packages/scaffold/src/partials/w3m-send-input-token/utils.ts b/packages/appkit/src/partials/w3m-send-input-token/utils.ts similarity index 100% rename from packages/scaffold/src/partials/w3m-send-input-token/utils.ts rename to packages/appkit/src/partials/w3m-send-input-token/utils.ts diff --git a/packages/scaffold/src/partials/w3m-snackbar/index.tsx b/packages/appkit/src/partials/w3m-snackbar/index.tsx similarity index 100% rename from packages/scaffold/src/partials/w3m-snackbar/index.tsx rename to packages/appkit/src/partials/w3m-snackbar/index.tsx diff --git a/packages/scaffold/src/partials/w3m-snackbar/styles.ts b/packages/appkit/src/partials/w3m-snackbar/styles.ts similarity index 100% rename from packages/scaffold/src/partials/w3m-snackbar/styles.ts rename to packages/appkit/src/partials/w3m-snackbar/styles.ts diff --git a/packages/scaffold/src/partials/w3m-swap-details/index.tsx b/packages/appkit/src/partials/w3m-swap-details/index.tsx similarity index 100% rename from packages/scaffold/src/partials/w3m-swap-details/index.tsx rename to packages/appkit/src/partials/w3m-swap-details/index.tsx diff --git a/packages/scaffold/src/partials/w3m-swap-details/styles.ts b/packages/appkit/src/partials/w3m-swap-details/styles.ts similarity index 100% rename from packages/scaffold/src/partials/w3m-swap-details/styles.ts rename to packages/appkit/src/partials/w3m-swap-details/styles.ts diff --git a/packages/scaffold/src/partials/w3m-swap-details/utils.ts b/packages/appkit/src/partials/w3m-swap-details/utils.ts similarity index 100% rename from packages/scaffold/src/partials/w3m-swap-details/utils.ts rename to packages/appkit/src/partials/w3m-swap-details/utils.ts diff --git a/packages/scaffold/src/partials/w3m-swap-input/index.tsx b/packages/appkit/src/partials/w3m-swap-input/index.tsx similarity index 100% rename from packages/scaffold/src/partials/w3m-swap-input/index.tsx rename to packages/appkit/src/partials/w3m-swap-input/index.tsx diff --git a/packages/scaffold/src/partials/w3m-swap-input/styles.ts b/packages/appkit/src/partials/w3m-swap-input/styles.ts similarity index 100% rename from packages/scaffold/src/partials/w3m-swap-input/styles.ts rename to packages/appkit/src/partials/w3m-swap-input/styles.ts diff --git a/packages/appkit/src/utils/HelpersUtil.ts b/packages/appkit/src/utils/HelpersUtil.ts new file mode 100644 index 00000000..33bd4db5 --- /dev/null +++ b/packages/appkit/src/utils/HelpersUtil.ts @@ -0,0 +1,214 @@ +import type { Namespace, NamespaceConfig } from '@walletconnect/universal-provider'; + +import type { + AppKitNetwork, + CaipNetworkId, + ChainNamespace +} from '@reown/appkit-common-react-native'; +import { solana, solanaDevnet } from '../networks/solana'; +// import { EnsController, type OptionsControllerState } from '@reown/appkit-controllers' + +// import { solana, solanaDevnet } from '../networks/index.js' + +export const DEFAULT_METHODS = { + solana: [ + 'solana_signMessage', + 'solana_signTransaction', + 'solana_requestAccounts', + 'solana_getAccounts', + 'solana_signAllTransactions', + 'solana_signAndSendTransaction' + ], + eip155: [ + 'eth_accounts', + 'eth_requestAccounts', + 'eth_sendRawTransaction', + 'eth_sign', + 'eth_signTransaction', + 'eth_signTypedData', + 'eth_signTypedData_v3', + 'eth_signTypedData_v4', + 'eth_sendTransaction', + 'personal_sign', + 'wallet_switchEthereumChain', + 'wallet_addEthereumChain', + 'wallet_getPermissions', + 'wallet_requestPermissions', + 'wallet_registerOnboarding', + 'wallet_watchAsset', + 'wallet_scanQRCode', + // EIP-5792 + 'wallet_getCallsStatus', + 'wallet_showCallsStatus', + 'wallet_sendCalls', + 'wallet_getCapabilities', + // EIP-7715 + 'wallet_grantPermissions', + 'wallet_revokePermissions', + //EIP-7811 + 'wallet_getAssets' + ], + bip122: ['sendTransfer', 'signMessage', 'signPsbt', 'getAccountAddresses'] +}; + +export const WcHelpersUtil = { + getMethodsByChainNamespace(chainNamespace: ChainNamespace): string[] { + return DEFAULT_METHODS[chainNamespace as keyof typeof DEFAULT_METHODS] || []; + }, + + createDefaultNamespace(chainNamespace: ChainNamespace): Namespace { + return { + methods: this.getMethodsByChainNamespace(chainNamespace), + events: ['accountsChanged', 'chainChanged'], + chains: [], + rpcMap: {} + }; + }, + + applyNamespaceOverrides( + baseNamespaces: NamespaceConfig, + overrides?: any //TODO: add OptionsControllerState['universalProviderConfigOverride'] + ): NamespaceConfig { + if (!overrides) { + return { ...baseNamespaces }; + } + + const result = { ...baseNamespaces }; + + const namespacesToOverride = new Set(); + + if (overrides.methods) { + Object.keys(overrides.methods).forEach(ns => namespacesToOverride.add(ns)); + } + + if (overrides.chains) { + Object.keys(overrides.chains).forEach(ns => namespacesToOverride.add(ns)); + } + + if (overrides.events) { + Object.keys(overrides.events).forEach(ns => namespacesToOverride.add(ns)); + } + + if (overrides.rpcMap) { + Object.keys(overrides.rpcMap).forEach(chainId => { + const [ns] = chainId.split(':'); + if (ns) { + namespacesToOverride.add(ns); + } + }); + } + + namespacesToOverride.forEach(ns => { + if (!result[ns]) { + result[ns] = this.createDefaultNamespace(ns as ChainNamespace); + } + }); + + if (overrides.methods) { + Object.entries(overrides.methods).forEach(([ns, methods]) => { + if (result[ns]) { + //@ts-ignore + result[ns].methods = methods; + } + }); + } + + if (overrides.chains) { + Object.entries(overrides.chains).forEach(([ns, chains]) => { + if (result[ns]) { + //@ts-ignore + result[ns].chains = chains; + } + }); + } + + if (overrides.events) { + Object.entries(overrides.events).forEach(([ns, events]) => { + if (result[ns]) { + //@ts-ignore + result[ns].events = events; + } + }); + } + + if (overrides.rpcMap) { + const processedNamespaces = new Set(); + + Object.entries(overrides.rpcMap).forEach(([chainId, rpcUrl]) => { + const [ns, id] = chainId.split(':'); + if (!ns || !id || !result[ns]) { + return; + } + + //@ts-ignore + if (!result[ns].rpcMap) { + //@ts-ignore + result[ns].rpcMap = {}; + } + + //@ts-ignore + if (!processedNamespaces.has(ns)) { + //@ts-ignore + result[ns].rpcMap = {}; + processedNamespaces.add(ns); + } + + //@ts-ignore + result[ns].rpcMap[id] = rpcUrl; + }); + } + + return result; + }, + + createNamespaces( + caipNetworks: AppKitNetwork[], + configOverride?: any //TODO: fix this + ): NamespaceConfig { + const defaultNamespaces = caipNetworks.reduce((acc, chain) => { + const { id, rpcUrls } = chain; + const chainNamespace = chain.chainNamespace || 'eip155'; + const rpcUrl = rpcUrls.default.http[0]; + + if (!acc[chainNamespace]) { + acc[chainNamespace] = this.createDefaultNamespace(chainNamespace); + } + + const caipNetworkId: CaipNetworkId = `${chainNamespace}:${id}`; + + const namespace = acc[chainNamespace]; + + if (namespace) { + //@ts-ignore + namespace.chains.push(caipNetworkId); + + // Workaround for wallets that only support deprecated Solana network ID + switch (caipNetworkId) { + case solana.caipNetworkId: + namespace.chains.push(solana.deprecatedCaipNetworkId as string); + break; + case solanaDevnet.caipNetworkId: + namespace.chains.push(solanaDevnet.deprecatedCaipNetworkId as string); + break; + default: + } + + if (namespace?.rpcMap && rpcUrl) { + namespace.rpcMap[id] = rpcUrl; + } + } + + return acc; + }, {}); + + return this.applyNamespaceOverrides(defaultNamespaces, configOverride); + } +}; + +export namespace WcHelpersUtil { + export type SessionEventData = { + id: string; + topic: string; + params: { chainId: string; event: { data: unknown; name: string } }; + }; +} diff --git a/packages/appkit/src/utils/NetworkUtil.ts b/packages/appkit/src/utils/NetworkUtil.ts new file mode 100644 index 00000000..39103ae4 --- /dev/null +++ b/packages/appkit/src/utils/NetworkUtil.ts @@ -0,0 +1,53 @@ +import { ConstantsUtil } from '@reown/appkit-common-react-native'; +import type { AppKitNetwork, CaipNetworkId } from '@reown/appkit-common-react-native'; + +export const NetworkUtil = { + //TODO: check this function + formatNetworks(networks: AppKitNetwork[], projectId: string): AppKitNetwork[] { + return networks.map(network => { + const formattedNetwork = { + ...network, + rpcUrls: { ...network.rpcUrls } + }; + + Object.keys(formattedNetwork.rpcUrls).forEach(key => { + const rpcConfig = formattedNetwork.rpcUrls[key]; + if (rpcConfig?.http?.some(url => url.includes(ConstantsUtil.BLOCKCHAIN_API_RPC_URL))) { + formattedNetwork.rpcUrls[key] = { + ...rpcConfig, + http: [ + this.getBlockchainApiRpcUrl( + network.caipNetworkId ?? `${network.chainNamespace ?? 'eip155'}:${network.id}`, + projectId + ) + ] + }; + } + }); + + return formattedNetwork; + }); + }, + + getBlockchainApiRpcUrl(caipNetworkId: CaipNetworkId, projectId: string) { + const url = new URL(`${ConstantsUtil.BLOCKCHAIN_API_RPC_URL}/v1/`); + url.searchParams.set('chainId', caipNetworkId); + url.searchParams.set('projectId', projectId); + + return url.toString(); + }, + + getDefaultChainId(network?: AppKitNetwork): CaipNetworkId | undefined { + if (!network) return undefined; + + if (network.caipNetworkId) { + return network.caipNetworkId; + } + + if (network.chainNamespace) { + return `${network.chainNamespace}:${network.id}`; + } + + return `eip155:${network.id}`; + } +}; diff --git a/packages/scaffold/src/utils/UiUtil.ts b/packages/appkit/src/utils/UiUtil.ts similarity index 100% rename from packages/scaffold/src/utils/UiUtil.ts rename to packages/appkit/src/utils/UiUtil.ts diff --git a/packages/scaffold/src/views/w3m-account-default-view/components/auth-buttons.tsx b/packages/appkit/src/views/w3m-account-default-view/components/auth-buttons.tsx similarity index 100% rename from packages/scaffold/src/views/w3m-account-default-view/components/auth-buttons.tsx rename to packages/appkit/src/views/w3m-account-default-view/components/auth-buttons.tsx diff --git a/packages/scaffold/src/views/w3m-account-default-view/components/upgrade-wallet-button.tsx b/packages/appkit/src/views/w3m-account-default-view/components/upgrade-wallet-button.tsx similarity index 100% rename from packages/scaffold/src/views/w3m-account-default-view/components/upgrade-wallet-button.tsx rename to packages/appkit/src/views/w3m-account-default-view/components/upgrade-wallet-button.tsx diff --git a/packages/scaffold/src/views/w3m-account-default-view/index.tsx b/packages/appkit/src/views/w3m-account-default-view/index.tsx similarity index 85% rename from packages/scaffold/src/views/w3m-account-default-view/index.tsx rename to packages/appkit/src/views/w3m-account-default-view/index.tsx index 5cd83abb..77c353aa 100644 --- a/packages/scaffold/src/views/w3m-account-default-view/index.tsx +++ b/packages/appkit/src/views/w3m-account-default-view/index.tsx @@ -8,7 +8,6 @@ import { ConnectionController, ConnectorController, CoreHelperUtil, - ConnectionUtil, EventsController, ModalController, NetworkController, @@ -18,7 +17,8 @@ import { type AppKitFrameProvider, ConstantsUtil, SwapController, - OnRampController + OnRampController, + ConnectionsController } from '@reown/appkit-core-react-native'; import { Avatar, @@ -30,40 +30,42 @@ import { Spacing, ListItem } from '@reown/appkit-ui-react-native'; -import { useCustomDimensions } from '../../hooks/useCustomDimensions'; -import styles from './styles'; +import { useAppKit } from '../../AppKitContext'; + +import { useCustomDimensions } from '../../hooks/useCustomDimensions'; import { AuthButtons } from './components/auth-buttons'; +import styles from './styles'; export function AccountDefaultView() { - const { - address, - profileName, - profileImage, - balance, - balanceSymbol, - addressExplorerUrl, - preferredAccountType - } = useSnapshot(AccountController.state); + const { profileName, profileImage, preferredAccountType } = useSnapshot(AccountController.state); const { loading } = useSnapshot(ModalController.state); + const { + activeAddress: address, + activeBalance: balance, + activeNetwork, + activeNamespace + } = useSnapshot(ConnectionsController.state); + const account = address?.split(':')[2]; const [disconnecting, setDisconnecting] = useState(false); - const { caipNetwork } = useSnapshot(NetworkController.state); const { connectedConnector } = useSnapshot(ConnectorController.state); const { connectedSocialProvider } = useSnapshot(ConnectionController.state); const { features, isOnRampEnabled } = useSnapshot(OptionsController.state); const { history } = useSnapshot(RouterController.state); - const networkImage = AssetUtil.getNetworkImage(caipNetwork); + const networkImage = AssetUtil.getNetworkImage(activeNetwork?.id); const showCopy = OptionsController.isClipboardAvailable(); const isAuth = connectedConnector === 'AUTH'; const showBalance = balance && !isAuth; - const showExplorer = addressExplorerUrl && !isAuth; + const showExplorer = Object.keys(activeNetwork?.blockExplorers ?? {}).length > 0 && !isAuth; const showBack = history.length > 1; const showSwitchAccountType = isAuth && NetworkController.checkIfSmartAccountEnabled(); + const showActivity = !isAuth && activeNamespace && ['eip155', 'solana'].includes(activeNamespace); const { padding } = useCustomDimensions(); + const { disconnect } = useAppKit(); async function onDisconnect() { setDisconnecting(true); - await ConnectionUtil.disconnect(); + await disconnect(ConnectionsController.state.activeNamespace); setDisconnecting(false); } @@ -105,20 +107,19 @@ export function AccountDefaultView() { }; const onExplorerPress = () => { - if (AccountController.state.addressExplorerUrl) { - Linking.openURL(AccountController.state.addressExplorerUrl); + if (showExplorer && ConnectionsController.state.activeNetwork?.blockExplorers?.default?.url) { + Linking.openURL(ConnectionsController.state.activeNetwork?.blockExplorers?.default?.url); } }; const onCopyAddress = () => { - if (AccountController.state.profileName) { - OptionsController.copyToClipboard(AccountController.state.profileName); - SnackController.showSuccess('Name copied'); - } else if (AccountController.state.address) { - OptionsController.copyToClipboard( - AccountController.state.profileName ?? AccountController.state.address - ); - SnackController.showSuccess('Address copied'); + //TODO: Check ENS name + if (OptionsController.isClipboardAvailable() && ConnectionsController.state.activeAddress) { + const _address = ConnectionsController.state.activeAddress.split(':')[2]; + if (_address) { + OptionsController.copyToClipboard(_address); + SnackController.showSuccess('Address copied'); + } } }; @@ -188,7 +189,7 @@ export function AccountDefaultView() { /> - + {profileName @@ -199,7 +200,7 @@ export function AccountDefaultView() { truncate: 'end' }) : UiUtil.getTruncateString({ - string: address ?? '', + string: account ?? '', charsStart: 4, charsEnd: 6, truncate: 'middle' @@ -217,7 +218,7 @@ export function AccountDefaultView() { {showBalance && ( - {CoreHelperUtil.formatBalance(balance, balanceSymbol)} + {CoreHelperUtil.formatBalance(balance.amount, balance.symbol, 6)} )} {showExplorer && ( @@ -259,7 +260,7 @@ export function AccountDefaultView() { style={styles.actionButton} > - {caipNetwork?.name} + {activeNetwork?.name} {!isAuth && isOnRampEnabled && ( @@ -288,7 +289,7 @@ export function AccountDefaultView() { Swap )} - {!isAuth && ( + {showActivity && ( c.type === 'WALLET_CONNECT'); + // const isWalletConnectEnabled = connectors.some(c => c.type === 'WALLET_CONNECT'); + const isWalletConnectEnabled = true; const isAuthEnabled = connectors.some(c => c.type === 'AUTH'); const isCoinbaseEnabled = connectors.some(c => c.type === 'COINBASE'); const isEmailEnabled = isAuthEnabled && features?.email; diff --git a/packages/scaffold/src/views/w3m-connect-view/styles.ts b/packages/appkit/src/views/w3m-connect-view/styles.ts similarity index 100% rename from packages/scaffold/src/views/w3m-connect-view/styles.ts rename to packages/appkit/src/views/w3m-connect-view/styles.ts diff --git a/packages/scaffold/src/views/w3m-connect-view/utils.ts b/packages/appkit/src/views/w3m-connect-view/utils.ts similarity index 100% rename from packages/scaffold/src/views/w3m-connect-view/utils.ts rename to packages/appkit/src/views/w3m-connect-view/utils.ts diff --git a/packages/scaffold/src/views/w3m-connecting-external-view/index.tsx b/packages/appkit/src/views/w3m-connecting-external-view/index.tsx similarity index 100% rename from packages/scaffold/src/views/w3m-connecting-external-view/index.tsx rename to packages/appkit/src/views/w3m-connecting-external-view/index.tsx diff --git a/packages/scaffold/src/views/w3m-connecting-external-view/styles.ts b/packages/appkit/src/views/w3m-connecting-external-view/styles.ts similarity index 100% rename from packages/scaffold/src/views/w3m-connecting-external-view/styles.ts rename to packages/appkit/src/views/w3m-connecting-external-view/styles.ts diff --git a/packages/scaffold/src/views/w3m-connecting-farcaster-view/index.tsx b/packages/appkit/src/views/w3m-connecting-farcaster-view/index.tsx similarity index 100% rename from packages/scaffold/src/views/w3m-connecting-farcaster-view/index.tsx rename to packages/appkit/src/views/w3m-connecting-farcaster-view/index.tsx diff --git a/packages/scaffold/src/views/w3m-connecting-farcaster-view/styles.ts b/packages/appkit/src/views/w3m-connecting-farcaster-view/styles.ts similarity index 100% rename from packages/scaffold/src/views/w3m-connecting-farcaster-view/styles.ts rename to packages/appkit/src/views/w3m-connecting-farcaster-view/styles.ts diff --git a/packages/scaffold/src/views/w3m-connecting-social-view/index.tsx b/packages/appkit/src/views/w3m-connecting-social-view/index.tsx similarity index 100% rename from packages/scaffold/src/views/w3m-connecting-social-view/index.tsx rename to packages/appkit/src/views/w3m-connecting-social-view/index.tsx diff --git a/packages/scaffold/src/views/w3m-connecting-social-view/styles.ts b/packages/appkit/src/views/w3m-connecting-social-view/styles.ts similarity index 100% rename from packages/scaffold/src/views/w3m-connecting-social-view/styles.ts rename to packages/appkit/src/views/w3m-connecting-social-view/styles.ts diff --git a/packages/scaffold/src/views/w3m-connecting-view/index.tsx b/packages/appkit/src/views/w3m-connecting-view/index.tsx similarity index 88% rename from packages/scaffold/src/views/w3m-connecting-view/index.tsx rename to packages/appkit/src/views/w3m-connecting-view/index.tsx index 44ee537c..8bd17423 100644 --- a/packages/scaffold/src/views/w3m-connecting-view/index.tsx +++ b/packages/appkit/src/views/w3m-connecting-view/index.tsx @@ -11,11 +11,10 @@ import { type Platform, OptionsController, ApiController, - EventsController, - ConnectorController + EventsController } from '@reown/appkit-core-react-native'; import { SIWEController } from '@reown/appkit-siwe-react-native'; - +import { useAppKit } from '../../AppKitContext'; import { ConnectingQrCode } from '../../partials/w3m-connecting-qrcode'; import { ConnectingMobile } from '../../partials/w3m-connecting-mobile'; import { ConnectingWeb } from '../../partials/w3m-connecting-web'; @@ -23,6 +22,7 @@ import { ConnectingHeader } from '../../partials/w3m-connecting-header'; import { UiUtil } from '../../utils/UiUtil'; export function ConnectingView() { + const { connect } = useAppKit(); const { installed } = useSnapshot(ApiController.state); const { data } = RouterController.state; const [lastRetry, setLastRetry] = useState(Date.now()); @@ -45,12 +45,17 @@ export function ConnectingView() { const initializeConnection = async (retry = false) => { try { const { wcPairingExpiry } = ConnectionController.state; - const { data: routeData } = RouterController.state; + // const { data: routeData } = RouterController.state; if (retry || CoreHelperUtil.isPairingExpired(wcPairingExpiry)) { ConnectionController.setWcError(false); - ConnectionController.connectWalletConnect(routeData?.wallet?.link_mode ?? undefined); - await ConnectionController.state.wcPromise; - ConnectorController.setConnectedConnector('WALLET_CONNECT'); + // ConnectionController.connectWalletConnect(routeData?.wallet?.link_mode ?? undefined); + + //TODO: check linkmode + const wcPromise = connect('walletconnect'); + ConnectionController.setWcPromise(wcPromise); + await wcPromise; + // await ConnectionController.state.wcPromise; + // ConnectorController.setConnectedConnector('WALLET_CONNECT'); AccountController.setIsConnected(true); if (OptionsController.state.isSiweEnabled) { diff --git a/packages/scaffold/src/views/w3m-create-view/index.tsx b/packages/appkit/src/views/w3m-create-view/index.tsx similarity index 100% rename from packages/scaffold/src/views/w3m-create-view/index.tsx rename to packages/appkit/src/views/w3m-create-view/index.tsx diff --git a/packages/scaffold/src/views/w3m-email-verify-device-view/index.tsx b/packages/appkit/src/views/w3m-email-verify-device-view/index.tsx similarity index 100% rename from packages/scaffold/src/views/w3m-email-verify-device-view/index.tsx rename to packages/appkit/src/views/w3m-email-verify-device-view/index.tsx diff --git a/packages/scaffold/src/views/w3m-email-verify-device-view/styles.ts b/packages/appkit/src/views/w3m-email-verify-device-view/styles.ts similarity index 100% rename from packages/scaffold/src/views/w3m-email-verify-device-view/styles.ts rename to packages/appkit/src/views/w3m-email-verify-device-view/styles.ts diff --git a/packages/scaffold/src/views/w3m-email-verify-otp-view/index.tsx b/packages/appkit/src/views/w3m-email-verify-otp-view/index.tsx similarity index 100% rename from packages/scaffold/src/views/w3m-email-verify-otp-view/index.tsx rename to packages/appkit/src/views/w3m-email-verify-otp-view/index.tsx diff --git a/packages/scaffold/src/views/w3m-get-wallet-view/index.tsx b/packages/appkit/src/views/w3m-get-wallet-view/index.tsx similarity index 100% rename from packages/scaffold/src/views/w3m-get-wallet-view/index.tsx rename to packages/appkit/src/views/w3m-get-wallet-view/index.tsx diff --git a/packages/scaffold/src/views/w3m-get-wallet-view/styles.ts b/packages/appkit/src/views/w3m-get-wallet-view/styles.ts similarity index 100% rename from packages/scaffold/src/views/w3m-get-wallet-view/styles.ts rename to packages/appkit/src/views/w3m-get-wallet-view/styles.ts diff --git a/packages/scaffold/src/views/w3m-network-switch-view/index.tsx b/packages/appkit/src/views/w3m-network-switch-view/index.tsx similarity index 92% rename from packages/scaffold/src/views/w3m-network-switch-view/index.tsx rename to packages/appkit/src/views/w3m-network-switch-view/index.tsx index 8adf0455..a9e39f97 100644 --- a/packages/scaffold/src/views/w3m-network-switch-view/index.tsx +++ b/packages/appkit/src/views/w3m-network-switch-view/index.tsx @@ -5,6 +5,7 @@ import { ApiController, AssetUtil, ConnectionController, + ConnectionsController, ConnectorController, EventsController, NetworkController, @@ -24,7 +25,7 @@ import styles from './styles'; export function NetworkSwitchView() { const { data } = useSnapshot(RouterController.state); const { recentWallets } = useSnapshot(ConnectionController.state); - const { caipNetwork } = useSnapshot(NetworkController.state); + const { activeNetwork } = useSnapshot(ConnectionsController.state); const isAuthConnected = ConnectorController.state.connectedConnector === 'AUTH'; const [error, setError] = useState(false); const [showRetry, setShowRetry] = useState(false); @@ -34,6 +35,7 @@ export function NetworkSwitchView() { const onSwitchNetwork = async () => { try { setError(false); + //TODO: change to appkit switchNetwork await NetworkController.switchActiveNetwork(network); EventsController.sendEvent({ type: 'track', @@ -55,10 +57,10 @@ export function NetworkSwitchView() { useEffect(() => { // Go back if network is already switched - if (caipNetwork?.id === network?.id) { + if (activeNetwork?.id === network?.id) { RouterUtil.navigateAfterNetworkSwitch(); } - }, [caipNetwork?.id, network?.id]); + }, [activeNetwork?.id, network?.id]); const retryTemplate = () => { if (!showRetry) return null; @@ -115,7 +117,7 @@ export function NetworkSwitchView() { diff --git a/packages/scaffold/src/views/w3m-network-switch-view/styles.ts b/packages/appkit/src/views/w3m-network-switch-view/styles.ts similarity index 100% rename from packages/scaffold/src/views/w3m-network-switch-view/styles.ts rename to packages/appkit/src/views/w3m-network-switch-view/styles.ts diff --git a/packages/scaffold/src/views/w3m-networks-view/index.tsx b/packages/appkit/src/views/w3m-networks-view/index.tsx similarity index 57% rename from packages/scaffold/src/views/w3m-networks-view/index.tsx rename to packages/appkit/src/views/w3m-networks-view/index.tsx index 30bdd029..4fff357d 100644 --- a/packages/scaffold/src/views/w3m-networks-view/index.tsx +++ b/packages/appkit/src/views/w3m-networks-view/index.tsx @@ -10,20 +10,19 @@ import { } from '@reown/appkit-ui-react-native'; import { ApiController, - AssetUtil, NetworkController, RouterController, - type CaipNetwork, EventsController, - CoreHelperUtil, - NetworkUtil + ConnectionsController, + AssetUtil } from '@reown/appkit-core-react-native'; +import type { AppKitNetwork } from '@reown/appkit-common-react-native'; import { useCustomDimensions } from '../../hooks/useCustomDimensions'; import styles from './styles'; +import { useAppKit } from '../../AppKitContext'; export function NetworksView() { - const { caipNetwork, requestedCaipNetworks, approvedCaipNetworkIds, supportsAllNetworks } = - NetworkController.state; + const { caipNetwork } = NetworkController.state; const imageHeaders = ApiController._getApiHeaders(); const { maxWidth: width, padding } = useCustomDimensions(); const numColumns = 4; @@ -32,6 +31,7 @@ export function NetworksView() { const itemGap = Math.abs( Math.trunc((usableWidth - numColumns * CardSelectWidth) / numColumns) / 2 ); + const { switchNetwork } = useAppKit(); const onHelpPress = () => { RouterController.push('WhatIsANetwork'); @@ -39,44 +39,40 @@ export function NetworksView() { }; const networksTemplate = () => { - const networks = CoreHelperUtil.sortNetworks(approvedCaipNetworkIds, requestedCaipNetworks); + //TODO: should show requested networks disabled + // const networks = CoreHelperUtil.sortNetworks(approvedCaipNetworkIds, requestedCaipNetworks); + const networks = ConnectionsController.getConnectedNetworks(); - const onNetworkPress = async (network: CaipNetwork) => { - const result = await NetworkUtil.handleNetworkSwitch(network); - if (result?.type === 'SWITCH_NETWORK') { - EventsController.sendEvent({ - type: 'track', - event: 'SWITCH_NETWORK', - properties: { - network: network.id - } - }); - } + const onNetworkPress = async (network: AppKitNetwork) => { + await switchNetwork(network); + RouterController.goBack(); }; - return networks.map(network => ( - - onNetworkPress(network)} - /> - - )); + return networks.map(network => { + return ( + + onNetworkPress(network)} + /> + + ); + }); }; return ( diff --git a/packages/scaffold/src/views/w3m-networks-view/styles.ts b/packages/appkit/src/views/w3m-networks-view/styles.ts similarity index 100% rename from packages/scaffold/src/views/w3m-networks-view/styles.ts rename to packages/appkit/src/views/w3m-networks-view/styles.ts diff --git a/packages/scaffold/src/views/w3m-onramp-checkout-view/index.tsx b/packages/appkit/src/views/w3m-onramp-checkout-view/index.tsx similarity index 97% rename from packages/scaffold/src/views/w3m-onramp-checkout-view/index.tsx rename to packages/appkit/src/views/w3m-onramp-checkout-view/index.tsx index a3085027..a3d83456 100644 --- a/packages/scaffold/src/views/w3m-onramp-checkout-view/index.tsx +++ b/packages/appkit/src/views/w3m-onramp-checkout-view/index.tsx @@ -1,6 +1,6 @@ import { AssetUtil, - NetworkController, + ConnectionsController, OnRampController, RouterController, ThemeController @@ -27,8 +27,8 @@ export function OnRampCheckoutView() { OnRampController.state ); - const { caipNetwork } = useSnapshot(NetworkController.state); - const networkImage = AssetUtil.getNetworkImage(caipNetwork); + const { activeNetwork } = useSnapshot(ConnectionsController.state); + const networkImage = AssetUtil.getNetworkImage(activeNetwork?.id); const value = NumberUtil.roundNumber(selectedQuote?.destinationAmount ?? 0, 6, 5); const symbol = selectedQuote?.destinationCurrencyCode; diff --git a/packages/scaffold/src/views/w3m-onramp-loading-view/index.tsx b/packages/appkit/src/views/w3m-onramp-loading-view/index.tsx similarity index 100% rename from packages/scaffold/src/views/w3m-onramp-loading-view/index.tsx rename to packages/appkit/src/views/w3m-onramp-loading-view/index.tsx diff --git a/packages/scaffold/src/views/w3m-onramp-loading-view/styles.ts b/packages/appkit/src/views/w3m-onramp-loading-view/styles.ts similarity index 100% rename from packages/scaffold/src/views/w3m-onramp-loading-view/styles.ts rename to packages/appkit/src/views/w3m-onramp-loading-view/styles.ts diff --git a/packages/scaffold/src/views/w3m-onramp-settings-view/components/Country.tsx b/packages/appkit/src/views/w3m-onramp-settings-view/components/Country.tsx similarity index 100% rename from packages/scaffold/src/views/w3m-onramp-settings-view/components/Country.tsx rename to packages/appkit/src/views/w3m-onramp-settings-view/components/Country.tsx diff --git a/packages/scaffold/src/views/w3m-onramp-settings-view/index.tsx b/packages/appkit/src/views/w3m-onramp-settings-view/index.tsx similarity index 100% rename from packages/scaffold/src/views/w3m-onramp-settings-view/index.tsx rename to packages/appkit/src/views/w3m-onramp-settings-view/index.tsx diff --git a/packages/scaffold/src/views/w3m-onramp-settings-view/styles.ts b/packages/appkit/src/views/w3m-onramp-settings-view/styles.ts similarity index 100% rename from packages/scaffold/src/views/w3m-onramp-settings-view/styles.ts rename to packages/appkit/src/views/w3m-onramp-settings-view/styles.ts diff --git a/packages/scaffold/src/views/w3m-onramp-settings-view/utils.ts b/packages/appkit/src/views/w3m-onramp-settings-view/utils.ts similarity index 100% rename from packages/scaffold/src/views/w3m-onramp-settings-view/utils.ts rename to packages/appkit/src/views/w3m-onramp-settings-view/utils.ts diff --git a/packages/scaffold/src/views/w3m-onramp-transaction-view/index.tsx b/packages/appkit/src/views/w3m-onramp-transaction-view/index.tsx similarity index 100% rename from packages/scaffold/src/views/w3m-onramp-transaction-view/index.tsx rename to packages/appkit/src/views/w3m-onramp-transaction-view/index.tsx diff --git a/packages/scaffold/src/views/w3m-onramp-transaction-view/styles.ts b/packages/appkit/src/views/w3m-onramp-transaction-view/styles.ts similarity index 100% rename from packages/scaffold/src/views/w3m-onramp-transaction-view/styles.ts rename to packages/appkit/src/views/w3m-onramp-transaction-view/styles.ts diff --git a/packages/scaffold/src/views/w3m-onramp-view/components/Currency.tsx b/packages/appkit/src/views/w3m-onramp-view/components/Currency.tsx similarity index 100% rename from packages/scaffold/src/views/w3m-onramp-view/components/Currency.tsx rename to packages/appkit/src/views/w3m-onramp-view/components/Currency.tsx diff --git a/packages/scaffold/src/views/w3m-onramp-view/components/CurrencyInput.tsx b/packages/appkit/src/views/w3m-onramp-view/components/CurrencyInput.tsx similarity index 100% rename from packages/scaffold/src/views/w3m-onramp-view/components/CurrencyInput.tsx rename to packages/appkit/src/views/w3m-onramp-view/components/CurrencyInput.tsx diff --git a/packages/scaffold/src/views/w3m-onramp-view/components/Header.tsx b/packages/appkit/src/views/w3m-onramp-view/components/Header.tsx similarity index 100% rename from packages/scaffold/src/views/w3m-onramp-view/components/Header.tsx rename to packages/appkit/src/views/w3m-onramp-view/components/Header.tsx diff --git a/packages/scaffold/src/views/w3m-onramp-view/components/LoadingView.tsx b/packages/appkit/src/views/w3m-onramp-view/components/LoadingView.tsx similarity index 100% rename from packages/scaffold/src/views/w3m-onramp-view/components/LoadingView.tsx rename to packages/appkit/src/views/w3m-onramp-view/components/LoadingView.tsx diff --git a/packages/scaffold/src/views/w3m-onramp-view/components/PaymentMethod.tsx b/packages/appkit/src/views/w3m-onramp-view/components/PaymentMethod.tsx similarity index 100% rename from packages/scaffold/src/views/w3m-onramp-view/components/PaymentMethod.tsx rename to packages/appkit/src/views/w3m-onramp-view/components/PaymentMethod.tsx diff --git a/packages/scaffold/src/views/w3m-onramp-view/components/Quote.tsx b/packages/appkit/src/views/w3m-onramp-view/components/Quote.tsx similarity index 100% rename from packages/scaffold/src/views/w3m-onramp-view/components/Quote.tsx rename to packages/appkit/src/views/w3m-onramp-view/components/Quote.tsx diff --git a/packages/scaffold/src/views/w3m-onramp-view/components/SelectPaymentModal.tsx b/packages/appkit/src/views/w3m-onramp-view/components/SelectPaymentModal.tsx similarity index 100% rename from packages/scaffold/src/views/w3m-onramp-view/components/SelectPaymentModal.tsx rename to packages/appkit/src/views/w3m-onramp-view/components/SelectPaymentModal.tsx diff --git a/packages/scaffold/src/views/w3m-onramp-view/index.tsx b/packages/appkit/src/views/w3m-onramp-view/index.tsx similarity index 98% rename from packages/scaffold/src/views/w3m-onramp-view/index.tsx rename to packages/appkit/src/views/w3m-onramp-view/index.tsx index 74a76291..f4a14bff 100644 --- a/packages/scaffold/src/views/w3m-onramp-view/index.tsx +++ b/packages/appkit/src/views/w3m-onramp-view/index.tsx @@ -7,10 +7,10 @@ import { ThemeController, RouterController, type OnRampControllerState, - NetworkController, AssetUtil, SnackController, - ConstantsUtil + ConstantsUtil, + ConnectionsController } from '@reown/appkit-core-react-native'; import { Button, @@ -51,7 +51,7 @@ export function OnRampView() { loading, initialLoading } = useSnapshot(OnRampController.state) as OnRampControllerState; - const { caipNetwork } = useSnapshot(NetworkController.state); + const { activeNetwork } = useSnapshot(ConnectionsController.state); const [searchValue, setSearchValue] = useState(''); const [isCurrencyModalVisible, setIsCurrencyModalVisible] = useState(false); const [isPaymentMethodModalVisible, setIsPaymentMethodModalVisible] = useState(false); @@ -59,7 +59,7 @@ export function OnRampView() { const suggestedValues = getCurrencySuggestedValues(paymentCurrency); const purchaseCurrencyCode = purchaseCurrency?.currencyCode?.split('_')[0] ?? purchaseCurrency?.currencyCode; - const networkImage = AssetUtil.getNetworkImage(caipNetwork); + const networkImage = AssetUtil.getNetworkImage(activeNetwork?.id); const getQuotes = useCallback(() => { if (OnRampController.canGenerateQuote()) { diff --git a/packages/scaffold/src/views/w3m-onramp-view/styles.ts b/packages/appkit/src/views/w3m-onramp-view/styles.ts similarity index 100% rename from packages/scaffold/src/views/w3m-onramp-view/styles.ts rename to packages/appkit/src/views/w3m-onramp-view/styles.ts diff --git a/packages/scaffold/src/views/w3m-onramp-view/utils.ts b/packages/appkit/src/views/w3m-onramp-view/utils.ts similarity index 100% rename from packages/scaffold/src/views/w3m-onramp-view/utils.ts rename to packages/appkit/src/views/w3m-onramp-view/utils.ts diff --git a/packages/scaffold/src/views/w3m-swap-preview-view/index.tsx b/packages/appkit/src/views/w3m-swap-preview-view/index.tsx similarity index 100% rename from packages/scaffold/src/views/w3m-swap-preview-view/index.tsx rename to packages/appkit/src/views/w3m-swap-preview-view/index.tsx diff --git a/packages/scaffold/src/views/w3m-swap-preview-view/styles.ts b/packages/appkit/src/views/w3m-swap-preview-view/styles.ts similarity index 100% rename from packages/scaffold/src/views/w3m-swap-preview-view/styles.ts rename to packages/appkit/src/views/w3m-swap-preview-view/styles.ts diff --git a/packages/scaffold/src/views/w3m-swap-select-token-view/index.tsx b/packages/appkit/src/views/w3m-swap-select-token-view/index.tsx similarity index 96% rename from packages/scaffold/src/views/w3m-swap-select-token-view/index.tsx rename to packages/appkit/src/views/w3m-swap-select-token-view/index.tsx index 0a716800..418f0b3e 100644 --- a/packages/scaffold/src/views/w3m-swap-select-token-view/index.tsx +++ b/packages/appkit/src/views/w3m-swap-select-token-view/index.tsx @@ -14,7 +14,7 @@ import { import { AssetUtil, - NetworkController, + ConnectionsController, RouterController, SwapController, type SwapTokenWithBalance @@ -28,9 +28,9 @@ import { createSections } from './utils'; export function SwapSelectTokenView() { const { padding } = useCustomDimensions(); const Theme = useTheme(); - const { caipNetwork } = useSnapshot(NetworkController.state); + const { activeNetwork } = useSnapshot(ConnectionsController.state); const { sourceToken, suggestedTokens } = useSnapshot(SwapController.state); - const networkImage = AssetUtil.getNetworkImage(caipNetwork); + const networkImage = AssetUtil.getNetworkImage(activeNetwork?.id); const [tokenSearch, setTokenSearch] = useState(''); const isSourceToken = RouterController.state.data?.swapTarget === 'sourceToken'; diff --git a/packages/scaffold/src/views/w3m-swap-select-token-view/styles.ts b/packages/appkit/src/views/w3m-swap-select-token-view/styles.ts similarity index 100% rename from packages/scaffold/src/views/w3m-swap-select-token-view/styles.ts rename to packages/appkit/src/views/w3m-swap-select-token-view/styles.ts diff --git a/packages/scaffold/src/views/w3m-swap-select-token-view/utils.ts b/packages/appkit/src/views/w3m-swap-select-token-view/utils.ts similarity index 100% rename from packages/scaffold/src/views/w3m-swap-select-token-view/utils.ts rename to packages/appkit/src/views/w3m-swap-select-token-view/utils.ts diff --git a/packages/scaffold/src/views/w3m-swap-view/index.tsx b/packages/appkit/src/views/w3m-swap-view/index.tsx similarity index 100% rename from packages/scaffold/src/views/w3m-swap-view/index.tsx rename to packages/appkit/src/views/w3m-swap-view/index.tsx diff --git a/packages/scaffold/src/views/w3m-swap-view/styles.ts b/packages/appkit/src/views/w3m-swap-view/styles.ts similarity index 100% rename from packages/scaffold/src/views/w3m-swap-view/styles.ts rename to packages/appkit/src/views/w3m-swap-view/styles.ts diff --git a/packages/scaffold/src/views/w3m-transactions-view/index.tsx b/packages/appkit/src/views/w3m-transactions-view/index.tsx similarity index 100% rename from packages/scaffold/src/views/w3m-transactions-view/index.tsx rename to packages/appkit/src/views/w3m-transactions-view/index.tsx diff --git a/packages/scaffold/src/views/w3m-unsupported-chain-view/index.tsx b/packages/appkit/src/views/w3m-unsupported-chain-view/index.tsx similarity index 79% rename from packages/scaffold/src/views/w3m-unsupported-chain-view/index.tsx rename to packages/appkit/src/views/w3m-unsupported-chain-view/index.tsx index f2074e50..1c200c17 100644 --- a/packages/scaffold/src/views/w3m-unsupported-chain-view/index.tsx +++ b/packages/appkit/src/views/w3m-unsupported-chain-view/index.tsx @@ -5,25 +5,30 @@ import { Icon, ListItem, Separator, Text } from '@reown/appkit-ui-react-native'; import { ApiController, AssetUtil, + ConnectionsController, CoreHelperUtil, - ConnectionUtil, EventsController, NetworkController, NetworkUtil, - type CaipNetwork, type NetworkControllerState } from '@reown/appkit-core-react-native'; +import type { CaipNetwork } from '@reown/appkit-common-react-native'; +import { useAppKit } from '../../AppKitContext'; import styles from './styles'; export function UnsupportedChainView() { - const { caipNetwork, supportsAllNetworks, approvedCaipNetworkIds, requestedCaipNetworks } = - useSnapshot(NetworkController.state) as NetworkControllerState; + const { supportsAllNetworks, approvedCaipNetworkIds, requestedCaipNetworks } = useSnapshot( + NetworkController.state + ) as NetworkControllerState; + const { activeNetwork } = useSnapshot(ConnectionsController.state); const [disconnecting, setDisconnecting] = useState(false); const networks = CoreHelperUtil.sortNetworks(approvedCaipNetworkIds, requestedCaipNetworks); const imageHeaders = ApiController._getApiHeaders(); + const { disconnect } = useAppKit(); const onNetworkPress = async (network: CaipNetwork) => { + //TODO: change to appkit switchNetwork const result = await NetworkUtil.handleNetworkSwitch(network); if (result?.type === 'SWITCH_NETWORK') { EventsController.sendEvent({ @@ -38,7 +43,7 @@ export function UnsupportedChainView() { const onDisconnect = async () => { setDisconnecting(true); - await ConnectionUtil.disconnect(); + await disconnect(ConnectionsController.state.activeNamespace); setDisconnecting(false); }; @@ -59,7 +64,7 @@ export function UnsupportedChainView() { key={item.id} icon="networkPlaceholder" iconBackgroundColor="gray-glass-010" - imageSrc={AssetUtil.getNetworkImage(item)} + imageSrc={AssetUtil.getNetworkImage(item.id)} imageHeaders={imageHeaders} onPress={() => onNetworkPress(item)} testID="button-network" @@ -70,7 +75,7 @@ export function UnsupportedChainView() { {item.name ?? 'Unknown'} - {item.id === caipNetwork?.id && } + {item.id === activeNetwork?.id && } )} ListFooterComponent={ diff --git a/packages/scaffold/src/views/w3m-unsupported-chain-view/styles.ts b/packages/appkit/src/views/w3m-unsupported-chain-view/styles.ts similarity index 100% rename from packages/scaffold/src/views/w3m-unsupported-chain-view/styles.ts rename to packages/appkit/src/views/w3m-unsupported-chain-view/styles.ts diff --git a/packages/scaffold/src/views/w3m-update-email-primary-otp-view/index.tsx b/packages/appkit/src/views/w3m-update-email-primary-otp-view/index.tsx similarity index 100% rename from packages/scaffold/src/views/w3m-update-email-primary-otp-view/index.tsx rename to packages/appkit/src/views/w3m-update-email-primary-otp-view/index.tsx diff --git a/packages/scaffold/src/views/w3m-update-email-secondary-otp-view/index.tsx b/packages/appkit/src/views/w3m-update-email-secondary-otp-view/index.tsx similarity index 100% rename from packages/scaffold/src/views/w3m-update-email-secondary-otp-view/index.tsx rename to packages/appkit/src/views/w3m-update-email-secondary-otp-view/index.tsx diff --git a/packages/scaffold/src/views/w3m-update-email-wallet-view/index.tsx b/packages/appkit/src/views/w3m-update-email-wallet-view/index.tsx similarity index 100% rename from packages/scaffold/src/views/w3m-update-email-wallet-view/index.tsx rename to packages/appkit/src/views/w3m-update-email-wallet-view/index.tsx diff --git a/packages/scaffold/src/views/w3m-update-email-wallet-view/styles.ts b/packages/appkit/src/views/w3m-update-email-wallet-view/styles.ts similarity index 100% rename from packages/scaffold/src/views/w3m-update-email-wallet-view/styles.ts rename to packages/appkit/src/views/w3m-update-email-wallet-view/styles.ts diff --git a/packages/scaffold/src/views/w3m-upgrade-email-wallet-view/index.tsx b/packages/appkit/src/views/w3m-upgrade-email-wallet-view/index.tsx similarity index 100% rename from packages/scaffold/src/views/w3m-upgrade-email-wallet-view/index.tsx rename to packages/appkit/src/views/w3m-upgrade-email-wallet-view/index.tsx diff --git a/packages/scaffold/src/views/w3m-upgrade-to-smart-account-view/index.tsx b/packages/appkit/src/views/w3m-upgrade-to-smart-account-view/index.tsx similarity index 100% rename from packages/scaffold/src/views/w3m-upgrade-to-smart-account-view/index.tsx rename to packages/appkit/src/views/w3m-upgrade-to-smart-account-view/index.tsx diff --git a/packages/scaffold/src/views/w3m-upgrade-to-smart-account-view/styles.ts b/packages/appkit/src/views/w3m-upgrade-to-smart-account-view/styles.ts similarity index 100% rename from packages/scaffold/src/views/w3m-upgrade-to-smart-account-view/styles.ts rename to packages/appkit/src/views/w3m-upgrade-to-smart-account-view/styles.ts diff --git a/packages/scaffold/src/views/w3m-wallet-compatible-networks-view/index.tsx b/packages/appkit/src/views/w3m-wallet-compatible-networks-view/index.tsx similarity index 96% rename from packages/scaffold/src/views/w3m-wallet-compatible-networks-view/index.tsx rename to packages/appkit/src/views/w3m-wallet-compatible-networks-view/index.tsx index 3695bc47..5d74c74b 100644 --- a/packages/scaffold/src/views/w3m-wallet-compatible-networks-view/index.tsx +++ b/packages/appkit/src/views/w3m-wallet-compatible-networks-view/index.tsx @@ -32,7 +32,7 @@ export function WalletCompatibleNetworks() { padding={['s', 's', 's', 's']} > network?.imageId) + .filter(network => network?.id) .slice(0, 5) - .map(AssetUtil.getNetworkImage) + .map(network => AssetUtil.getNetworkImage(network?.id)) .filter(Boolean) as string[]; const label = UiUtil.getTruncateString({ diff --git a/packages/scaffold/src/views/w3m-wallet-receive-view/styles.ts b/packages/appkit/src/views/w3m-wallet-receive-view/styles.ts similarity index 100% rename from packages/scaffold/src/views/w3m-wallet-receive-view/styles.ts rename to packages/appkit/src/views/w3m-wallet-receive-view/styles.ts diff --git a/packages/scaffold/src/views/w3m-wallet-send-preview-view/components/preview-send-details.tsx b/packages/appkit/src/views/w3m-wallet-send-preview-view/components/preview-send-details.tsx similarity index 92% rename from packages/scaffold/src/views/w3m-wallet-send-preview-view/components/preview-send-details.tsx rename to packages/appkit/src/views/w3m-wallet-send-preview-view/components/preview-send-details.tsx index d71df174..129adc20 100644 --- a/packages/scaffold/src/views/w3m-wallet-send-preview-view/components/preview-send-details.tsx +++ b/packages/appkit/src/views/w3m-wallet-send-preview-view/components/preview-send-details.tsx @@ -1,4 +1,5 @@ -import { AssetUtil, type CaipNetwork } from '@reown/appkit-core-react-native'; +import { AssetUtil } from '@reown/appkit-core-react-native'; +import type { CaipNetwork } from '@reown/appkit-common-react-native'; import { BorderRadius, FlexView, @@ -41,7 +42,7 @@ export function PreviewSendDetails({ truncate: 'middle' }); - const networkImage = AssetUtil.getNetworkImage(caipNetwork); + const networkImage = AssetUtil.getNetworkImage(caipNetwork?.id); return ( (''); const [filteredTokens, setFilteredTokens] = useState(tokenBalance ?? []); diff --git a/packages/scaffold/src/views/w3m-wallet-send-select-token-view/styles.ts b/packages/appkit/src/views/w3m-wallet-send-select-token-view/styles.ts similarity index 100% rename from packages/scaffold/src/views/w3m-wallet-send-select-token-view/styles.ts rename to packages/appkit/src/views/w3m-wallet-send-select-token-view/styles.ts diff --git a/packages/scaffold/src/views/w3m-wallet-send-view/index.tsx b/packages/appkit/src/views/w3m-wallet-send-view/index.tsx similarity index 100% rename from packages/scaffold/src/views/w3m-wallet-send-view/index.tsx rename to packages/appkit/src/views/w3m-wallet-send-view/index.tsx diff --git a/packages/scaffold/src/views/w3m-wallet-send-view/styles.ts b/packages/appkit/src/views/w3m-wallet-send-view/styles.ts similarity index 100% rename from packages/scaffold/src/views/w3m-wallet-send-view/styles.ts rename to packages/appkit/src/views/w3m-wallet-send-view/styles.ts diff --git a/packages/scaffold/src/views/w3m-what-is-a-network-view/index.tsx b/packages/appkit/src/views/w3m-what-is-a-network-view/index.tsx similarity index 100% rename from packages/scaffold/src/views/w3m-what-is-a-network-view/index.tsx rename to packages/appkit/src/views/w3m-what-is-a-network-view/index.tsx diff --git a/packages/scaffold/src/views/w3m-what-is-a-network-view/styles.ts b/packages/appkit/src/views/w3m-what-is-a-network-view/styles.ts similarity index 100% rename from packages/scaffold/src/views/w3m-what-is-a-network-view/styles.ts rename to packages/appkit/src/views/w3m-what-is-a-network-view/styles.ts diff --git a/packages/scaffold/src/views/w3m-what-is-a-wallet-view/index.tsx b/packages/appkit/src/views/w3m-what-is-a-wallet-view/index.tsx similarity index 100% rename from packages/scaffold/src/views/w3m-what-is-a-wallet-view/index.tsx rename to packages/appkit/src/views/w3m-what-is-a-wallet-view/index.tsx diff --git a/packages/scaffold/src/views/w3m-what-is-a-wallet-view/styles.ts b/packages/appkit/src/views/w3m-what-is-a-wallet-view/styles.ts similarity index 100% rename from packages/scaffold/src/views/w3m-what-is-a-wallet-view/styles.ts rename to packages/appkit/src/views/w3m-what-is-a-wallet-view/styles.ts diff --git a/packages/scaffold/tsconfig.json b/packages/appkit/tsconfig.json similarity index 100% rename from packages/scaffold/tsconfig.json rename to packages/appkit/tsconfig.json diff --git a/packages/bitcoin/.eslintignore b/packages/bitcoin/.eslintignore new file mode 100644 index 00000000..c18ed016 --- /dev/null +++ b/packages/bitcoin/.eslintignore @@ -0,0 +1,2 @@ +node_modules/ +lib/ \ No newline at end of file diff --git a/packages/bitcoin/.eslintrc.json b/packages/bitcoin/.eslintrc.json new file mode 100644 index 00000000..b9233ee4 --- /dev/null +++ b/packages/bitcoin/.eslintrc.json @@ -0,0 +1,3 @@ +{ + "extends": ["../../.eslintrc"] +} diff --git a/packages/bitcoin/.npmignore b/packages/bitcoin/.npmignore new file mode 100644 index 00000000..e203f76a --- /dev/null +++ b/packages/bitcoin/.npmignore @@ -0,0 +1,10 @@ +*.log +*.env +npm-debug.log* +node_modules +package-lock.json +src +tests +index.ts +.eslintrc.json +.turbo diff --git a/packages/bitcoin/bob.config.js b/packages/bitcoin/bob.config.js new file mode 100644 index 00000000..b7ca0ad6 --- /dev/null +++ b/packages/bitcoin/bob.config.js @@ -0,0 +1,14 @@ +module.exports = { + source: 'src', + output: 'lib', + targets: [ + 'commonjs', + 'module', + [ + 'typescript', + { + tsc: '../../node_modules/.bin/tsc' + } + ] + ] +}; diff --git a/packages/bitcoin/package.json b/packages/bitcoin/package.json new file mode 100644 index 00000000..33339838 --- /dev/null +++ b/packages/bitcoin/package.json @@ -0,0 +1,44 @@ +{ + "name": "@reown/appkit-bitcoin-react-native", + "version": "1.2.3", + "main": "lib/commonjs/index.js", + "types": "lib/typescript/index.d.ts", + "module": "lib/module/index.js", + "source": "src/index.tsx", + "react-native": "src/index.tsx", + "scripts": { + "build": "bob build", + "clean": "rm -rf lib", + "lint": "eslint . --ext .js,.jsx,.ts,.tsx" + }, + "files": [ + "src", + "lib", + "!**/__tests__", + "!**/__fixtures__", + "!**/__mocks__" + ], + "keywords": [ + "web3", + "crypto", + "bitcoin", + "appkit", + "reown", + "walletconnect", + "react-native" + ], + "repository": "https://github.com/reown-com/appkit-react-native", + "author": "Reown (https://reown.com)", + "homepage": "https://reown.com/appkit", + "license": "Apache-2.0", + "bugs": { + "url": "https://github.com/reown-com/appkit-react-native/issues" + }, + "publishConfig": { + "registry": "https://registry.npmjs.org/", + "access": "public" + }, + "dependencies": { + "@reown/appkit-common-react-native": "1.2.3" + } +} diff --git a/packages/bitcoin/readme.md b/packages/bitcoin/readme.md new file mode 100644 index 00000000..60524ccd --- /dev/null +++ b/packages/bitcoin/readme.md @@ -0,0 +1,9 @@ +#### 📚 [Documentation](https://docs.reown.com/appkit/react-native/core/installation) + +#### 🔎 [Examples](https://github.com/reown-com/react-native-examples) + +#### 🔗 [Website](https://reown.com/appkit) + +# AppKit + +Your on-ramp to web3 multichain. AppKit is a versatile library that makes it super easy to connect users with your Dapp and start interacting with the blockchain. diff --git a/packages/bitcoin/src/adapter.ts b/packages/bitcoin/src/adapter.ts new file mode 100644 index 00000000..e38e8992 --- /dev/null +++ b/packages/bitcoin/src/adapter.ts @@ -0,0 +1,93 @@ +import { + BlockchainAdapter, + type AppKitNetwork, + type CaipAddress, + type ChainNamespace, + type GetBalanceParams, + type GetBalanceResponse +} from '@reown/appkit-common-react-native'; +import { BitcoinApi } from './utils/BitcoinApi'; +import { UnitsUtil } from './utils/UnitsUtil'; + +export class BitcoinAdapter extends BlockchainAdapter { + private static supportedNamespace: ChainNamespace = 'bip122'; + private static api = BitcoinApi; + + constructor(configParams: { projectId: string }) { + super({ + projectId: configParams.projectId, + supportedNamespace: BitcoinAdapter.supportedNamespace + }); + } + + async getBalance(params: GetBalanceParams): Promise { + const { network, address } = params; + + if (!this.connector) throw new Error('No active connector'); + if (!network) throw new Error('No network provided'); + + const balanceCaipAddress = + address || this.getAccounts()?.find(account => account.includes(network.id.toString())); + + const balanceAddress = balanceCaipAddress?.split(':')[2]; + + if (!balanceCaipAddress || !balanceAddress) { + return Promise.resolve({ amount: '0.00', symbol: 'BTC' }); + } + + try { + const utxos = await BitcoinAdapter.api.getUTXOs({ + network, + address: balanceAddress + }); + + const balance = utxos.reduce((acc, utxo) => acc + utxo.value, 0); + const formattedBalance = UnitsUtil.parseSatoshis(balance.toString(), network); + + this.emit('balanceChanged', { + namespace: this.getSupportedNamespace(), + address: balanceCaipAddress, + balance: { + amount: formattedBalance, + symbol: network.nativeCurrency.symbol + } + }); + + return { amount: formattedBalance, symbol: network.nativeCurrency.symbol }; + } catch (error) { + return { amount: '0.00', symbol: 'BTC' }; + } + } + + async switchNetwork(network: AppKitNetwork): Promise { + if (!this.connector) throw new Error('No active connector'); + + const provider = this.connector.getProvider(); + if (!provider) throw new Error('No active provider'); + + try { + await this.connector.switchNetwork(network); + + return; + } catch (switchError: any) { + throw switchError; + } + } + + getAccounts(): CaipAddress[] | undefined { + if (!this.connector) throw new Error('No active connector'); + const namespaces = this.connector.getNamespaces(); + + return namespaces[this.getSupportedNamespace()]?.accounts; + } + + disconnect(): Promise { + if (!this.connector) throw new Error('SolanaAdapter:disconnect - No active connector'); + + return this.connector.disconnect(); + } + + getSupportedNamespace(): ChainNamespace { + return BitcoinAdapter.supportedNamespace; + } +} diff --git a/packages/bitcoin/src/index.tsx b/packages/bitcoin/src/index.tsx new file mode 100644 index 00000000..d1cec69d --- /dev/null +++ b/packages/bitcoin/src/index.tsx @@ -0,0 +1,2 @@ +import { BitcoinAdapter } from './adapter'; +export { BitcoinAdapter }; diff --git a/packages/bitcoin/src/utils/BitcoinApi.ts b/packages/bitcoin/src/utils/BitcoinApi.ts new file mode 100644 index 00000000..a7521245 --- /dev/null +++ b/packages/bitcoin/src/utils/BitcoinApi.ts @@ -0,0 +1,41 @@ +import type { AppKitNetwork } from '@reown/appkit-common-react-native'; + +export const BitcoinApi: BitcoinApi.Interface = { + getUTXOs: async ({ network, address }: BitcoinApi.GetUTXOsParams): Promise => { + const isTestnet = network.caipNetworkId === 'bip122:000000000933ea01ad0ee984209779ba'; + // Make chain dynamic + + //TODO: Call rpc to get balance + const url = `https://mempool.space${isTestnet ? '/testnet' : ''}/api/address/${address}/utxo`; + const response = await fetch(url); + + if (!response.ok) { + throw new Error(`Failed to fetch UTXOs: ${await response.text()}`); + } + + return (await response.json()) as BitcoinApi.UTXO[]; + } +}; + +export namespace BitcoinApi { + export type Interface = { + getUTXOs: (params: GetUTXOsParams) => Promise; + }; + + export type GetUTXOsParams = { + network: AppKitNetwork; + address: string; + }; + + export type UTXO = { + txid: string; + vout: number; + value: number; + status: { + confirmed: boolean; + block_height: number; + block_hash: string; + block_time: number; + }; + }; +} diff --git a/packages/bitcoin/src/utils/UnitsUtil.ts b/packages/bitcoin/src/utils/UnitsUtil.ts new file mode 100644 index 00000000..66bd85f3 --- /dev/null +++ b/packages/bitcoin/src/utils/UnitsUtil.ts @@ -0,0 +1,11 @@ +import type { AppKitNetwork } from '@reown/appkit-common-react-native'; + +export const UnitsUtil = { + parseSatoshis(amount: string, network: AppKitNetwork): string { + const value = parseFloat(amount) / 10 ** network.nativeCurrency.decimals; + + return Intl.NumberFormat('en-US', { + maximumFractionDigits: network.nativeCurrency.decimals + }).format(value); + } +}; diff --git a/packages/bitcoin/tsconfig.json b/packages/bitcoin/tsconfig.json new file mode 100644 index 00000000..512da539 --- /dev/null +++ b/packages/bitcoin/tsconfig.json @@ -0,0 +1,5 @@ +{ + "extends": "../../tsconfig.json", + "include": ["src", "src/index.tsx"], + "exclude": ["lib", "node_modules"] +} diff --git a/packages/common/src/utils/PresetsUtil.ts b/packages/common/src/utils/PresetsUtil.ts index b949dded..3f9f6879 100644 --- a/packages/common/src/utils/PresetsUtil.ts +++ b/packages/common/src/utils/PresetsUtil.ts @@ -7,11 +7,11 @@ export const PresetsUtil = { 'fd20dc426fb37566d803205b19bbc1d4096b248ac04548e3cfb6b3a38bd033aa' } as Record, - EIP155NetworkImageIds: { + NetworkImageIds: { // Ethereum 1: 'ba0ba0cd-17c6-4806-ad93-f9d174f17900', // Arbitrum - 42161: '600a9a04-c1b9-42ca-6785-9b4b6ff85200', + 42161: '3bff954d-5cb0-47a0-9a23-d20192e74600', // Avalanche 43114: '30c46e53-e989-45fb-4549-be3bd4eb3b00', // Binance Smart Chain @@ -22,6 +22,20 @@ export const PresetsUtil = { 10: 'ab9c186a-c52f-464b-2906-ca59d760a400', // Polygon 137: '41d04d42-da3b-4453-8506-668cc0727900', + // Mantle + 5000: 'e86fae9b-b770-4eea-e520-150e12c81100', + // Hedera Mainnet + 295: '6a97d510-cac8-4e58-c7ce-e8681b044c00', + // Sepolia + 11_155_111: 'e909ea0a-f92a-4512-c8fc-748044ea6800', + // Base Sepolia + 84532: 'a18a7ecd-e307-4360-4746-283182228e00', + // Unichain Sepolia + 1301: '4eeea7ef-0014-4649-5d1d-07271a80f600', + // Unichain Mainnet + 130: '2257980a-3463-48c6-cbac-a42d2a956e00', + // Monad Testnet + 10_143: '0a728e83-bacb-46db-7844-948f05434900', // Gnosis 100: '02b53f6a-e3d4-479e-1cb4-21178987d100', // EVMos @@ -45,7 +59,26 @@ export const PresetsUtil = { // Base 8453: '7289c336-3981-4081-c5f4-efc26ac64a00', // Aurora - 1313161554: '3ff73439-a619-4894-9262-4470c773a100' + 1313161554: '3ff73439-a619-4894-9262-4470c773a100', + // Ronin Mainnet + 2020: 'b8101fc0-9c19-4b6f-ec65-f6dfff106e00', + // Saigon Testnet (a.k.a. Ronin) + 2021: 'b8101fc0-9c19-4b6f-ec65-f6dfff106e00', + // Berachain Mainnet + 80094: 'e329c2c9-59b0-4a02-83e4-212ff3779900', + // Abstract Mainnet + 2741: 'fc2427d1-5af9-4a9c-8da5-6f94627cd900', + + // Solana networks + /// Mainnet + '5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp': 'a1b58899-f671-4276-6a5e-56ca5bd59700', + /// Testnet + '4uhcVJyU9pJkvQyS88uRDiswHXSCkY3z': 'a1b58899-f671-4276-6a5e-56ca5bd59700', + + // Bitcoin + '000000000019d6689c085ae165831e93': '0b4838db-0161-4ffe-022d-532bf03dba00', + // Bitcoin Testnet + '000000000933ea01ad0ee984209779ba': '39354064-d79b-420b-065d-f980c4b78200' } as Record, ConnectorNamesMap: { diff --git a/packages/common/src/utils/TypeUtil.ts b/packages/common/src/utils/TypeUtil.ts index 13ea7ede..10faed0a 100644 --- a/packages/common/src/utils/TypeUtil.ts +++ b/packages/common/src/utils/TypeUtil.ts @@ -1,3 +1,41 @@ +import { EventEmitter } from 'events'; + +export type CaipAddress = `${string}:${string}:${string}`; + +export type CaipNetworkId = `${string}:${string}`; + +export type ChainNamespace = 'eip155' | 'solana' | 'polkadot' | 'bip122'; + +export type Network = { + // Core viem/chain properties + id: number | string; + name: string; + nativeCurrency: { name: string; symbol: string; decimals: number }; + rpcUrls: { + default: { http: readonly string[] }; + [key: string]: { http: readonly string[] } | undefined; + }; + blockExplorers?: { + default: { name: string; url: string }; + [key: string]: { name: string; url: string } | undefined; + }; +}; + +export type AppKitNetwork = Network & { + // AppKit specific / CAIP properties (Optional in type, but often needed in practice) + chainNamespace?: ChainNamespace; // e.g., 'eip155' + caipNetworkId?: CaipNetworkId; // e.g., 'eip155:1' + testnet?: boolean; + deprecatedCaipNetworkId?: CaipNetworkId; // for Solana +}; + +export interface CaipNetwork { + id: CaipNetworkId; + name?: string; + imageId?: string; + imageUrl?: string; +} + export interface Balance { name: string; symbol: string; @@ -94,4 +132,241 @@ export interface ThemeVariables { accent?: string; } +export interface Token { + address: string; + image?: string; +} + +export type Tokens = Record; + export type ConnectorType = 'WALLET_CONNECT' | 'COINBASE' | 'AUTH' | 'EXTERNAL'; + +//********** Adapter Event Payloads **********// +export type AccountsChangedEvent = { + accounts: string[]; + namespace: ChainNamespace; +}; + +export type ChainChangedEvent = { + chainId: string; + namespace: ChainNamespace; +}; + +export type DisconnectEvent = { + namespace: ChainNamespace; +}; + +export type BalanceChangedEvent = { + namespace: ChainNamespace; + address: CaipAddress; + balance: { + amount: string; + symbol: string; + contractAddress?: ContractAddress; + }; +}; + +//********** Adapter Event Map **********// +export interface AdapterEvents { + accountsChanged: (event: AccountsChangedEvent) => void; + chainChanged: (event: ChainChangedEvent) => void; + disconnect: (event: DisconnectEvent) => void; + balanceChanged: (event: BalanceChangedEvent) => void; +} + +//********** Adapter Types **********// +export abstract class BlockchainAdapter extends EventEmitter { + public projectId: string; + public connector?: WalletConnector; + public supportedNamespace: ChainNamespace; + + // Typed emit method + override emit( + event: K, + payload: Parameters[0] + ): boolean { + return super.emit(event, payload); + } + + constructor({ + projectId, + supportedNamespace + }: { + projectId: string; + supportedNamespace: ChainNamespace; + }) { + super(); + this.projectId = projectId; + this.supportedNamespace = supportedNamespace; + } + + setConnector(connector: WalletConnector) { + this.connector = connector; + this.subscribeToEvents(); + } + + removeConnector() { + this.connector = undefined; + } + + getProvider(): Provider { + if (!this.connector) throw new Error('No active connector'); + + return this.connector.getProvider(); + } + + subscribeToEvents(): void { + const provider = this.connector?.getProvider(); + if (!provider) return; + + provider.on('chainChanged', this.onChainChanged.bind(this)); + provider.on('accountsChanged', this.onAccountsChanged.bind(this)); + provider.on('disconnect', this.onDisconnect.bind(this)); + } + + onChainChanged(chainId: string): void { + const _chains = this.getAccounts()?.map(account => account.split(':')[1]); + const shouldEmit = _chains?.some(chain => chain === chainId); + + if (shouldEmit) { + this.emit('chainChanged', { chainId, namespace: this.getSupportedNamespace() }); + } + } + + onAccountsChanged(accounts: string[]): void { + const _accounts = this.getAccounts(); + const shouldEmit = _accounts?.some(account => { + const accountAddress = account.split(':')[2]; + + return accountAddress !== undefined && accounts.includes(accountAddress); + }); + + if (shouldEmit) { + this.emit('accountsChanged', { accounts, namespace: this.getSupportedNamespace() }); + } + } + + onDisconnect(): void { + this.emit('disconnect', { namespace: this.getSupportedNamespace() }); + + const provider = this.connector?.getProvider(); + if (provider) { + provider.off('chainChanged', this.onChainChanged.bind(this)); + provider.off('accountsChanged', this.onAccountsChanged.bind(this)); + provider.off('disconnect', this.onDisconnect.bind(this)); + } + + this.connector = undefined; + } + + abstract disconnect(): Promise; + abstract getSupportedNamespace(): ChainNamespace; + abstract getBalance(params: GetBalanceParams): Promise; + abstract getAccounts(): CaipAddress[] | undefined; + abstract switchNetwork(network: AppKitNetwork): Promise; +} + +export abstract class EVMAdapter extends BlockchainAdapter { + // ens logic +} + +export abstract class SolanaBaseAdapter extends BlockchainAdapter {} + +export interface GetBalanceParams { + address?: CaipAddress; + network?: AppKitNetwork; + tokens?: Tokens; +} + +type ContractAddress = CaipAddress; + +export interface GetBalanceResponse { + amount: string; + symbol: string; + contractAddress?: ContractAddress; +} + +//********** Connector Types **********// +interface BaseNamespace { + chains?: CaipNetworkId[]; + accounts: CaipAddress[]; + methods: string[]; + events: string[]; +} + +type Namespace = BaseNamespace; + +export type Namespaces = Record; + +export type ProposalNamespaces = Record< + string, + Omit & Required> +>; + +export abstract class WalletConnector extends EventEmitter { + public type: New_ConnectorType; + protected provider: Provider; + protected namespaces?: Namespaces; + protected wallet?: WalletInfo; + + constructor({ type, provider }: { type: New_ConnectorType; provider: Provider }) { + super(); + this.type = type; + this.provider = provider; + } + + abstract connect(opts: { + namespaces?: ProposalNamespaces; + defaultChain?: CaipNetworkId; + }): Promise; + abstract disconnect(): Promise; + abstract getProvider(): Provider; + abstract getNamespaces(): Namespaces; + abstract getChainId(namespace: ChainNamespace): CaipNetworkId | undefined; + abstract getWalletInfo(): WalletInfo | undefined; + abstract switchNetwork(network: AppKitNetwork): Promise; +} + +//********** Provider Types **********// + +export interface Provider { + connect(params?: any): Promise; + disconnect(): Promise; + request( + args: RequestArguments, + chain?: string | undefined, + expiry?: number | undefined + ): Promise; + on(event: string, listener: (args?: any) => void): any; + off(event: string, listener: (args?: any) => void): any; +} + +export interface RequestArguments { + method: string; + params?: unknown[] | Record | object | undefined; +} + +//TODO: rename this and remove the old one ConnectorType +export type New_ConnectorType = 'walletconnect' | 'coinbase' | 'auth'; + +//********** Others **********// + +export interface ConnectionResponse { + accounts: string[]; + chainId: string; + [key: string]: any; +} + +export interface WalletInfo { + name?: string; + icon?: string; + description?: string; + url?: string; + icons?: string[]; + redirect?: { + native?: string; + universal?: string; + linkMode?: boolean; + }; + [key: string]: unknown; +} diff --git a/packages/core/src/__tests__/controllers/BlockchainApiController.test.ts b/packages/core/src/__tests__/controllers/BlockchainApiController.test.ts index 63258e38..72054a6e 100644 --- a/packages/core/src/__tests__/controllers/BlockchainApiController.test.ts +++ b/packages/core/src/__tests__/controllers/BlockchainApiController.test.ts @@ -5,6 +5,25 @@ const MOCK_IDENTITY = { avatar: 'https://example.com' }; +// Mock FetchUtil using jest +jest.mock('../../utils/FetchUtil', () => ({ + FetchUtil: jest.fn().mockImplementation(() => ({ + get: jest.fn().mockResolvedValue(MOCK_IDENTITY) + })) +})); + +// Mock ConnectionsController +jest.mock('../../controllers/ConnectionsController', () => ({ + ConnectionsController: { + state: { + activeCaipNetworkId: 'eip155:1' + } + } +})); + +// Mock isNetworkSupported using jest +jest.spyOn(BlockchainApiController, 'isNetworkSupported').mockResolvedValue(true); + // @ts-ignore global.fetch = jest.fn(() => Promise.resolve({ @@ -18,9 +37,16 @@ global.fetch = jest.fn(() => // -- Tests -------------------------------------------------------------------- describe('BlockchainApiController', () => { + // Reset the API state before each test + beforeEach(() => { + // Ensure API instance is properly mocked + BlockchainApiController.state.api = { + get: jest.fn().mockResolvedValue(MOCK_IDENTITY) + } as any; + }); + it('fetch identity of account', async () => { let identity = await BlockchainApiController.fetchIdentity({ - caipChainId: 'eip155:1', address: '0x00000' }); expect(identity).toEqual(MOCK_IDENTITY); diff --git a/packages/core/src/__tests__/controllers/OnRampController.test.ts b/packages/core/src/__tests__/controllers/OnRampController.test.ts index da42e4d2..a40c78da 100644 --- a/packages/core/src/__tests__/controllers/OnRampController.test.ts +++ b/packages/core/src/__tests__/controllers/OnRampController.test.ts @@ -110,7 +110,7 @@ beforeEach(() => { OnRampController.resetState(); }); -// -- Tests -------------------------------------------------------------------- +// -- Tests --------------------------------------------------------------------- describe('OnRampController', () => { it('should have valid default state', () => { expect(OnRampController.state.quotesLoading).toBe(false); @@ -223,7 +223,7 @@ describe('OnRampController', () => { Object.defineProperty(ConstantsUtil, 'COUNTRY_CURRENCIES', { value: { US: 'USD', - AR: 'ARS' // Assuming mockCountry2 has ES country code + AR: 'ARS' }, configurable: true }); @@ -237,9 +237,17 @@ describe('OnRampController', () => { mockFiatCurrency, // USD mockFiatCurrency2 // ARS ]); + (StorageUtil.getOnRampCountries as jest.Mock).mockResolvedValue([]); + (StorageUtil.getOnRampPreferredCountry as jest.Mock).mockResolvedValue(null); + (StorageUtil.getOnRampFiatCurrencies as jest.Mock).mockResolvedValue([]); + (StorageUtil.getOnRampPreferredFiatCurrency as jest.Mock).mockResolvedValue(null); + (BlockchainApiController.fetchOnRampPaymentMethods as jest.Mock).mockResolvedValue([]); + (BlockchainApiController.fetchOnRampCryptoCurrencies as jest.Mock).mockResolvedValue([]); - // Execute - await OnRampController.loadOnRampData(); + // Explicitly set the state to ensure the initial values are as expected + OnRampController.state.selectedCountry = mockCountry; + OnRampController.state.paymentCurrency = mockFiatCurrency; + OnRampController.state.paymentCurrencies = [mockFiatCurrency, mockFiatCurrency2]; // First verify the initial state expect(OnRampController.state.selectedCountry).toEqual(mockCountry); diff --git a/packages/core/src/controllers/AccountController.ts b/packages/core/src/controllers/AccountController.ts index 808d38f4..bd2172f7 100644 --- a/packages/core/src/controllers/AccountController.ts +++ b/packages/core/src/controllers/AccountController.ts @@ -1,9 +1,9 @@ import { proxy } from 'valtio'; import { subscribeKey as subKey } from 'valtio/utils'; -import type { Balance } from '@reown/appkit-common-react-native'; +import type { Balance, CaipAddress } from '@reown/appkit-common-react-native'; import { CoreHelperUtil } from '../utils/CoreHelperUtil'; -import type { AppKitFrameAccountType, CaipAddress, ConnectedWalletInfo } from '../utils/TypeUtil'; +import type { AppKitFrameAccountType, ConnectedWalletInfo } from '../utils/TypeUtil'; import { NetworkController } from './NetworkController'; import { BlockchainApiController } from './BlockchainApiController'; import { SnackController } from './SnackController'; diff --git a/packages/core/src/controllers/ApiController.ts b/packages/core/src/controllers/ApiController.ts index aaafc162..132ca702 100644 --- a/packages/core/src/controllers/ApiController.ts +++ b/packages/core/src/controllers/ApiController.ts @@ -12,12 +12,13 @@ import type { WcWallet } from '../utils/TypeUtil'; import { AssetController } from './AssetController'; -import { NetworkController } from './NetworkController'; import { OptionsController } from './OptionsController'; import { ConnectorController } from './ConnectorController'; import { ConnectionController } from './ConnectionController'; import { ApiUtil } from '../utils/ApiUtil'; import { SnackController } from './SnackController'; +import { ConnectionsController } from './ConnectionsController'; +import { PresetsUtil } from '@reown/appkit-common-react-native'; // -- Helpers ------------------------------------------- // const baseUrl = CoreHelperUtil.getApiUrl(); @@ -92,11 +93,16 @@ export const ApiController = { } }, - async _fetchNetworkImage(imageId: string) { + async _fetchNetworkImage(networkId: string) { + const imageId = PresetsUtil.NetworkImageIds[networkId]; + if (!imageId) { + return; + } + const headers = ApiController._getApiHeaders(); const url = await api.fetchImage(`/public/getAssetImage/${imageId}`, headers); if (url) { - AssetController.setNetworkImage(imageId, url); + AssetController.setNetworkImage(networkId, url); } }, @@ -109,11 +115,10 @@ export const ApiController = { }, async fetchNetworkImages() { - const { requestedCaipNetworks } = NetworkController.state; - const ids = requestedCaipNetworks?.map(({ imageId }) => imageId).filter(Boolean); - if (ids) { + const networks = ConnectionsController.state.networks; + if (networks) { await CoreHelperUtil.allSettled( - (ids as string[]).map(id => ApiController._fetchNetworkImage(id)) + networks.map(network => ApiController._fetchNetworkImage(network.id as string)) ); } }, diff --git a/packages/core/src/controllers/BlockchainApiController.ts b/packages/core/src/controllers/BlockchainApiController.ts index 5a7f7f26..17e3d9a6 100644 --- a/packages/core/src/controllers/BlockchainApiController.ts +++ b/packages/core/src/controllers/BlockchainApiController.ts @@ -37,10 +37,12 @@ import type { import { OptionsController } from './OptionsController'; import { ConstantsUtil } from '../utils/ConstantsUtil'; import { ApiUtil } from '../utils/ApiUtil'; +import type { CaipNetworkId } from '@reown/appkit-common-react-native'; +import { ConnectionsController } from './ConnectionsController'; +import { SnackController } from './SnackController'; // -- Helpers ------------------------------------------- // const baseUrl = CoreHelperUtil.getBlockchainApiUrl(); -const stagingUrl = CoreHelperUtil.getBlockchainStagingApiUrl(); const getHeaders = () => { const { sdkType, sdkVersion } = OptionsController.state; @@ -58,22 +60,54 @@ const getHeaders = () => { export interface BlockchainApiControllerState { clientId: string | null; api: FetchUtil; - stageApi: FetchUtil; + supportedChains: { http: CaipNetworkId[]; ws: CaipNetworkId[] }; } // -- State --------------------------------------------- // const state = proxy({ clientId: null, api: new FetchUtil({ baseUrl }), - //TODO: remove this before release - stageApi: new FetchUtil({ baseUrl: stagingUrl }) + supportedChains: { http: [], ws: [] } }); // -- Controller ---------------------------------------- // export const BlockchainApiController = { state, - fetchIdentity({ address }: BlockchainApiIdentityRequest) { + async isNetworkSupported(networkId?: CaipNetworkId) { + if (!networkId) { + return false; + } + try { + if (!state.supportedChains.http.length) { + await BlockchainApiController.getSupportedNetworks(); + } + } catch (e) { + return false; + } + + return state.supportedChains.http.includes(networkId); + }, + + async getSupportedNetworks() { + const supportedChains = await state.api.get({ + path: 'v1/supported-chains' + }); + + state.supportedChains = supportedChains!; + + return supportedChains; + }, + + async fetchIdentity({ address }: BlockchainApiIdentityRequest) { + const isSupported = await BlockchainApiController.isNetworkSupported( + ConnectionsController.state.activeCaipNetworkId + ); + + if (!isSupported) { + return { avatar: '', name: '' }; + } + return state.api.get({ path: `/v1/identity/${address}`, params: { @@ -83,29 +117,48 @@ export const BlockchainApiController = { }); }, - fetchTransactions({ + async fetchTransactions({ account, projectId, cursor, onramp, signal, - cache + cache, + chainId }: BlockchainApiTransactionsRequest) { - return state.api.get({ + const _chainId = chainId ?? ConnectionsController.state.activeCaipNetworkId; + const isSupported = await BlockchainApiController.isNetworkSupported(_chainId); + + if (!isSupported) { + return { data: [], next: undefined }; + } + + const response = await state.api.get({ path: `/v1/account/${account}/history`, headers: getHeaders(), params: { projectId, cursor, - onramp + onramp, + chainId: _chainId }, signal, cache }); + + return response; }, - fetchTokenPrice({ projectId, addresses }: BlockchainApiTokenPriceRequest) { - return state.api.post({ + async fetchTokenPrice({ projectId, addresses }: BlockchainApiTokenPriceRequest) { + const isSupported = await BlockchainApiController.isNetworkSupported( + ConnectionsController.state.activeCaipNetworkId + ); + + if (!isSupported) { + return { fungibles: [] }; + } + + const response = await state.api.post({ path: '/v1/fungible/price', body: { projectId, @@ -114,9 +167,23 @@ export const BlockchainApiController = { }, headers: getHeaders() }); + + return response; }, - fetchSwapAllowance({ projectId, tokenAddress, userAddress }: BlockchainApiSwapAllowanceRequest) { + async fetchSwapAllowance({ + projectId, + tokenAddress, + userAddress + }: BlockchainApiSwapAllowanceRequest) { + const isSupported = await BlockchainApiController.isNetworkSupported( + ConnectionsController.state.activeCaipNetworkId + ); + + if (!isSupported) { + return { allowance: '0' }; + } + return state.api.get({ path: `/v1/convert/allowance`, params: { @@ -128,7 +195,15 @@ export const BlockchainApiController = { }); }, - fetchGasPrice({ projectId, chainId }: BlockchainApiGasPriceRequest) { + async fetchGasPrice({ projectId, chainId }: BlockchainApiGasPriceRequest) { + const isSupported = await BlockchainApiController.isNetworkSupported( + ConnectionsController.state.activeCaipNetworkId + ); + + if (!isSupported) { + throw new Error('Network not supported for Gas Price'); + } + return state.api.get({ path: `/v1/convert/gas-price`, headers: getHeaders(), @@ -139,7 +214,7 @@ export const BlockchainApiController = { }); }, - fetchSwapQuote({ + async fetchSwapQuote({ projectId, amount, userAddress, @@ -147,6 +222,14 @@ export const BlockchainApiController = { to, gasPrice }: BlockchainApiSwapQuoteRequest) { + const isSupported = await BlockchainApiController.isNetworkSupported( + ConnectionsController.state.activeCaipNetworkId + ); + + if (!isSupported) { + return { quotes: [] }; + } + return state.api.get({ path: `/v1/convert/quotes`, headers: getHeaders(), @@ -161,7 +244,15 @@ export const BlockchainApiController = { }); }, - fetchSwapTokens({ projectId, chainId }: BlockchainApiSwapTokensRequest) { + async fetchSwapTokens({ projectId, chainId }: BlockchainApiSwapTokensRequest) { + const isSupported = await BlockchainApiController.isNetworkSupported( + ConnectionsController.state.activeCaipNetworkId + ); + + if (!isSupported) { + return { tokens: [] }; + } + return state.api.get({ path: `/v1/convert/tokens`, headers: getHeaders(), @@ -172,13 +263,21 @@ export const BlockchainApiController = { }); }, - generateSwapCalldata({ + async generateSwapCalldata({ amount, from, projectId, to, userAddress }: BlockchainApiGenerateSwapCalldataRequest) { + const isSupported = await BlockchainApiController.isNetworkSupported( + ConnectionsController.state.activeCaipNetworkId + ); + + if (!isSupported) { + throw new Error('Network not supported for Swaps'); + } + return state.api.post({ path: '/v1/convert/build-transaction', headers: getHeaders(), @@ -195,12 +294,20 @@ export const BlockchainApiController = { }); }, - generateApproveCalldata({ + async generateApproveCalldata({ from, projectId, to, userAddress }: BlockchainApiGenerateApproveCalldataRequest) { + const isSupported = await BlockchainApiController.isNetworkSupported( + ConnectionsController.state.activeCaipNetworkId + ); + + if (!isSupported) { + throw new Error('Network not supported for Swaps'); + } + return state.api.get({ path: `/v1/convert/build-approve`, headers: getHeaders(), @@ -214,6 +321,15 @@ export const BlockchainApiController = { }, async getBalance(address: string, chainId?: string, forceUpdate?: string) { + const isSupported = await BlockchainApiController.isNetworkSupported( + ConnectionsController.state.activeCaipNetworkId + ); + if (!isSupported) { + SnackController.showError('Token Balance Unavailable'); + + return { balances: [] }; + } + return state.api.get({ path: `/v1/account/${address}/balance`, headers: getHeaders(), @@ -238,7 +354,7 @@ export const BlockchainApiController = { }, async fetchOnRampCountries() { - return await state.stageApi.get({ + return await state.api.get({ path: '/v1/onramp/providers/properties', headers: getHeaders(), params: { @@ -249,7 +365,7 @@ export const BlockchainApiController = { }, async fetchOnRampServiceProviders() { - return await state.stageApi.get({ + return await state.api.get({ path: '/v1/onramp/providers', headers: getHeaders(), params: { @@ -259,7 +375,7 @@ export const BlockchainApiController = { }, async fetchOnRampPaymentMethods(params: { countries?: string }) { - return await state.stageApi.get({ + return await state.api.get({ path: '/v1/onramp/providers/properties', headers: getHeaders(), params: { @@ -271,7 +387,7 @@ export const BlockchainApiController = { }, async fetchOnRampCryptoCurrencies(params: { countries?: string }) { - return await state.stageApi.get({ + return await state.api.get({ path: '/v1/onramp/providers/properties', headers: getHeaders(), params: { @@ -283,7 +399,7 @@ export const BlockchainApiController = { }, async fetchOnRampFiatCurrencies() { - return await state.stageApi.get({ + return await state.api.get({ path: '/v1/onramp/providers/properties', headers: getHeaders(), params: { @@ -294,7 +410,7 @@ export const BlockchainApiController = { }, async fetchOnRampFiatLimits() { - return await state.stageApi.get({ + return await state.api.get({ path: '/v1/onramp/providers/properties', headers: getHeaders(), params: { @@ -305,7 +421,7 @@ export const BlockchainApiController = { }, async getOnRampQuotes(body: BlockchainApiOnRampQuotesRequest, signal?: AbortSignal) { - return await state.stageApi.post({ + return await state.api.post({ path: '/v1/onramp/multi/quotes', headers: getHeaders(), body: { @@ -317,7 +433,7 @@ export const BlockchainApiController = { }, async getOnRampWidget(body: BlockchainApiOnRampWidgetRequest, signal?: AbortSignal) { - return await state.stageApi.post({ + return await state.api.post({ path: '/v1/onramp/widget', headers: getHeaders(), body: { diff --git a/packages/core/src/controllers/ConnectionController.ts b/packages/core/src/controllers/ConnectionController.ts index d36182b7..04b236bb 100644 --- a/packages/core/src/controllers/ConnectionController.ts +++ b/packages/core/src/controllers/ConnectionController.ts @@ -120,6 +120,15 @@ export const ConnectionController = { state.pressedWallet = undefined; }, + setWcPromise(wcPromise: ConnectionControllerState['wcPromise']) { + state.wcPromise = wcPromise; + }, + + setWcUri(wcUri: ConnectionControllerState['wcUri']) { + state.wcUri = wcUri; + state.wcPairingExpiry = CoreHelperUtil.getPairingExpiry(); + }, + setRecentWallets(wallets: ConnectionControllerState['recentWallets']) { state.recentWallets = wallets; }, @@ -192,7 +201,6 @@ export const ConnectionController = { }, async disconnect() { - await this._getClient().disconnect(); this.resetWcConnection(); // remove transactions // RouterController.reset('Connect'); diff --git a/packages/core/src/controllers/ConnectionsController.ts b/packages/core/src/controllers/ConnectionsController.ts new file mode 100644 index 00000000..d93aceb9 --- /dev/null +++ b/packages/core/src/controllers/ConnectionsController.ts @@ -0,0 +1,235 @@ +import { proxy, ref } from 'valtio'; +import { derive } from 'valtio/utils'; +import type { + AppKitNetwork, + BlockchainAdapter, + CaipAddress, + CaipNetworkId, + ChainNamespace, + GetBalanceResponse, + WalletInfo +} from '@reown/appkit-common-react-native'; +import { StorageUtil } from '../utils/StorageUtil'; + +// -- Types --------------------------------------------- // +type Balance = GetBalanceResponse; + +//TODO: balance could be elsewhere +interface Connection { + accounts: CaipAddress[]; + balances: Record; //TODO: make this an array of balances + adapter: BlockchainAdapter; + chains: CaipNetworkId[]; + activeChain: CaipNetworkId; + wallet?: WalletInfo; +} + +export interface ConnectionsControllerState { + activeNamespace?: ChainNamespace; + connections: Record; + networks: AppKitNetwork[]; +} + +// -- State --------------------------------------------- // +const baseState = proxy({ + activeNamespace: undefined, + connections: {}, + networks: [] +}); + +const derivedState = derive( + { + activeAddress: (get): CaipAddress | undefined => { + const snap = get(baseState); + + if (!snap.activeNamespace) return undefined; + + const connection = snap.connections[snap.activeNamespace]; + + if (!connection || !connection.accounts || connection.accounts.length === 0) { + return undefined; + } + + //TODO: what happens if there are several accounts on the same chain? + const activeAccount = connection.accounts.find(account => + account.startsWith(connection.activeChain) + ); + + return activeAccount; + }, + activeBalance: (get): Balance | undefined => { + const snap = get(baseState); + + if (!snap.activeNamespace) return undefined; + const connection = snap.connections[snap.activeNamespace]; + + if (!connection || !connection.accounts || connection.accounts.length === 0) { + return undefined; + } + + const activeAccount = connection.accounts.find(account => + account.startsWith(connection.activeChain) + ); + + if ( + !connection || + !connection.balances || + !activeAccount || + Object.keys(connection.balances).length === 0 + ) { + return undefined; + } + + return connection.balances[activeAccount]; + }, + activeNetwork: (get): AppKitNetwork | undefined => { + const snap = get(baseState); + + if (!snap.activeNamespace) return undefined; + + const connection = snap.connections[snap.activeNamespace]; + + if (!connection) return undefined; + + return snap.networks.find( + network => + (network.chainNamespace ?? 'eip155') === snap.activeNamespace && + network.id?.toString() === connection.activeChain?.split(':')[1] + ); + }, + activeCaipNetworkId: (get): CaipNetworkId | undefined => { + const snap = get(baseState); + + if (!snap.activeNamespace) return undefined; + + const connection = snap.connections[snap.activeNamespace]; + + if (!connection) return undefined; + + return connection.activeChain; + }, + walletInfo: (get): WalletInfo | undefined => { + const snap = get(baseState); + + if (!snap.activeNamespace) return undefined; + + return snap.connections[snap.activeNamespace]?.wallet; + } + }, + { + proxy: baseState // Link derived proxy to the base state proxy + } +); + +// -- Controller ---------------------------------------- // +export const ConnectionsController = { + state: derivedState, + + setActiveNamespace(namespace?: ChainNamespace) { + baseState.activeNamespace = namespace; + StorageUtil.setActiveNamespace(namespace); + }, + + storeConnection({ + namespace, + adapter, + accounts, + chains, + wallet, + activeChain + }: { + namespace: string; + adapter: BlockchainAdapter; + accounts: CaipAddress[]; + chains: CaipNetworkId[]; + wallet?: WalletInfo; + activeChain?: CaipNetworkId; + }) { + baseState.connections[namespace] = { + balances: {}, + activeChain: activeChain ?? chains[0]!, + adapter: ref(adapter), + accounts, + chains, + wallet + }; + }, + + updateAccounts(namespace: string, accounts: CaipAddress[]) { + const connection = baseState.connections[namespace]; + if (!connection) { + return; + } + connection.accounts = accounts; + }, + + updateBalance(namespace: string, address: CaipAddress, balance: Balance) { + const connection = baseState.connections[namespace]; + if (!connection) { + return; + } + connection.balances[address] = balance; + }, + + setActiveChain(namespace: string, chain: CaipNetworkId) { + const connection = baseState.connections[namespace]; + + if (!connection) { + return; + } + + connection.activeChain = chain; + }, + + setNetworks(networks: AppKitNetwork[]) { + baseState.networks = networks; + }, + + getConnectedNetworks() { + return baseState.networks.filter( + network => baseState.connections[network.chainNamespace ?? 'eip155'] + ); + }, + + async disconnect(namespace: string, isInternal = true) { + const connection = baseState.connections[namespace]; + if (!connection) return; + + // Get the current connector from the adapter + const connector = connection.adapter.connector; + if (!connector) return; + + // Find all namespaces that use the same connector + const namespacesUsingConnector = Object.keys(baseState.connections).filter( + ns => baseState.connections[ns]?.adapter.connector === connector + ); + + // Unsubscribe all event listeners from the adapter + namespacesUsingConnector.forEach(ns => { + const _connection = baseState.connections[ns]; + if (_connection?.adapter) { + _connection.adapter.removeAllListeners(); + } + }); + + // Disconnect the adapter + if (isInternal) { + await connection.adapter.disconnect(); + } + + // Remove all namespaces that used this connector + namespacesUsingConnector.forEach(ns => { + delete baseState.connections[ns]; + }); + + // Remove activeNamespace if it is in the list of namespaces using the connector + if ( + baseState.activeNamespace && + (baseState.activeNamespace === namespace || + namespacesUsingConnector.includes(baseState.activeNamespace)) + ) { + baseState.activeNamespace = undefined; + StorageUtil.setActiveNamespace(undefined); + } + } +}; diff --git a/packages/core/src/controllers/ConnectorController.ts b/packages/core/src/controllers/ConnectorController.ts index 7ff77664..64c4b525 100644 --- a/packages/core/src/controllers/ConnectorController.ts +++ b/packages/core/src/controllers/ConnectorController.ts @@ -50,6 +50,7 @@ export const ConnectorController = { if (saveStorage) { if (connectorType) { + //TODO: Check this StorageUtil.setConnectedConnector(connectorType); } else { StorageUtil.removeConnectedConnector(); diff --git a/packages/core/src/controllers/NetworkController.ts b/packages/core/src/controllers/NetworkController.ts index f1023c95..8901de32 100644 --- a/packages/core/src/controllers/NetworkController.ts +++ b/packages/core/src/controllers/NetworkController.ts @@ -1,5 +1,5 @@ import { proxy, ref } from 'valtio'; -import type { CaipNetwork, CaipNetworkId } from '../utils/TypeUtil'; +import type { CaipNetwork, CaipNetworkId } from '@reown/appkit-common-react-native'; import { PublicStateController } from './PublicStateController'; import { NetworkUtil } from '@reown/appkit-common-react-native'; import { ConstantsUtil } from '../utils/ConstantsUtil'; diff --git a/packages/core/src/controllers/OptionsController.ts b/packages/core/src/controllers/OptionsController.ts index 8ecc2e94..753ef500 100644 --- a/packages/core/src/controllers/OptionsController.ts +++ b/packages/core/src/controllers/OptionsController.ts @@ -1,13 +1,14 @@ import { proxy, ref } from 'valtio'; +import type { Tokens } from '@reown/appkit-common-react-native'; import type { CustomWallet, Features, Metadata, ProjectId, SdkType, - SdkVersion, - Tokens + SdkVersion } from '../utils/TypeUtil'; + import { ConstantsUtil } from '../utils/ConstantsUtil'; // -- Types --------------------------------------------- // @@ -17,7 +18,7 @@ export interface ClipboardClient { export interface OptionsControllerState { projectId: ProjectId; - _clipboardClient?: ClipboardClient; + clipboardClient?: ClipboardClient; includeWalletIds?: string[]; excludeWalletIds?: string[]; featuredWalletIds?: string[]; @@ -47,7 +48,7 @@ export const OptionsController = { state, setClipboardClient(client: ClipboardClient) { - state._clipboardClient = ref(client); + state.clipboardClient = ref(client); }, setProjectId(projectId: OptionsControllerState['projectId']) { @@ -103,11 +104,11 @@ export const OptionsController = { }, isClipboardAvailable() { - return !!state._clipboardClient; + return !!state.clipboardClient; }, copyToClipboard(value: string) { - const client = state._clipboardClient; + const client = state.clipboardClient; if (client) { client?.setString(value); } diff --git a/packages/core/src/controllers/PublicStateController.ts b/packages/core/src/controllers/PublicStateController.ts index ca95a55a..fa093aef 100644 --- a/packages/core/src/controllers/PublicStateController.ts +++ b/packages/core/src/controllers/PublicStateController.ts @@ -1,6 +1,6 @@ import { proxy, subscribe as sub } from 'valtio'; import { subscribeKey as subKey } from 'valtio/utils'; -import type { CaipNetworkId } from '../utils/TypeUtil.js'; +import type { CaipNetworkId } from '@reown/appkit-common-react-native'; // -- Types --------------------------------------------- // export interface PublicStateControllerState { diff --git a/packages/core/src/controllers/RouterController.ts b/packages/core/src/controllers/RouterController.ts index 703f369e..f6702908 100644 --- a/packages/core/src/controllers/RouterController.ts +++ b/packages/core/src/controllers/RouterController.ts @@ -1,7 +1,8 @@ import { proxy } from 'valtio'; +import type { CaipNetwork } from '@reown/appkit-common-react-native'; + import type { WcWallet, - CaipNetwork, Connector, SwapInputTarget, OnRampTransactionResult diff --git a/packages/core/src/controllers/SwapController.ts b/packages/core/src/controllers/SwapController.ts index ce2601af..6a4db8c7 100644 --- a/packages/core/src/controllers/SwapController.ts +++ b/packages/core/src/controllers/SwapController.ts @@ -17,6 +17,7 @@ import { CoreHelperUtil } from '../utils/CoreHelperUtil'; import { ConnectionController } from './ConnectionController'; import { TransactionsController } from './TransactionsController'; import { EventsController } from './EventsController'; +import { ConnectionsController } from './ConnectionsController'; // -- Constants ---------------------------------------- // export const INITIAL_GAS_LIMIT = 150000; @@ -158,9 +159,17 @@ export const SwapController = { }, getParams() { - const caipAddress = AccountController.state.caipAddress; - const address = CoreHelperUtil.getPlainAddress(caipAddress); - const networkAddress = NetworkController.getActiveNetworkTokenAddress(); + const { activeAddress, activeNamespace, activeNetwork } = ConnectionsController.state; + const address = CoreHelperUtil.getPlainAddress(activeAddress); + + if (!activeNamespace || !activeNetwork) { + throw new Error('No active namespace or network found to swap the tokens from.'); + } + + const networkAddress = `${activeNetwork.caipNetworkId ?? 'eip155:1'}:${ + ConstantsUtil.NATIVE_TOKEN_ADDRESS[activeNamespace] + }`; + const type = ConnectorController.state.connectedConnector; if (!address) { @@ -178,7 +187,7 @@ export const SwapController = { return { networkAddress, fromAddress: address, - fromCaipAddress: caipAddress, + fromCaipAddress: activeAddress, sourceTokenAddress: state.sourceToken?.address, toTokenAddress: state.toToken?.address, toTokenAmount: state.toTokenAmount, @@ -189,7 +198,7 @@ export const SwapController = { invalidSourceToken, invalidSourceTokenAmount, availableToSwap: - caipAddress && !invalidToToken && !invalidSourceToken && !invalidSourceTokenAmount, + activeAddress && !invalidToToken && !invalidSourceToken && !invalidSourceTokenAmount, isAuthConnector: type === 'AUTH' }; }, diff --git a/packages/core/src/controllers/ThemeController.ts b/packages/core/src/controllers/ThemeController.ts index f3453b00..29514468 100644 --- a/packages/core/src/controllers/ThemeController.ts +++ b/packages/core/src/controllers/ThemeController.ts @@ -1,10 +1,11 @@ +import { Appearance } from 'react-native'; import { proxy, subscribe as sub } from 'valtio'; import type { ThemeMode, ThemeVariables } from '@reown/appkit-common-react-native'; // -- Types --------------------------------------------- // export interface ThemeControllerState { themeMode?: ThemeMode; - themeVariables: ThemeVariables; + themeVariables?: ThemeVariables; } // -- State --------------------------------------------- // @@ -22,10 +23,18 @@ export const ThemeController = { }, setThemeMode(themeMode: ThemeControllerState['themeMode']) { - state.themeMode = themeMode; + if (!themeMode) { + state.themeMode = Appearance.getColorScheme() as ThemeMode; + } else { + state.themeMode = themeMode; + } }, setThemeVariables(themeVariables: ThemeControllerState['themeVariables']) { + if (!themeVariables) { + state.themeVariables = {}; + } + state.themeVariables = { ...state.themeVariables, ...themeVariables }; } }; diff --git a/packages/core/src/controllers/TransactionsController.ts b/packages/core/src/controllers/TransactionsController.ts index 333f1d5c..23679623 100644 --- a/packages/core/src/controllers/TransactionsController.ts +++ b/packages/core/src/controllers/TransactionsController.ts @@ -3,9 +3,9 @@ import { proxy, subscribe as sub } from 'valtio/vanilla'; import { OptionsController } from './OptionsController'; import { EventsController } from './EventsController'; import { SnackController } from './SnackController'; -import { NetworkController } from './NetworkController'; import { BlockchainApiController } from './BlockchainApiController'; import { AccountController } from './AccountController'; +import { ConnectionsController } from './ConnectionsController'; // -- Types --------------------------------------------- // type TransactionByMonthMap = Record; @@ -121,7 +121,7 @@ export const TransactionsController = { }, filterByConnectedChain(transactions: Transaction[]) { - const chainId = NetworkController.state.caipNetwork?.id; + const chainId = ConnectionsController.state.activeCaipNetworkId; const filteredTransactions = transactions.filter( transaction => transaction.metadata.chain === chainId ); diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 8c196a7a..63e8b133 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -21,6 +21,11 @@ export { type ConnectionControllerState } from './controllers/ConnectionController'; +export { + ConnectionsController, + type ConnectionsControllerState +} from './controllers/ConnectionsController'; + export { ConnectorController, type ConnectorControllerState @@ -62,7 +67,6 @@ export { WebviewController, type WebviewControllerState } from './controllers/We // -- Utils ------------------------------------------------------------------- export { ApiUtil } from './utils/ApiUtil'; export { AssetUtil } from './utils/AssetUtil'; -export { ConnectionUtil } from './utils/ConnectionUtil'; export { ConstantsUtil } from './utils/ConstantsUtil'; export { CoreHelperUtil } from './utils/CoreHelperUtil'; export { StorageUtil } from './utils/StorageUtil'; diff --git a/packages/core/src/utils/AssetUtil.ts b/packages/core/src/utils/AssetUtil.ts index 30efc3eb..89f783e3 100644 --- a/packages/core/src/utils/AssetUtil.ts +++ b/packages/core/src/utils/AssetUtil.ts @@ -1,5 +1,5 @@ import { AssetController } from '../controllers/AssetController'; -import type { CaipNetwork, Connector, WcWallet } from './TypeUtil'; +import type { Connector, WcWallet } from './TypeUtil'; export const AssetUtil = { getWalletImage(wallet?: WcWallet) { @@ -14,13 +14,11 @@ export const AssetUtil = { return undefined; }, - getNetworkImage(network?: CaipNetwork) { - if (network?.imageUrl) { - return network?.imageUrl; - } + getNetworkImage(networkId?: string | number) { + //TODO: check if imageUrl case is needed - if (network?.imageId) { - return AssetController.state.networkImages[network.imageId]; + if (networkId) { + return AssetController.state.networkImages[networkId]; } return undefined; diff --git a/packages/core/src/utils/ConnectionUtil.ts b/packages/core/src/utils/ConnectionUtil.ts deleted file mode 100644 index 0803b699..00000000 --- a/packages/core/src/utils/ConnectionUtil.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { AccountController } from '../controllers/AccountController'; -import { ConnectionController } from '../controllers/ConnectionController'; -import { EventsController } from '../controllers/EventsController'; -import { ModalController } from '../controllers/ModalController'; -import { RouterController } from '../controllers/RouterController'; -import { TransactionsController } from '../controllers/TransactionsController'; - -export const ConnectionUtil = { - async disconnect() { - try { - await ConnectionController.disconnect(); - ModalController.close(); - AccountController.setIsConnected(false); - RouterController.reset('Connect'); - TransactionsController.resetTransactions(); - EventsController.sendEvent({ - type: 'track', - event: 'DISCONNECT_SUCCESS' - }); - } catch (error) { - EventsController.sendEvent({ - type: 'track', - event: 'DISCONNECT_ERROR' - }); - } - } -}; diff --git a/packages/core/src/utils/ConstantsUtil.ts b/packages/core/src/utils/ConstantsUtil.ts index 9e10b999..840e195d 100644 --- a/packages/core/src/utils/ConstantsUtil.ts +++ b/packages/core/src/utils/ConstantsUtil.ts @@ -1,10 +1,12 @@ +import type { ChainNamespace } from '@reown/appkit-common-react-native'; import type { Features } from './TypeUtil'; +//TODO: enable this again after implemented const defaultFeatures: Features = { - swaps: true, - onramp: true, - email: true, - emailShowWallets: true, + swaps: false, + onramp: false, + email: false, + emailShowWallets: false, socials: ['x', 'discord', 'apple'] }; @@ -34,7 +36,12 @@ export const ConstantsUtil = { LINKING_ERROR: 'LINKING_ERROR', - NATIVE_TOKEN_ADDRESS: '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee', + NATIVE_TOKEN_ADDRESS: { + eip155: '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee', + solana: 'So11111111111111111111111111111111111111111', + polkadot: '0x', + bip122: '0x' + } as const satisfies Record, ONRAMP_ERROR_TYPES: OnRampErrorType, diff --git a/packages/core/src/utils/CoreHelperUtil.ts b/packages/core/src/utils/CoreHelperUtil.ts index 07914a69..1df99944 100644 --- a/packages/core/src/utils/CoreHelperUtil.ts +++ b/packages/core/src/utils/CoreHelperUtil.ts @@ -1,12 +1,17 @@ /* eslint-disable no-bitwise */ import { Linking, Platform } from 'react-native'; -import { ConstantsUtil as CommonConstants, type Balance } from '@reown/appkit-common-react-native'; +import { + ConstantsUtil as CommonConstants, + type Balance, + type CaipAddress, + type CaipNetwork +} from '@reown/appkit-common-react-native'; + import * as ct from 'countries-and-timezones'; import { ConstantsUtil } from './ConstantsUtil'; -import type { CaipAddress, CaipNetwork, DataWallet, LinkingRecord } from './TypeUtil'; - +import type { DataWallet, LinkingRecord } from './TypeUtil'; // -- Helpers ----------------------------------------------------------------- async function isAppInstalledIos(deepLink?: string): Promise { try { diff --git a/packages/core/src/utils/FetchUtil.ts b/packages/core/src/utils/FetchUtil.ts index 7f0ee6dd..db9aed88 100644 --- a/packages/core/src/utils/FetchUtil.ts +++ b/packages/core/src/utils/FetchUtil.ts @@ -8,7 +8,7 @@ interface Options { interface RequestArguments { path: string; - headers?: HeadersInit_; + headers?: HeadersInit; params?: Record; cache?: RequestCache; signal?: AbortSignal; diff --git a/packages/core/src/utils/NetworkUtil.ts b/packages/core/src/utils/NetworkUtil.ts index 4ab2b5b4..fcdc446e 100644 --- a/packages/core/src/utils/NetworkUtil.ts +++ b/packages/core/src/utils/NetworkUtil.ts @@ -4,7 +4,7 @@ import { NetworkController } from '../controllers/NetworkController'; import { AccountController } from '../controllers/AccountController'; import { ConnectorController } from '../controllers/ConnectorController'; import { SwapController } from '../controllers/SwapController'; -import type { CaipNetwork } from '../utils/TypeUtil'; +import { type CaipNetwork } from '@reown/appkit-common-react-native'; export const NetworkUtil = { async handleNetworkSwitch(network: CaipNetwork) { diff --git a/packages/core/src/utils/StorageUtil.ts b/packages/core/src/utils/StorageUtil.ts index acddf5c0..294fe43f 100644 --- a/packages/core/src/utils/StorageUtil.ts +++ b/packages/core/src/utils/StorageUtil.ts @@ -10,7 +10,9 @@ import type { import { DateUtil, type SocialProvider, - type ConnectorType + type New_ConnectorType, + type ConnectorType, + type ChainNamespace } from '@reown/appkit-common-react-native'; // -- Helpers ----------------------------------------------------------------- @@ -18,6 +20,7 @@ const WC_DEEPLINK = 'WALLETCONNECT_DEEPLINK_CHOICE'; const RECENT_WALLET = '@w3m/recent'; const CONNECTED_WALLET_IMAGE_URL = '@w3m/connected_wallet_image_url'; const CONNECTED_CONNECTOR = '@w3m/connected_connector'; +const CONNECTED_CONNECTORS = '@appkit/connected_connectors'; const CONNECTED_SOCIAL = '@appkit/connected_social'; const ONRAMP_PREFERRED_COUNTRY = '@appkit/onramp_preferred_country'; const ONRAMP_COUNTRIES = '@appkit/onramp_countries'; @@ -25,6 +28,8 @@ const ONRAMP_SERVICE_PROVIDERS = '@appkit/onramp_service_providers'; const ONRAMP_FIAT_LIMITS = '@appkit/onramp_fiat_limits'; const ONRAMP_FIAT_CURRENCIES = '@appkit/onramp_fiat_currencies'; const ONRAMP_PREFERRED_FIAT_CURRENCY = '@appkit/onramp_preferred_fiat_currency'; +const ACTIVE_NAMESPACE = '@appkit/active_namespace'; + // -- Utility ----------------------------------------------------------------- export const StorageUtil = { setWalletConnectDeepLink({ href, name }: { href: string; name: string }) { @@ -99,6 +104,7 @@ export const StorageUtil = { return []; }, + //TODO: remove this async setConnectedConnector(connectorType: ConnectorType) { try { await AsyncStorage.setItem(CONNECTED_CONNECTOR, JSON.stringify(connectorType)); @@ -127,6 +133,47 @@ export const StorageUtil = { } }, + async setConnectedConnectors({ + type, + namespaces + }: { + type: New_ConnectorType; + namespaces: string[]; + }) { + try { + const currentConnectors = (await StorageUtil.getConnectedConnectors()) || []; + // Only add if it doesn't exist already + if (!currentConnectors.some(c => c.type === type)) { + const updatedConnectors = [...currentConnectors, { type, namespaces }]; + await AsyncStorage.setItem(CONNECTED_CONNECTORS, JSON.stringify(updatedConnectors)); + } + } catch { + console.info('Unable to set Connected Connector'); + } + }, + + async getConnectedConnectors(): Promise<{ type: New_ConnectorType; namespaces: string[] }[]> { + try { + const connectors = await AsyncStorage.getItem(CONNECTED_CONNECTORS); + + return connectors ? JSON.parse(connectors) : []; + } catch { + console.info('Unable to get Connected Connector'); + } + + return []; + }, + + async removeConnectedConnectors(type: New_ConnectorType) { + try { + const currentConnectors = await StorageUtil.getConnectedConnectors(); + const updatedConnectors = currentConnectors.filter(c => c.type !== type); + await AsyncStorage.setItem(CONNECTED_CONNECTORS, JSON.stringify(updatedConnectors)); + } catch { + console.info('Unable to remove Connected Connector'); + } + }, + async setConnectedWalletImageUrl(url: string) { try { await AsyncStorage.setItem(CONNECTED_WALLET_IMAGE_URL, url); @@ -348,5 +395,39 @@ export const StorageUtil = { } return []; + }, + + async setActiveNamespace(namespace?: ChainNamespace) { + try { + if (!namespace) { + await AsyncStorage.removeItem(ACTIVE_NAMESPACE); + + return; + } + + await AsyncStorage.setItem(ACTIVE_NAMESPACE, namespace); + } catch { + console.info('Unable to set Active Namespace'); + } + }, + + async getActiveNamespace() { + try { + const namespace = (await AsyncStorage.getItem(ACTIVE_NAMESPACE)) as ChainNamespace; + + return namespace ?? undefined; + } catch (err) { + console.info('Unable to get Active Namespace'); + } + + return undefined; + }, + + async removeActiveNamespace() { + try { + await AsyncStorage.removeItem(ACTIVE_NAMESPACE); + } catch { + console.info('Unable to remove Active Namespace'); + } } }; diff --git a/packages/core/src/utils/SwapApiUtil.ts b/packages/core/src/utils/SwapApiUtil.ts index be33994b..9156e14c 100644 --- a/packages/core/src/utils/SwapApiUtil.ts +++ b/packages/core/src/utils/SwapApiUtil.ts @@ -8,12 +8,14 @@ import type { } from './TypeUtil'; import { AccountController } from '../controllers/AccountController'; import { ConnectionController } from '../controllers/ConnectionController'; +import { ConnectionsController } from '../controllers/ConnectionsController'; export const SwapApiUtil = { async getTokenList() { + const chainId = ConnectionsController.state.activeNetwork?.caipNetworkId ?? 'eip155:1'; const response = await BlockchainApiController.fetchSwapTokens({ projectId: OptionsController.state.projectId, - chainId: NetworkController.state.caipNetwork?.id + chainId }); const tokens = response?.tokens?.map( @@ -62,14 +64,18 @@ export const SwapApiUtil = { }, async getMyTokensWithBalance(forceUpdate?: string) { - const address = AccountController.state.address; - const chainId = NetworkController.state.caipNetwork?.id; + const { activeAddress, activeNetwork: network } = ConnectionsController.state; + const address = activeAddress?.split(':')[2]; if (!address) { return []; } - const response = await BlockchainApiController.getBalance(address, chainId, forceUpdate); + const response = await BlockchainApiController.getBalance( + address, + network?.caipNetworkId, + forceUpdate + ); const balances = response?.balances.filter(balance => balance.quantity.decimals !== '0'); AccountController.setTokenBalance(balances); diff --git a/packages/core/src/utils/TypeUtil.ts b/packages/core/src/utils/TypeUtil.ts index 841c797b..fd251133 100644 --- a/packages/core/src/utils/TypeUtil.ts +++ b/packages/core/src/utils/TypeUtil.ts @@ -1,4 +1,6 @@ import { type EventEmitter } from 'events'; +import type { CaipAddress, CaipNetworkId } from '@reown/appkit-common-react-native'; + import type { Balance, SocialProvider, @@ -6,23 +8,13 @@ import type { Transaction, ConnectorType } from '@reown/appkit-common-react-native'; + import { OnRampErrorType } from './ConstantsUtil'; export interface BaseError { message?: string; } -export type CaipAddress = `${string}:${string}:${string}`; - -export type CaipNetworkId = `${string}:${string}`; - -export interface CaipNetwork { - id: CaipNetworkId; - name?: string; - imageId?: string; - imageUrl?: string; -} - export type ConnectedWalletInfo = | { name?: string; @@ -179,6 +171,7 @@ export interface BlockchainApiTransactionsRequest { onramp?: 'coinbase'; signal?: AbortSignal; cache?: RequestCache; + chainId?: CaipNetworkId; } export interface BlockchainApiTransactionsResponse { @@ -346,13 +339,6 @@ export type BlockchainApiOnRampWidgetResponse = { }; // -- OptionsController Types --------------------------------------------------- -export interface Token { - address: string; - image?: string; -} - -export type Tokens = Record; - export type Metadata = { name: string; description: string; @@ -461,7 +447,7 @@ export type Event = type: 'track'; event: 'SWITCH_NETWORK'; properties: { - network: string; + network: number | string; }; } | { diff --git a/packages/ethers/package.json b/packages/ethers/package.json index 9618b79d..98b610f9 100644 --- a/packages/ethers/package.json +++ b/packages/ethers/package.json @@ -39,22 +39,18 @@ }, "dependencies": { "@reown/appkit-common-react-native": "1.2.3", - "@reown/appkit-scaffold-react-native": "1.2.3", + "@reown/appkit-react-native": "1.2.3", "@reown/appkit-scaffold-utils-react-native": "1.2.3", "@reown/appkit-siwe-react-native": "1.2.3", - "@walletconnect/ethereum-provider": "2.17.3" + "@walletconnect/ethereum-provider": "2.20.2" }, "peerDependencies": { "@react-native-async-storage/async-storage": ">=1.17.0", "@react-native-community/netinfo": "*", "@walletconnect/react-native-compat": ">=2.13.1", - "ethers": ">=6.0.0", "react": ">=17", "react-native": ">=0.68.5", "react-native-get-random-values": "*" }, - "devDependencies": { - "ethers": "6.10.0" - }, "react-native": "src/index.tsx" } diff --git a/packages/ethers/src/adapter.ts b/packages/ethers/src/adapter.ts new file mode 100644 index 00000000..84ce97bc --- /dev/null +++ b/packages/ethers/src/adapter.ts @@ -0,0 +1,98 @@ +import { + EVMAdapter, + type AppKitNetwork, + type CaipAddress, + type ChainNamespace, + type GetBalanceParams, + type GetBalanceResponse +} from '@reown/appkit-common-react-native'; +import { EthersHelpersUtil } from '@reown/appkit-scaffold-utils-react-native'; +import { formatEther, getEthBalance } from './helpers'; + +export class EthersAdapter extends EVMAdapter { + private static supportedNamespace: ChainNamespace = 'eip155'; + + constructor(configParams: { projectId: string }) { + super({ + projectId: configParams.projectId, + supportedNamespace: EthersAdapter.supportedNamespace + }); + } + + async getBalance(params: GetBalanceParams): Promise { + const { network, address } = params; + + if (!this.connector) throw new Error('No active connector'); + if (!network) throw new Error('No network provided'); + + const balanceAddress = + address || this.getAccounts()?.find(account => account.includes(network.id.toString())); + + const balance: GetBalanceResponse = { + amount: '0.00', + symbol: network.nativeCurrency.symbol || 'ETH' + }; + + if (!balanceAddress) return balance; + + const account = balanceAddress.split(':')[2]; + const rpcUrl = network.rpcUrls.default.http?.[0]; + if (!rpcUrl || !account) return balance; + + try { + const wei = await getEthBalance(rpcUrl, account); + balance.amount = formatEther(wei); + + this.emit('balanceChanged', { + namespace: this.getSupportedNamespace(), + address: balanceAddress, + balance + }); + + return balance; + } catch { + return balance; + } + } + + async switchNetwork(network: AppKitNetwork): Promise { + if (!this.connector) throw new Error('No active connector'); + + const provider = this.connector.getProvider(); + if (!provider) throw new Error('No active provider'); + + try { + await provider.request( + { + method: 'wallet_switchEthereumChain', + params: [{ chainId: EthersHelpersUtil.numberToHexString(Number(network.id)) }] //TODO: check util + }, + `${EthersAdapter.supportedNamespace}:${network.id}` + ); + } catch (switchError: any) { + const message = switchError?.message as string; + if (/(?user rejected)/u.test(message?.toLowerCase())) { + throw new Error('Chain is not supported'); + } + + throw switchError; + } + } + + getAccounts(): CaipAddress[] | undefined { + if (!this.connector) throw new Error('No active connector'); + const namespaces = this.connector.getNamespaces(); + + return namespaces[this.getSupportedNamespace()]?.accounts; + } + + disconnect(): Promise { + if (!this.connector) throw new Error('EthersAdapter:disconnect - No active connector'); + + return this.connector.disconnect(); + } + + getSupportedNamespace(): ChainNamespace { + return EthersAdapter.supportedNamespace; + } +} diff --git a/packages/ethers/src/client.ts b/packages/ethers/src/client.ts deleted file mode 100644 index c6204de2..00000000 --- a/packages/ethers/src/client.ts +++ /dev/null @@ -1,1071 +0,0 @@ -import { - BrowserProvider, - Contract, - InfuraProvider, - JsonRpcProvider, - JsonRpcSigner, - formatEther, - formatUnits, - getAddress, - hexlify, - isHexString, - parseUnits, - toUtf8Bytes -} from 'ethers'; -import { - type CaipAddress, - type CaipNetwork, - type CaipNetworkId, - type ConnectionControllerClient, - type Connector, - type LibraryOptions, - type NetworkControllerClient, - type PublicStateControllerState, - type SendTransactionArgs, - type Token, - AppKitScaffold, - type WriteContractArgs, - type AppKitFrameAccountType, - type EstimateGasTransactionArgs -} from '@reown/appkit-scaffold-react-native'; -import { - erc20ABI, - ErrorUtil, - NamesUtil, - NetworkUtil, - PresetsUtil, - ConstantsUtil -} from '@reown/appkit-common-react-native'; -import { - HelpersUtil, - StorageUtil, - EthersConstantsUtil, - EthersHelpersUtil, - EthersStoreUtil, - type Address, - type Metadata, - type ProviderType, - type Chain, - type Provider, - type EthersStoreUtilState, - type CombinedProviderType, - type AppKitFrameProvider -} from '@reown/appkit-scaffold-utils-react-native'; -import { - type AppKitSIWEClient, - SIWEController, - getDidChainId, - getDidAddress -} from '@reown/appkit-siwe-react-native'; -import EthereumProvider, { OPTIONAL_METHODS } from '@walletconnect/ethereum-provider'; -import type { EthereumProviderOptions } from '@walletconnect/ethereum-provider'; -import { type JsonRpcError } from '@walletconnect/jsonrpc-types'; - -import { getAuthCaipNetworks, getWalletConnectCaipNetworks } from './utils/helpers'; - -// -- Types --------------------------------------------------------------------- -export interface AppKitClientOptions extends Omit { - config: ProviderType; - siweConfig?: AppKitSIWEClient; - chains: Chain[]; - defaultChain?: Chain; - chainImages?: Record; - connectorImages?: Record; - tokens?: Record; -} - -export type AppKitOptions = Omit; - -// @ts-expect-error: Overriden state type is correct -interface AppKitState extends PublicStateControllerState { - selectedNetworkId: number | undefined; -} - -interface ExternalProvider extends EthereumProvider { - address?: string; -} - -// -- Client -------------------------------------------------------------------- -export class AppKit extends AppKitScaffold { - private hasSyncedConnectedAccount = false; - - private walletConnectProvider?: EthereumProvider; - - private walletConnectProviderInitPromise?: Promise; - - private projectId: string; - - private chains: Chain[]; - - private metadata: Metadata; - - private options: AppKitClientOptions | undefined = undefined; - - private authProvider?: AppKitFrameProvider; - - public constructor(options: AppKitClientOptions) { - const { - config, - siweConfig, - chains, - defaultChain, - tokens, - chainImages, - _sdkVersion, - ...appKitOptions - } = options; - - if (!config) { - throw new Error('appkit:constructor - config is undefined'); - } - - if (!appKitOptions.projectId) { - throw new Error(ErrorUtil.ALERT_ERRORS.PROJECT_ID_NOT_CONFIGURED.shortMessage); - } - - const networkControllerClient: NetworkControllerClient = { - switchCaipNetwork: async caipNetwork => { - const chainId = NetworkUtil.caipNetworkIdToNumber(caipNetwork?.id); - if (chainId) { - try { - await this.switchNetwork(chainId); - } catch (error) { - EthersStoreUtil.setError(error); - } - } - }, - - getApprovedCaipNetworksData: async () => - new Promise(async resolve => { - const walletChoice = await StorageUtil.getConnectedConnector(); - const walletConnectType = - PresetsUtil.ConnectorTypesMap[ConstantsUtil.WALLET_CONNECT_CONNECTOR_ID]!; - - const authType = PresetsUtil.ConnectorTypesMap[ConstantsUtil.AUTH_CONNECTOR_ID]!; - if (walletChoice?.includes(walletConnectType)) { - const provider = await this.getWalletConnectProvider(); - const result = getWalletConnectCaipNetworks(provider); - - resolve(result); - } else if (walletChoice?.includes(authType)) { - const result = getAuthCaipNetworks(); - resolve(result); - } else { - const result = { - approvedCaipNetworkIds: undefined, - supportsAllNetworks: true - }; - - resolve(result); - } - }) - }; - - const connectionControllerClient: ConnectionControllerClient = { - connectWalletConnect: async onUri => { - const WalletConnectProvider = await this.getWalletConnectProvider(); - if (!WalletConnectProvider) { - throw new Error('connectionControllerClient:getWalletConnectUri - provider is undefined'); - } - - WalletConnectProvider.on('display_uri', (uri: string) => { - onUri(uri); - }); - - // When connecting through walletconnect, we need to set the clientId in the store - const clientId = await WalletConnectProvider.signer?.client?.core?.crypto?.getClientId(); - if (clientId) { - this.setClientId(clientId); - } - - // SIWE - const params = await siweConfig?.getMessageParams?.(); - if (siweConfig?.options?.enabled && params && Object.keys(params).length > 0) { - const result = await WalletConnectProvider.authenticate({ - nonce: await siweConfig.getNonce(), - methods: OPTIONAL_METHODS, - ...params - }); - // Auths is an array of signed CACAO objects https://github.com/ChainAgnostic/CAIPs/blob/main/CAIPs/caip-74.md - const signedCacao = result?.auths?.[0]; - if (signedCacao) { - const { p, s } = signedCacao; - const chainId = getDidChainId(p.iss); - const address = getDidAddress(p.iss); - - try { - // Kicks off verifyMessage and populates external states - const message = WalletConnectProvider.signer.client.formatAuthMessage({ - request: p, - iss: p.iss - }); - - await SIWEController.verifyMessage({ - message, - signature: s.s, - cacao: signedCacao - }); - - if (address && chainId) { - const session = { - address, - chainId: parseInt(chainId, 10) - }; - - SIWEController.setSession(session); - SIWEController.onSignIn?.(session); - } - } catch (error) { - // eslint-disable-next-line no-console - console.error('Error verifying message', error); - // eslint-disable-next-line no-console - await WalletConnectProvider.disconnect().catch(console.error); - // eslint-disable-next-line no-console - await SIWEController.signOut().catch(console.error); - throw error; - } - } - } else { - await WalletConnectProvider.connect(); - } - - await this.setWalletConnectProvider(); - }, - - // @ts-expect-error TODO expected types in arguments are incomplete - connectExternal: async ({ id }: { id: string; provider: Provider }) => { - // If connecting with something else than walletconnect, we need to clear the clientId in the store - this.setClientId(null); - - if (id === ConstantsUtil.COINBASE_CONNECTOR_ID) { - const coinbaseProvider = config.extraConnectors?.find(connector => connector.id === id); - if (!coinbaseProvider) { - throw new Error('connectionControllerClient:connectCoinbase - connector is undefined'); - } - - try { - await coinbaseProvider.request({ method: 'eth_requestAccounts' }); - await this.setCoinbaseProvider(coinbaseProvider as Provider); - } catch (error) { - EthersStoreUtil.setError(error); - } - } else if (id === ConstantsUtil.AUTH_CONNECTOR_ID) { - await this.setAuthProvider(); - } - }, - - disconnect: async () => { - const provider = EthersStoreUtil.state.provider; - const providerType = EthersStoreUtil.state.providerType; - const walletConnectType = - PresetsUtil.ConnectorTypesMap[ConstantsUtil.WALLET_CONNECT_CONNECTOR_ID]; - - const authType = PresetsUtil.ConnectorTypesMap[ConstantsUtil.AUTH_CONNECTOR_ID]; - - if (siweConfig?.options?.signOutOnDisconnect) { - await SIWEController.signOut(); - } - - if (providerType === walletConnectType) { - const WalletConnectProvider = provider; - await (WalletConnectProvider as unknown as EthereumProvider).disconnect(); - } else if (providerType === authType) { - await this.authProvider?.disconnect(); - } else if (provider) { - provider.emit('disconnect'); - } - StorageUtil.removeItem(EthersConstantsUtil.WALLET_ID); - EthersStoreUtil.reset(); - this.setClientId(null); - }, - - signMessage: async (message: string) => { - const provider = EthersStoreUtil.state.provider; - if (!provider) { - throw new Error('connectionControllerClient:signMessage - provider is undefined'); - } - const hexMessage = isHexString(message) ? message : hexlify(toUtf8Bytes(message)); - const signature = await provider.request({ - method: 'personal_sign', - params: [hexMessage, this.getAddress()] - }); - - return signature as `0x${string}`; - }, - - estimateGas: async ({ - address, - to, - data, - chainNamespace - }: EstimateGasTransactionArgs): Promise => { - const caipNetwork = this.getCaipNetwork(); - const provider = EthersStoreUtil.state.provider; - - if (!provider) { - throw new Error('Provider is undefined'); - } - - try { - if (!provider) { - throw new Error('estimateGas - provider is undefined'); - } - if (!address) { - throw new Error('estimateGas - address is undefined'); - } - if (chainNamespace && chainNamespace !== 'eip155') { - throw new Error('estimateGas - chainNamespace is not eip155'); - } - - const txParams = { - from: address, - to, - data, - type: 0 - }; - const browserProvider = new BrowserProvider(provider, Number(caipNetwork?.id)); - const signer = new JsonRpcSigner(browserProvider, address); - - return await signer.estimateGas(txParams); - } catch (error) { - throw new Error('Ethers: estimateGas - Estimate gas failed'); - } - }, - - parseUnits: (value: string, decimals: number) => parseUnits(value, decimals), - - formatUnits: (value: bigint, decimals: number) => formatUnits(value, decimals), - - sendTransaction: async (data: SendTransactionArgs) => { - const { chainId, provider, address } = EthersStoreUtil.state; - - if (!provider) { - throw new Error('ethersClient:sendTransaction - provider is undefined'); - } - - if (!address) { - throw new Error('ethersClient:sendTransaction - address is undefined'); - } - - const txParams = { - to: data.to, - value: data.value, - gasLimit: data.gas, - gasPrice: data.gasPrice, - data: data.data, - type: 0 - }; - - const browserProvider = new BrowserProvider(provider, chainId); - const signer = new JsonRpcSigner(browserProvider, address); - const txResponse = await signer.sendTransaction(txParams); - const txReceipt = await txResponse.wait(); - - return (txReceipt?.hash as `0x${string}`) || null; - }, - - writeContract: async (data: WriteContractArgs) => { - const { chainId, provider, address } = EthersStoreUtil.state; - - if (!provider) { - throw new Error('ethersClient:writeContract - provider is undefined'); - } - - if (!address) { - throw new Error('ethersClient:writeContract - address is undefined'); - } - - const browserProvider = new BrowserProvider(provider, chainId); - const signer = new JsonRpcSigner(browserProvider, address); - const contract = new Contract(data.tokenAddress, data.abi, signer); - - if (!contract || !data.method) { - throw new Error('Contract method is undefined'); - } - - const method = contract[data.method]; - if (method) { - const tx = await method(data.receiverAddress, data.tokenAmount); - - return tx; - } - - throw new Error('Contract method is undefined'); - }, - - getEnsAddress: async (value: string) => { - try { - const chainId = Number(this.getCaipNetwork()?.id); - let ensName: string | null = null; - let wcName: boolean | string = false; - - if (NamesUtil.isReownName(value)) { - wcName = (await this?.resolveReownName(value)) || false; - } - - // If on mainnet, fetch from ENS - if (chainId === 1) { - const ensProvider = new InfuraProvider('mainnet'); - ensName = await ensProvider.resolveName(value); - } - - return ensName || wcName || false; - } catch { - return false; - } - }, - - getEnsAvatar: async (value: string) => { - const chainId = Number(NetworkUtil.caipNetworkIdToNumber(this.getCaipNetwork()?.id)); - if (chainId === 1) { - const ensProvider = new InfuraProvider('mainnet'); - const avatar = await ensProvider.getAvatar(value); - - return avatar || false; - } - - return false; - } - }; - - super({ - networkControllerClient, - connectionControllerClient, - siweControllerClient: siweConfig, - defaultChain: EthersHelpersUtil.getCaipDefaultChain(defaultChain), - tokens: HelpersUtil.getCaipTokens(tokens), - _sdkVersion: _sdkVersion ?? `react-native-ethers-${ConstantsUtil.VERSION}`, - ...appKitOptions - }); - - this.options = options; - - this.metadata = config.metadata; - - this.projectId = appKitOptions.projectId; - this.chains = chains; - - this.createProvider(); - - EthersStoreUtil.subscribeKey('address', address => { - this.syncAccount({ address }); - }); - - EthersStoreUtil.subscribeKey('chainId', () => { - this.syncNetwork(chainImages); - }); - - EthersStoreUtil.subscribeKey('provider', provider => { - this.syncConnectedWalletInfo(provider); - }); - - this.syncRequestedNetworks(chains, chainImages); - this.syncConnectors(config); - this.syncAuthConnector(config); - } - - // -- Public ------------------------------------------------------------------ - - // @ts-expect-error: Overriden state type is correct - public override getState() { - const state = super.getState(); - - return { - ...state, - selectedNetworkId: NetworkUtil.caipNetworkIdToNumber(state.selectedNetworkId) - }; - } - - // @ts-expect-error: Overriden state type is correct - public override subscribeState(callback: (state: AppKitState) => void) { - return super.subscribeState(state => - callback({ - ...state, - selectedNetworkId: NetworkUtil.caipNetworkIdToNumber(state.selectedNetworkId) - }) - ); - } - - public setAddress(address?: string) { - const originalAddress = address ? (getAddress(address) as Address) : undefined; - EthersStoreUtil.setAddress(originalAddress); - } - - public getAddress() { - const { address } = EthersStoreUtil.state; - - return address ? getAddress(address) : address; - } - - public getError() { - return EthersStoreUtil.state.error; - } - - public getChainId() { - return EthersStoreUtil.state.chainId; - } - - public getIsConnected() { - return EthersStoreUtil.state.isConnected; - } - - public getWalletProvider() { - return EthersStoreUtil.state.provider; - } - - public getWalletProviderType() { - return EthersStoreUtil.state.providerType; - } - - public subscribeProvider(callback: (newState: EthersStoreUtilState) => void) { - return EthersStoreUtil.subscribe(callback); - } - - public async disconnect() { - const { provider } = EthersStoreUtil.state; - StorageUtil.removeItem(EthersConstantsUtil.WALLET_ID); - EthersStoreUtil.reset(); - this.setClientId(null); - - await (provider as unknown as EthereumProvider).disconnect(); - } - - // -- Private ----------------------------------------------------------------- - private createProvider() { - if (!this.walletConnectProviderInitPromise) { - this.walletConnectProviderInitPromise = this.initWalletConnectProvider(); - } - - return this.walletConnectProviderInitPromise; - } - - private async initWalletConnectProvider() { - const rpcMap = this.chains - ? this.chains.reduce>((map, chain) => { - map[chain.chainId] = chain.rpcUrl; - - return map; - }, {}) - : ({} as Record); - - const walletConnectProviderOptions: EthereumProviderOptions = { - projectId: this.projectId, - showQrModal: false, - rpcMap, - optionalChains: [...this.chains.map(chain => chain.chainId)] as [number], - metadata: this.metadata - }; - - this.walletConnectProvider = await EthereumProvider.init(walletConnectProviderOptions); - this.addWalletConnectListeners(this.walletConnectProvider); - - await this.checkActiveWalletConnectProvider(); - } - - private async getWalletConnectProvider() { - if (!this.walletConnectProvider) { - try { - await this.createProvider(); - } catch (error) { - EthersStoreUtil.setError(error); - } - } - - return this.walletConnectProvider; - } - - private syncRequestedNetworks( - chains: AppKitClientOptions['chains'], - chainImages?: AppKitClientOptions['chainImages'] - ) { - const requestedCaipNetworks = chains?.map( - chain => - ({ - id: `${ConstantsUtil.EIP155}:${chain.chainId}`, - name: chain.name, - imageId: PresetsUtil.EIP155NetworkImageIds[chain.chainId], - imageUrl: chainImages?.[chain.chainId] - }) as CaipNetwork - ); - this.setRequestedCaipNetworks(requestedCaipNetworks ?? []); - } - - private async checkActiveWalletConnectProvider() { - const WalletConnectProvider = await this.getWalletConnectProvider(); - const walletId = await StorageUtil.getItem(EthersConstantsUtil.WALLET_ID); - - if (WalletConnectProvider) { - if (walletId === ConstantsUtil.WALLET_CONNECT_CONNECTOR_ID) { - await this.setWalletConnectProvider(); - } - } - } - - private async checkActiveCoinbaseProvider(provider: Provider) { - const CoinbaseProvider = provider as unknown as ExternalProvider; - const walletId = await StorageUtil.getItem(EthersConstantsUtil.WALLET_ID); - - if (CoinbaseProvider) { - if (walletId === ConstantsUtil.COINBASE_CONNECTOR_ID) { - if (CoinbaseProvider.address) { - await this.setCoinbaseProvider(provider); - await this.watchCoinbase(provider); - } else { - await StorageUtil.removeItem(EthersConstantsUtil.WALLET_ID); - EthersStoreUtil.reset(); - } - } - } - } - - private async setWalletConnectProvider() { - StorageUtil.setItem(EthersConstantsUtil.WALLET_ID, ConstantsUtil.WALLET_CONNECT_CONNECTOR_ID); - const WalletConnectProvider = await this.getWalletConnectProvider(); - if (WalletConnectProvider) { - const providerType = PresetsUtil.ConnectorTypesMap[ConstantsUtil.WALLET_CONNECT_CONNECTOR_ID]; - EthersStoreUtil.setChainId(WalletConnectProvider.chainId); - EthersStoreUtil.setProviderType(providerType); - EthersStoreUtil.setProvider(WalletConnectProvider as unknown as Provider); - EthersStoreUtil.setIsConnected(true); - this.setAddress(WalletConnectProvider.accounts?.[0]); - await this.watchWalletConnect(); - } - } - - private async setCoinbaseProvider(provider: Provider) { - await StorageUtil.setItem(EthersConstantsUtil.WALLET_ID, ConstantsUtil.COINBASE_CONNECTOR_ID); - - if (provider) { - const { address, chainId } = await EthersHelpersUtil.getUserInfo(provider); - if (address && chainId) { - const providerType = PresetsUtil.ConnectorTypesMap[ConstantsUtil.COINBASE_CONNECTOR_ID]; - EthersStoreUtil.setChainId(chainId); - EthersStoreUtil.setProviderType(providerType); - EthersStoreUtil.setProvider(provider); - EthersStoreUtil.setIsConnected(true); - this.setAddress(address); - await this.watchCoinbase(provider); - } - } - } - - private async setAuthProvider() { - StorageUtil.setItem(EthersConstantsUtil.WALLET_ID, ConstantsUtil.AUTH_CONNECTOR_ID); - - if (this.authProvider) { - const { address, chainId } = await this.authProvider.connect(); - super.setLoading(false); - if (address && chainId) { - EthersStoreUtil.setChainId(chainId); - EthersStoreUtil.setProviderType( - PresetsUtil.ConnectorTypesMap[ConstantsUtil.AUTH_CONNECTOR_ID] - ); - EthersStoreUtil.setProvider(this.authProvider as CombinedProviderType); - EthersStoreUtil.setIsConnected(true); - EthersStoreUtil.setAddress(address as Address); - } - } - } - - private async watchWalletConnect() { - const WalletConnectProvider = await this.getWalletConnectProvider(); - - function disconnectHandler() { - StorageUtil.removeItem(EthersConstantsUtil.WALLET_ID); - EthersStoreUtil.reset(); - - WalletConnectProvider?.removeListener('disconnect', disconnectHandler); - WalletConnectProvider?.removeListener('accountsChanged', accountsChangedHandler); - WalletConnectProvider?.removeListener('chainChanged', chainChangedHandler); - } - - function chainChangedHandler(chainId: string) { - if (chainId) { - const chain = EthersHelpersUtil.hexStringToNumber(chainId); - EthersStoreUtil.setChainId(chain); - } - } - - const accountsChangedHandler = async (accounts: string[]) => { - if (accounts.length > 0) { - await this.setWalletConnectProvider(); - } - }; - - if (WalletConnectProvider) { - WalletConnectProvider.on('disconnect', disconnectHandler); - WalletConnectProvider.on('accountsChanged', accountsChangedHandler); - WalletConnectProvider.on('chainChanged', chainChangedHandler); - } - } - - private async watchCoinbase(provider: Provider) { - const walletId = await StorageUtil.getItem(EthersConstantsUtil.WALLET_ID); - - function disconnectHandler() { - StorageUtil.removeItem(EthersConstantsUtil.WALLET_ID); - EthersStoreUtil.reset(); - - provider?.removeListener('disconnect', disconnectHandler); - provider?.removeListener('accountsChanged', accountsChangedHandler); - provider?.removeListener('chainChanged', chainChangedHandler); - } - - function accountsChangedHandler(accounts: string[]) { - if (accounts.length === 0) { - StorageUtil.removeItem(EthersConstantsUtil.WALLET_ID); - EthersStoreUtil.reset(); - } else { - EthersStoreUtil.setAddress(accounts[0] as Address); - } - } - - function chainChangedHandler(chainId: string) { - if (chainId && walletId === ConstantsUtil.COINBASE_CONNECTOR_ID) { - const chain = Number(chainId); - EthersStoreUtil.setChainId(chain); - } - } - - if (provider) { - provider.on('disconnect', disconnectHandler); - provider.on('accountsChanged', accountsChangedHandler); - provider.on('chainChanged', chainChangedHandler); - } - } - - private async syncAccount({ address }: { address?: Address }) { - const chainId = EthersStoreUtil.state.chainId; - const isConnected = EthersStoreUtil.state.isConnected; - - if (isConnected && address && chainId) { - const caipAddress: CaipAddress = `${ConstantsUtil.EIP155}:${chainId}:${address}`; - - this.setIsConnected(isConnected); - - this.setCaipAddress(caipAddress); - - await Promise.all([ - this.syncProfile(address), - this.syncBalance(address), - this.getApprovedCaipNetworksData() - ]); - this.hasSyncedConnectedAccount = true; - } else if (!isConnected && this.hasSyncedConnectedAccount) { - this.close(); - this.resetAccount(); - this.resetWcConnection(); - this.resetNetwork(); - } - } - - private async syncNetwork(chainImages?: AppKitClientOptions['chainImages']) { - const address = EthersStoreUtil.state.address; - const chainId = EthersStoreUtil.state.chainId; - const isConnected = EthersStoreUtil.state.isConnected; - if (this.chains) { - const chain = this.chains.find(c => c.chainId === chainId); - - if (chain) { - const caipChainId: CaipNetworkId = `${ConstantsUtil.EIP155}:${chain.chainId}`; - - this.setCaipNetwork({ - id: caipChainId, - name: chain.name, - imageId: PresetsUtil.EIP155NetworkImageIds[chain.chainId], - imageUrl: chainImages?.[chain.chainId] - }); - if (isConnected && address) { - const caipAddress: CaipAddress = `${ConstantsUtil.EIP155}:${chainId}:${address}`; - this.setCaipAddress(caipAddress); - if (chain.explorerUrl) { - const url = `${chain.explorerUrl}/address/${address}`; - this.setAddressExplorerUrl(url); - } else { - this.setAddressExplorerUrl(undefined); - } - - if (this.hasSyncedConnectedAccount) { - await this.syncBalance(address); - } - } - } - } - } - - private async syncProfile(address: Address) { - const chainId = EthersStoreUtil.state.chainId; - - try { - const response = await this.fetchIdentity({ address }); - - if (!response) { - throw new Error('Couldnt fetch idendity'); - } - - this.setProfileName(response.name); - this.setProfileImage(response.avatar); - } catch { - if (chainId === 1) { - const ensProvider = new InfuraProvider('mainnet'); - const name = await ensProvider.lookupAddress(address); - const avatar = await ensProvider.getAvatar(address); - - if (name) { - this.setProfileName(name); - } - if (avatar) { - this.setProfileImage(avatar); - } - } else { - this.setProfileName(undefined); - this.setProfileImage(undefined); - } - } - } - - private async syncBalance(address: Address) { - const chainId = EthersStoreUtil.state.chainId; - if (chainId && this.chains) { - const chain = this.chains.find(c => c.chainId === chainId); - const token = this.options?.tokens?.[chainId]; - - try { - if (chain) { - const jsonRpcProvider = new JsonRpcProvider(chain.rpcUrl, { - chainId, - name: chain.name - }); - - if (jsonRpcProvider) { - if (token) { - // Get balance from custom token address - const erc20 = new Contract(token.address, erc20ABI, jsonRpcProvider); - // @ts-expect-error - const decimals = await erc20.decimals(); - // @ts-expect-error - const symbol = await erc20.symbol(); - // @ts-expect-error - const balanceOf = await erc20.balanceOf(address); - this.setBalance(formatUnits(balanceOf, decimals), symbol); - } else { - const balance = await jsonRpcProvider.getBalance(address); - const formattedBalance = formatEther(balance); - this.setBalance(formattedBalance, chain.currency); - } - } - } - } catch { - this.setBalance(undefined, undefined); - } - } - } - - private async switchNetwork(chainId: number) { - const provider = EthersStoreUtil.state.provider; - const providerType = EthersStoreUtil.state.providerType; - if (this.chains) { - const chain = this.chains.find(c => c.chainId === chainId); - - const walletConnectType = - PresetsUtil.ConnectorTypesMap[ConstantsUtil.WALLET_CONNECT_CONNECTOR_ID]; - - const coinbaseType = PresetsUtil.ConnectorTypesMap[ConstantsUtil.COINBASE_CONNECTOR_ID]; - - const authType = PresetsUtil.ConnectorTypesMap[ConstantsUtil.AUTH_CONNECTOR_ID]; - - if (providerType === walletConnectType && chain) { - const WalletConnectProvider = provider as unknown as EthereumProvider; - - if (WalletConnectProvider) { - try { - await WalletConnectProvider.request({ - method: 'wallet_switchEthereumChain', - params: [{ chainId: EthersHelpersUtil.numberToHexString(chain.chainId) }] - }); - - EthersStoreUtil.setChainId(chainId); - } catch (switchError: any) { - const message = switchError?.message as string; - if (/(?user rejected)/u.test(message?.toLowerCase())) { - throw new Error('Chain is not supported'); - } - await EthersHelpersUtil.addEthereumChain( - WalletConnectProvider as unknown as Provider, - chain - ); - } - } - } else if (providerType === coinbaseType && chain) { - const CoinbaseProvider = provider; - if (CoinbaseProvider) { - try { - await CoinbaseProvider.request({ - method: 'wallet_switchEthereumChain', - params: [{ chainId: EthersHelpersUtil.numberToHexString(chain.chainId) }] - }); - EthersStoreUtil.setChainId(chain.chainId); - } catch (switchError: any) { - if ( - switchError.code === EthersConstantsUtil.ERROR_CODE_UNRECOGNIZED_CHAIN_ID || - switchError.code === EthersConstantsUtil.ERROR_CODE_DEFAULT || - switchError?.data?.originalError?.code === - EthersConstantsUtil.ERROR_CODE_UNRECOGNIZED_CHAIN_ID - ) { - await EthersHelpersUtil.addEthereumChain(CoinbaseProvider, chain); - } else { - throw new Error('Error switching network'); - } - } - } - } else if (providerType === authType) { - if (this.authProvider && chain?.chainId) { - try { - await this.authProvider?.switchNetwork(chain?.chainId); - EthersStoreUtil.setChainId(chain.chainId); - } catch { - throw new Error('Switching chain failed'); - } - } - } - } - } - - private async handleAuthSetPreferredAccount(address: string, type: AppKitFrameAccountType) { - if (!address) { - return; - } - - const chainId = this.getCaipNetwork()?.id; - const caipAddress: CaipAddress = `${ConstantsUtil.EIP155}:${chainId}:${address}`; - this.setCaipAddress(caipAddress); - this.setPreferredAccountType(type); - - await this.syncAccount({ address: address as Address }); - this.setLoading(false); - } - - private syncConnectors(config: ProviderType) { - const _connectors: Connector[] = []; - const EXCLUDED_CONNECTORS = [ConstantsUtil.AUTH_CONNECTOR_ID]; - - _connectors.push({ - id: ConstantsUtil.WALLET_CONNECT_CONNECTOR_ID, - explorerId: PresetsUtil.ConnectorExplorerIds[ConstantsUtil.WALLET_CONNECT_CONNECTOR_ID], - imageId: PresetsUtil.ConnectorImageIds[ConstantsUtil.WALLET_CONNECT_CONNECTOR_ID], - imageUrl: this.options?.connectorImages?.[ConstantsUtil.WALLET_CONNECT_CONNECTOR_ID], - name: PresetsUtil.ConnectorNamesMap[ConstantsUtil.WALLET_CONNECT_CONNECTOR_ID], - type: PresetsUtil.ConnectorTypesMap[ConstantsUtil.WALLET_CONNECT_CONNECTOR_ID]! - }); - - config.extraConnectors?.forEach(connector => { - if (!EXCLUDED_CONNECTORS.includes(connector.id)) { - if (connector.id === ConstantsUtil.COINBASE_CONNECTOR_ID) { - _connectors.push({ - id: ConstantsUtil.COINBASE_CONNECTOR_ID, - explorerId: PresetsUtil.ConnectorExplorerIds[ConstantsUtil.COINBASE_CONNECTOR_ID], - imageId: PresetsUtil.ConnectorImageIds[ConstantsUtil.COINBASE_CONNECTOR_ID], - imageUrl: this.options?.connectorImages?.[ConstantsUtil.COINBASE_CONNECTOR_ID], - name: - connector?.name ?? PresetsUtil.ConnectorNamesMap[ConstantsUtil.COINBASE_CONNECTOR_ID], - type: PresetsUtil.ConnectorTypesMap[ConstantsUtil.COINBASE_CONNECTOR_ID]! - }); - this.checkActiveCoinbaseProvider(connector as Provider); - } else { - _connectors.push({ - id: connector.id, - name: connector.name ?? PresetsUtil.ConnectorNamesMap[connector.id], - type: 'EXTERNAL' - }); - } - } - }); - - this.setConnectors(_connectors); - } - - private async syncAuthConnector(config: ProviderType) { - const authConnector = config.extraConnectors?.find( - connector => connector.id === ConstantsUtil.AUTH_CONNECTOR_ID - ); - - if (!authConnector) { - return; - } - - this.authProvider = authConnector as AppKitFrameProvider; - - this.addConnector({ - id: ConstantsUtil.AUTH_CONNECTOR_ID, - name: PresetsUtil.ConnectorNamesMap[ConstantsUtil.AUTH_CONNECTOR_ID], - type: PresetsUtil.ConnectorTypesMap[ConstantsUtil.AUTH_CONNECTOR_ID]!, - provider: authConnector - }); - - const connectedConnector = await StorageUtil.getItem('@w3m/connected_connector'); - if (connectedConnector === 'AUTH') { - // Set loader until it reconnects - this.setLoading(true); - } - - const { isConnected } = await this.authProvider.isConnected(); - if (isConnected) { - this.setAuthProvider(); - } - - this.addAuthListeners(this.authProvider); - } - - private async syncConnectedWalletInfo(provider?: Provider) { - if (!provider) { - this.setConnectedWalletInfo(undefined); - - return; - } - - if ((provider as any)?.session?.peer?.metadata) { - const metadata = (provider as unknown as EthereumProvider)?.session?.peer.metadata; - if (metadata) { - this.setConnectedWalletInfo({ - ...metadata, - name: metadata.name, - icon: metadata.icons?.[0] - }); - } - } else if (provider?.id) { - this.setConnectedWalletInfo({ - id: provider.id, - name: provider?.name ?? PresetsUtil.ConnectorNamesMap[provider.id], - icon: this.options?.connectorImages?.[provider.id] - }); - } else { - this.setConnectedWalletInfo(undefined); - } - } - - private async addAuthListeners(authProvider: AppKitFrameProvider) { - authProvider.onSetPreferredAccount(async ({ address, type }) => { - if (address) { - await this.handleAuthSetPreferredAccount(address, type); - } - this.setLoading(false); - }); - - authProvider.setOnTimeout(async () => { - this.handleAlertError(ErrorUtil.ALERT_ERRORS.SOCIALS_TIMEOUT); - this.setLoading(false); - }); - } - - private async addWalletConnectListeners(provider: EthereumProvider) { - if (provider) { - provider.signer.client.core.relayer.on('relayer_connect', () => { - provider.signer.client.core.relayer?.provider?.on('payload', (payload: JsonRpcError) => { - if (payload?.error) { - this.handleAlertError(payload?.error.message); - } - }); - }); - } - } -} diff --git a/packages/ethers/src/helpers.ts b/packages/ethers/src/helpers.ts new file mode 100644 index 00000000..408e3838 --- /dev/null +++ b/packages/ethers/src/helpers.ts @@ -0,0 +1,25 @@ +// Helper to convert Wei (as string or bigint) to ETH +export const formatEther = (wei: bigint): string => { + return (Number(wei) / 1e18).toString(); +}; + +// Raw JSON-RPC for balance lookup +export async function getEthBalance(rpcUrl: string, address: string): Promise { + const body = { + jsonrpc: '2.0', + method: 'eth_getBalance', + params: [address, 'latest'], + id: 1 + }; + + const response = await fetch(rpcUrl, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(body) + }); + + const json = await response.json(); + if (json.error) throw new Error(json.error.message); + + return BigInt(json.result); // result is hex string +} diff --git a/packages/ethers/src/index.tsx b/packages/ethers/src/index.tsx index 2bd2a569..c9acfc5b 100644 --- a/packages/ethers/src/index.tsx +++ b/packages/ethers/src/index.tsx @@ -1,165 +1,2 @@ -import { useEffect, useState, useSyncExternalStore } from 'react'; -import { useSnapshot } from 'valtio'; -import { EthersStoreUtil, type Provider } from '@reown/appkit-scaffold-utils-react-native'; - -export { - AccountButton, - AppKitButton, - ConnectButton, - NetworkButton, - AppKit -} from '@reown/appkit-scaffold-react-native'; -import type { EventName, EventsControllerState } from '@reown/appkit-scaffold-react-native'; -import { ConstantsUtil } from '@reown/appkit-common-react-native'; -export { defaultConfig } from './utils/defaultConfig'; - -import type { AppKitOptions } from './client'; -import { AppKit } from './client'; - -// -- Types ------------------------------------------------------------------- -export type { AppKitOptions } from './client'; - -type OpenOptions = Parameters[0]; - -// -- Setup ------------------------------------------------------------------- -let modal: AppKit | undefined; - -export function createAppKit(options: AppKitOptions) { - if (!modal) { - modal = new AppKit({ - ...options, - _sdkVersion: `react-native-ethers-${ConstantsUtil.VERSION}` - }); - } - - return modal; -} - -// -- Hooks ------------------------------------------------------------------- -export function useAppKit() { - if (!modal) { - throw new Error('Please call "createAppKit" before using "useAppKit" hook'); - } - - async function open(options?: OpenOptions) { - await modal?.open(options); - } - - async function close() { - await modal?.close(); - } - - return { open, close }; -} - -export function useAppKitState() { - if (!modal) { - throw new Error('Please call "createAppKit" before using "useAppKitState" hook'); - } - - const [state, setState] = useState(modal.getState()); - - useEffect(() => { - const unsubscribe = modal?.subscribeState(newState => { - if (newState) setState({ ...newState }); - }); - - return () => { - unsubscribe?.(); - }; - }, []); - - return state; -} - -export function useAppKitProvider() { - const { provider, providerType } = useSnapshot(EthersStoreUtil.state); - - const walletProvider = provider as Provider | undefined; - const walletProviderType = providerType; - - return { - walletProvider, - walletProviderType - }; -} - -export function useDisconnect() { - async function disconnect() { - await modal?.disconnect(); - } - - return { - disconnect - }; -} - -export function useAppKitAccount() { - const { address, isConnected, chainId } = useSnapshot(EthersStoreUtil.state); - - return { - address, - isConnected, - chainId - }; -} - -export function useWalletInfo() { - if (!modal) { - throw new Error('Please call "createAppKit" before using "useWalletInfo" hook'); - } - - const walletInfo = useSyncExternalStore( - modal.subscribeWalletInfo, - modal.getWalletInfo, - modal.getWalletInfo - ); - - return { walletInfo }; -} - -export function useAppKitError() { - const { error } = useSnapshot(EthersStoreUtil.state); - - return { - error - }; -} - -export function useAppKitEvents(callback?: (newEvent: EventsControllerState) => void) { - if (!modal) { - throw new Error('Please call "createAppKit" before using "useAppKitEvents" hook'); - } - - const [event, setEvents] = useState(modal.getEvent()); - - useEffect(() => { - const unsubscribe = modal?.subscribeEvents(newEvent => { - setEvents({ ...newEvent }); - callback?.(newEvent); - }); - - return () => { - unsubscribe?.(); - }; - }, [callback]); - - return event; -} - -export function useAppKitEventSubscription( - event: EventName, - callback: (newEvent: EventsControllerState) => void -) { - if (!modal) { - throw new Error('Please call "createAppKit" before using "useAppKitEventSubscription" hook'); - } - - useEffect(() => { - const unsubscribe = modal?.subscribeEvent(event, callback); - - return () => { - unsubscribe?.(); - }; - }, [callback, event]); -} +import { EthersAdapter } from './adapter'; +export { EthersAdapter }; diff --git a/packages/ethers/src/utils/defaultConfig.ts b/packages/ethers/src/utils/defaultConfig.ts deleted file mode 100644 index 6ef65cee..00000000 --- a/packages/ethers/src/utils/defaultConfig.ts +++ /dev/null @@ -1,19 +0,0 @@ -import type { - Metadata, - Provider, - ProviderType, - AppKitFrameProvider -} from '@reown/appkit-scaffold-utils-react-native'; - -export interface ConfigOptions { - metadata: Metadata; - extraConnectors?: (Provider | AppKitFrameProvider)[]; -} - -export function defaultConfig(options: ConfigOptions) { - const { metadata, extraConnectors } = options; - - let providers: ProviderType = { metadata, extraConnectors }; - - return providers; -} diff --git a/packages/ethers/src/utils/helpers.ts b/packages/ethers/src/utils/helpers.ts deleted file mode 100644 index 2035ecb4..00000000 --- a/packages/ethers/src/utils/helpers.ts +++ /dev/null @@ -1,27 +0,0 @@ -import type { CaipNetworkId } from '@reown/appkit-scaffold-react-native'; -import { PresetsUtil, ConstantsUtil } from '@reown/appkit-common-react-native'; -import EthereumProvider from '@walletconnect/ethereum-provider'; - -export async function getWalletConnectCaipNetworks(provider?: EthereumProvider) { - if (!provider) { - throw new Error('networkControllerClient:getApprovedCaipNetworks - provider is undefined'); - } - - const ns = provider.signer?.session?.namespaces; - const nsMethods = ns?.[ConstantsUtil.EIP155]?.methods; - const nsChains = ns?.[ConstantsUtil.EIP155]?.chains as CaipNetworkId[]; - - return { - supportsAllNetworks: Boolean(nsMethods?.includes(ConstantsUtil.ADD_CHAIN_METHOD)), - approvedCaipNetworkIds: nsChains - }; -} - -export function getAuthCaipNetworks() { - return { - supportsAllNetworks: false, - approvedCaipNetworkIds: PresetsUtil.RpcChainIds.map( - id => `${ConstantsUtil.EIP155}:${id}` - ) as CaipNetworkId[] - }; -} diff --git a/packages/ethers5/package.json b/packages/ethers5/package.json index ff548157..63bd9136 100644 --- a/packages/ethers5/package.json +++ b/packages/ethers5/package.json @@ -42,7 +42,7 @@ "@reown/appkit-scaffold-react-native": "1.2.3", "@reown/appkit-scaffold-utils-react-native": "1.2.3", "@reown/appkit-siwe-react-native": "1.2.3", - "@walletconnect/ethereum-provider": "2.17.3" + "@walletconnect/ethereum-provider": "2.20.2" }, "peerDependencies": { "@react-native-async-storage/async-storage": ">=1.17.0", diff --git a/packages/ethers5/src/client.ts b/packages/ethers5/src/client.ts index c36e0c81..da7469f1 100644 --- a/packages/ethers5/src/client.ts +++ b/packages/ethers5/src/client.ts @@ -1,9 +1,6 @@ import { Contract, ethers, utils } from 'ethers'; import { type AppKitFrameAccountType, - type CaipAddress, - type CaipNetwork, - type CaipNetworkId, type ConnectionControllerClient, type Connector, type EstimateGasTransactionArgs, @@ -11,7 +8,6 @@ import { type NetworkControllerClient, type PublicStateControllerState, type SendTransactionArgs, - type Token, type WriteContractArgs, AppKitScaffold } from '@reown/appkit-scaffold-react-native'; @@ -37,6 +33,10 @@ import { type AppKitSIWEClient } from '@reown/appkit-siwe-react-native'; import { + type CaipAddress, + type CaipNetwork, + type CaipNetworkId, + type Token, erc20ABI, ErrorUtil, NamesUtil, @@ -563,7 +563,7 @@ export class AppKit extends AppKitScaffold { ({ id: `${ConstantsUtil.EIP155}:${chain.chainId}`, name: chain.name, - imageId: PresetsUtil.EIP155NetworkImageIds[chain.chainId], + imageId: PresetsUtil.NetworkImageIds[chain.chainId], imageUrl: chainImages?.[chain.chainId] }) as CaipNetwork ); @@ -752,7 +752,7 @@ export class AppKit extends AppKitScaffold { this.setCaipNetwork({ id: caipChainId, name: chain.name, - imageId: PresetsUtil.EIP155NetworkImageIds[chain.chainId], + imageId: PresetsUtil.NetworkImageIds[chain.chainId], imageUrl: chainImages?.[chain.chainId] }); if (isConnected && address) { diff --git a/packages/ethers5/src/utils/helpers.ts b/packages/ethers5/src/utils/helpers.ts index 2035ecb4..e2197eb4 100644 --- a/packages/ethers5/src/utils/helpers.ts +++ b/packages/ethers5/src/utils/helpers.ts @@ -1,4 +1,4 @@ -import type { CaipNetworkId } from '@reown/appkit-scaffold-react-native'; +import type { CaipNetworkId } from '@reown/appkit-common-react-native'; import { PresetsUtil, ConstantsUtil } from '@reown/appkit-common-react-native'; import EthereumProvider from '@walletconnect/ethereum-provider'; diff --git a/packages/scaffold-utils/package.json b/packages/scaffold-utils/package.json index dbfa9b21..3f3a1a94 100644 --- a/packages/scaffold-utils/package.json +++ b/packages/scaffold-utils/package.json @@ -36,7 +36,7 @@ }, "dependencies": { "@reown/appkit-common-react-native": "1.2.3", - "@reown/appkit-scaffold-react-native": "1.2.3" + "@reown/appkit-core-react-native": "1.2.3" }, "react-native": "src/index.ts", "react-native-builder-bob": { diff --git a/packages/scaffold-utils/src/utils/EthersHelpersUtil.ts b/packages/scaffold-utils/src/utils/EthersHelpersUtil.ts index 338fbd47..7ec45f58 100644 --- a/packages/scaffold-utils/src/utils/EthersHelpersUtil.ts +++ b/packages/scaffold-utils/src/utils/EthersHelpersUtil.ts @@ -1,4 +1,4 @@ -import type { CaipNetwork } from '@reown/appkit-scaffold-react-native'; +import type { CaipNetwork } from '@reown/appkit-common-react-native'; import { ConstantsUtil, PresetsUtil } from '@reown/appkit-common-react-native'; import type { Chain, Provider } from './EthersTypesUtil'; @@ -12,7 +12,7 @@ export const EthersHelpersUtil = { return { id: `${ConstantsUtil.EIP155}:${chain.chainId}`, name: chain.name, - imageId: PresetsUtil.EIP155NetworkImageIds[chain.chainId] + imageId: PresetsUtil.NetworkImageIds[chain.chainId] } as CaipNetwork; }, hexStringToNumber(value: string) { @@ -43,6 +43,7 @@ export const EthersHelpersUtil = { return address; }, async addEthereumChain(provider: Provider, chain: Chain) { + //TODO: Check if this is needed await provider.request({ method: 'wallet_addEthereumChain', params: [ @@ -56,7 +57,7 @@ export const EthersHelpersUtil = { symbol: chain.currency }, blockExplorerUrls: [chain.explorerUrl], - iconUrls: [PresetsUtil.EIP155NetworkImageIds[chain.chainId]] + iconUrls: [PresetsUtil.NetworkImageIds[chain.chainId]] } ] }); diff --git a/packages/scaffold-utils/src/utils/HelpersUtil.ts b/packages/scaffold-utils/src/utils/HelpersUtil.ts index 354d3e99..e07252ec 100644 --- a/packages/scaffold-utils/src/utils/HelpersUtil.ts +++ b/packages/scaffold-utils/src/utils/HelpersUtil.ts @@ -1,5 +1,4 @@ -import type { Tokens } from '@reown/appkit-scaffold-react-native'; -import { ConstantsUtil } from '@reown/appkit-common-react-native'; +import { ConstantsUtil, type Tokens } from '@reown/appkit-common-react-native'; export const HelpersUtil = { getCaipTokens(tokens?: Tokens) { diff --git a/packages/solana/.eslintignore b/packages/solana/.eslintignore new file mode 100644 index 00000000..c18ed016 --- /dev/null +++ b/packages/solana/.eslintignore @@ -0,0 +1,2 @@ +node_modules/ +lib/ \ No newline at end of file diff --git a/packages/solana/.eslintrc.json b/packages/solana/.eslintrc.json new file mode 100644 index 00000000..b9233ee4 --- /dev/null +++ b/packages/solana/.eslintrc.json @@ -0,0 +1,3 @@ +{ + "extends": ["../../.eslintrc"] +} diff --git a/packages/solana/.npmignore b/packages/solana/.npmignore new file mode 100644 index 00000000..e203f76a --- /dev/null +++ b/packages/solana/.npmignore @@ -0,0 +1,10 @@ +*.log +*.env +npm-debug.log* +node_modules +package-lock.json +src +tests +index.ts +.eslintrc.json +.turbo diff --git a/packages/solana/bob.config.js b/packages/solana/bob.config.js new file mode 100644 index 00000000..b7ca0ad6 --- /dev/null +++ b/packages/solana/bob.config.js @@ -0,0 +1,14 @@ +module.exports = { + source: 'src', + output: 'lib', + targets: [ + 'commonjs', + 'module', + [ + 'typescript', + { + tsc: '../../node_modules/.bin/tsc' + } + ] + ] +}; diff --git a/packages/solana/package.json b/packages/solana/package.json new file mode 100644 index 00000000..5eea0626 --- /dev/null +++ b/packages/solana/package.json @@ -0,0 +1,44 @@ +{ + "name": "@reown/appkit-solana-react-native", + "version": "1.2.3", + "main": "lib/commonjs/index.js", + "types": "lib/typescript/index.d.ts", + "module": "lib/module/index.js", + "source": "src/index.tsx", + "scripts": { + "build": "bob build", + "clean": "rm -rf lib", + "lint": "eslint . --ext .js,.jsx,.ts,.tsx" + }, + "files": [ + "src", + "lib", + "!**/__tests__", + "!**/__fixtures__", + "!**/__mocks__" + ], + "keywords": [ + "web3", + "crypto", + "solana", + "appkit", + "reown", + "walletconnect", + "react-native" + ], + "repository": "https://github.com/reown-com/appkit-react-native", + "author": "Reown (https://reown.com)", + "homepage": "https://reown.com/appkit", + "license": "Apache-2.0", + "bugs": { + "url": "https://github.com/reown-com/appkit-react-native/issues" + }, + "publishConfig": { + "registry": "https://registry.npmjs.org/", + "access": "public" + }, + "dependencies": { + "@reown/appkit-common-react-native": "1.2.3" + }, + "react-native": "src/index.tsx" +} diff --git a/packages/solana/readme.md b/packages/solana/readme.md new file mode 100644 index 00000000..60524ccd --- /dev/null +++ b/packages/solana/readme.md @@ -0,0 +1,9 @@ +#### 📚 [Documentation](https://docs.reown.com/appkit/react-native/core/installation) + +#### 🔎 [Examples](https://github.com/reown-com/react-native-examples) + +#### 🔗 [Website](https://reown.com/appkit) + +# AppKit + +Your on-ramp to web3 multichain. AppKit is a versatile library that makes it super easy to connect users with your Dapp and start interacting with the blockchain. diff --git a/packages/solana/src/adapter.ts b/packages/solana/src/adapter.ts new file mode 100644 index 00000000..e8a79dcf --- /dev/null +++ b/packages/solana/src/adapter.ts @@ -0,0 +1,103 @@ +import { + SolanaBaseAdapter, + type AppKitNetwork, + type CaipAddress, + type ChainNamespace, + type GetBalanceParams, + type GetBalanceResponse +} from '@reown/appkit-common-react-native'; +import { getSolanaNativeBalance, getSolanaTokenBalance } from './helpers'; + +export class SolanaAdapter extends SolanaBaseAdapter { + private static supportedNamespace: ChainNamespace = 'solana'; + + constructor(configParams: { projectId: string }) { + super({ + projectId: configParams.projectId, + supportedNamespace: SolanaAdapter.supportedNamespace + }); + } + + async getBalance(params: GetBalanceParams): Promise { + console.log('solana getBalance'); + const { network, address, tokens } = params; + + if (!this.connector) throw new Error('No active connector'); + if (!network) throw new Error('No network provided'); + + const balanceAddress = + address || this.getAccounts()?.find(account => account.includes(network.id.toString())); + + if (!balanceAddress) { + return { amount: '0.00', symbol: 'SOL' }; + } + + try { + const rpcUrl = network.rpcUrls?.default?.http?.[0]; + if (!rpcUrl) throw new Error('No RPC URL available'); + + const base58Address = balanceAddress.split(':')[2]; + + if (!base58Address) throw new Error('Invalid balance address'); + + const token = network?.caipNetworkId && tokens?.[network.caipNetworkId]?.address; + let balance; + + if (token) { + const { amount, symbol } = await getSolanaTokenBalance(rpcUrl, base58Address, token); + balance = { + amount, + symbol + }; + } else { + const amount = await getSolanaNativeBalance(rpcUrl, base58Address); + balance = { + amount: amount.toString(), + symbol: 'SOL' + }; + } + + this.emit('balanceChanged', { + namespace: this.getSupportedNamespace(), + address: balanceAddress, + balance + }); + + return balance; + } catch (error) { + return { amount: '0.00', symbol: 'SOL' }; + } + } + + async switchNetwork(network: AppKitNetwork): Promise { + if (!this.connector) throw new Error('No active connector'); + + const provider = this.connector.getProvider(); + if (!provider) throw new Error('No active provider'); + + try { + await this.connector.switchNetwork(network); + + return; + } catch (switchError: any) { + throw switchError; + } + } + + getAccounts(): CaipAddress[] | undefined { + if (!this.connector) throw new Error('No active connector'); + const namespaces = this.connector.getNamespaces(); + + return namespaces[this.getSupportedNamespace()]?.accounts; + } + + disconnect(): Promise { + if (!this.connector) throw new Error('No active connector'); + + return this.connector.disconnect(); + } + + getSupportedNamespace(): ChainNamespace { + return SolanaAdapter.supportedNamespace; + } +} diff --git a/packages/solana/src/helpers.ts b/packages/solana/src/helpers.ts new file mode 100644 index 00000000..fe9cac9a --- /dev/null +++ b/packages/solana/src/helpers.ts @@ -0,0 +1,100 @@ +export interface TokenInfo { + address: string; + symbol: string; + name: string; + decimals: number; + logoURI?: string; +} + +/** + * Validates if the given string is a Solana address. + * @param address The string to validate. + * @returns True if the address is valid, false otherwise. + */ +export function isSolanaAddress(address: string): boolean { + const solanaAddressRegex = /^[1-9A-HJ-NP-Za-km-z]{32,44}$/; + + return solanaAddressRegex.test(address); +} + +/** + * Helper to fetch SOL balance using JSON-RPC + * @param rpcUrl Solana RPC endpoint + * @param address Solana public address (base58) + */ +export async function getSolanaNativeBalance(rpcUrl: string, address: string): Promise { + if (!isSolanaAddress(address)) { + throw new Error('Invalid Solana address format'); + } + + const response = await fetch(rpcUrl, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + jsonrpc: '2.0', + id: 1, + method: 'getBalance', + params: [address] + }) + }); + + const json = (await response.json()) as { + result: { value: number }; + error?: { message: string }; + }; + if (json.error) throw new Error(json.error.message); + + return json.result.value / 1000000000; // Convert lamports to SOL +} + +let tokenCache: Record = {}; +/** + * Fetch metadata for a Solana SPL token using the Jupiter token list. + * @param mint - The token's mint address + * @returns TokenInfo if found, or undefined + */ +export async function getSolanaTokenMetadata(mint: string): Promise { + // Return from cache if available + if (tokenCache[mint]) return tokenCache[mint]; + + try { + const res = await fetch('https://token.jup.ag/all'); + const list: TokenInfo[] = await res.json(); + + for (const token of list) { + tokenCache[token.address] = token; + } + + return tokenCache[mint]; + } catch (error) { + return undefined; + } +} + +export async function getSolanaTokenBalance( + rpcUrl: string, + address: string, + tokenAddress: string +): Promise<{ amount: string; symbol: string }> { + if (!isSolanaAddress(address)) { + throw new Error('Invalid Solana address format'); + } + + const token = await getSolanaTokenMetadata(tokenAddress); + + const response = await fetch(rpcUrl, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + jsonrpc: '2.0', + id: 1, + method: 'getTokenAccountsByOwner', + params: [address, { mint: tokenAddress }, { encoding: 'jsonParsed' }] + }) + }); + + const result = await response.json(); + const balance = result.result.value[0]?.account?.data?.parsed?.info?.tokenAmount?.uiAmount; + + return { amount: balance?.toString() ?? '0', symbol: token?.symbol ?? 'SOL' }; +} diff --git a/packages/solana/src/index.tsx b/packages/solana/src/index.tsx new file mode 100644 index 00000000..616efc42 --- /dev/null +++ b/packages/solana/src/index.tsx @@ -0,0 +1,2 @@ +import { SolanaAdapter } from './adapter'; +export { SolanaAdapter }; diff --git a/packages/solana/tsconfig.json b/packages/solana/tsconfig.json new file mode 100644 index 00000000..512da539 --- /dev/null +++ b/packages/solana/tsconfig.json @@ -0,0 +1,5 @@ +{ + "extends": "../../tsconfig.json", + "include": ["src", "src/index.tsx"], + "exclude": ["lib", "node_modules"] +} diff --git a/packages/ui/src/composites/wui-network-button/styles.ts b/packages/ui/src/composites/wui-network-button/styles.ts index f2166e82..77352019 100644 --- a/packages/ui/src/composites/wui-network-button/styles.ts +++ b/packages/ui/src/composites/wui-network-button/styles.ts @@ -21,8 +21,7 @@ export default StyleSheet.create({ height: 24, width: 24, borderRadius: BorderRadius.full, - borderWidth: 2, - paddingLeft: Spacing['4xs'] + borderWidth: 2 }, imageDisabled: { opacity: 0.4 diff --git a/packages/ui/src/index.ts b/packages/ui/src/index.ts index da47af0c..4bcfb1a3 100644 --- a/packages/ui/src/index.ts +++ b/packages/ui/src/index.ts @@ -80,7 +80,8 @@ export type { SizeType, TagType, TextType, - VisualType + VisualType, + TransactionType } from './utils/TypesUtil'; export { UiUtil } from './utils/UiUtil'; export { TransactionUtil } from './utils/TransactionUtil'; diff --git a/packages/wagmi/package.json b/packages/wagmi/package.json index c998ec99..5635649a 100644 --- a/packages/wagmi/package.json +++ b/packages/wagmi/package.json @@ -40,7 +40,7 @@ }, "dependencies": { "@reown/appkit-common-react-native": "1.2.3", - "@reown/appkit-scaffold-react-native": "1.2.3", + "@reown/appkit-react-native": "1.2.3", "@reown/appkit-scaffold-utils-react-native": "1.2.3", "@reown/appkit-siwe-react-native": "1.2.3" }, diff --git a/packages/wagmi/src/adapter.ts b/packages/wagmi/src/adapter.ts new file mode 100644 index 00000000..e7c0b0b3 --- /dev/null +++ b/packages/wagmi/src/adapter.ts @@ -0,0 +1,184 @@ +import { + EVMAdapter, + WalletConnector, + type AppKitNetwork, + type CaipAddress, + type ChainNamespace, + type GetBalanceParams, + type GetBalanceResponse +} from '@reown/appkit-common-react-native'; +import { + type Config, + type CreateConfigParameters, + createConfig, + getBalance as getBalanceWagmi, + switchChain as switchChainWagmi, + disconnect as disconnectWagmiCore, + connect as connectWagmi, + type Connector +} from '@wagmi/core'; +import type { Chain } from 'wagmi/chains'; +import { getTransport } from './utils/helpers'; +import { formatUnits, type Hex } from 'viem'; +import { UniversalConnector } from './connectors/UniversalConnector'; + +type ConfigParams = Partial & { + networks: [Chain, ...Chain[]]; + projectId: string; + connectors?: Connector[]; +}; + +export class WagmiAdapter extends EVMAdapter { + private static supportedNamespace: ChainNamespace = 'eip155'; + public wagmiChains: readonly Chain[] | undefined; + public wagmiConfig!: Config; + private wagmiConfigConnector?: Connector; + + constructor(configParams: ConfigParams) { + super({ + projectId: configParams.projectId, + supportedNamespace: WagmiAdapter.supportedNamespace + }); + this.wagmiChains = configParams.networks; + this.wagmiConfig = this.createWagmiInternalConfig(configParams); + } + + private createWagmiInternalConfig(configParams: ConfigParams): Config { + // Connectors are typically added via wagmiConfig.connectors, but here AppKit manages the connection. + // We'll use the `connect` action with our dynamically created connector instance. + // So, the `connectors` array for createConfig can be empty and is added later. + const initialConnectors: (() => Connector)[] = []; + + const transportsArr = configParams.networks.map(chain => [ + chain.id, + getTransport({ chainId: chain.id, projectId: configParams.projectId }) + ]); + const transports = Object.fromEntries(transportsArr); + + return createConfig({ + chains: configParams.networks, + connectors: initialConnectors, // Empty, as we connect programmatically + transports, + multiInjectedProviderDiscovery: false + }); + } + + async switchNetwork(network: AppKitNetwork): Promise { + if (!this.wagmiConfigConnector) { + throw new Error('WagmiAdapter: AppKit connector not set or not connected via Wagmi.'); + } + + await switchChainWagmi(this.wagmiConfig, { + chainId: network.id as number, + connector: this.wagmiConfigConnector + }); + } + + async getBalance(params: GetBalanceParams): Promise { + const { network, address, tokens } = params; + + if (!this.connector) throw new Error('No active AppKit connector (EVMAdapter.connector)'); + if (!network) throw new Error('No network provided'); + + if (!this.wagmiConfigConnector) { + throw new Error('WagmiAdapter: AppKit connector not properly configured with Wagmi.'); + } + + const balanceAddress = + address || + this.getAccounts()?.find((acc: CaipAddress) => acc.includes(network.id.toString())); + + if (!balanceAddress) { + return Promise.resolve({ amount: '0.00', symbol: network.nativeCurrency.symbol || 'ETH' }); + } + + const accountHex = balanceAddress.split(':')[2] as Hex; + + const token = network?.caipNetworkId && (tokens?.[network.caipNetworkId]?.address as Hex); + + const balance = await getBalanceWagmi(this.wagmiConfig, { + address: accountHex, + chainId: network.id as number, + token + }); + + const formattedBalance = { + amount: formatUnits(balance.value, balance.decimals), + symbol: balance.symbol, + contractAddress: token ? (`${network.caipNetworkId}:${token}` as CaipAddress) : undefined + }; + + this.emit('balanceChanged', { + namespace: this.getSupportedNamespace(), + address: balanceAddress, + balance: formattedBalance + }); + + return Promise.resolve(formattedBalance); + } + + getAccounts(): CaipAddress[] | undefined { + if (!this.connector) { + return undefined; + } + + const namespaces = this.connector.getNamespaces(); + if (!namespaces) { + return undefined; + } + + const supportedNamespaceKey = this.getSupportedNamespace(); + const accountsForNamespace = namespaces[supportedNamespaceKey]; + + return accountsForNamespace?.accounts; + } + + async disconnect(): Promise { + if (this.wagmiConfigConnector) { + await disconnectWagmiCore(this.wagmiConfig, { connector: this.wagmiConfigConnector }); + this.wagmiConfigConnector = undefined; + } else if (this.connector) { + await this.connector.disconnect(); + this.onDisconnect(); + } + + const evmAdapterInstance = this as any; + if ('connector' in evmAdapterInstance) { + evmAdapterInstance.connector = undefined; + } + } + + getSupportedNamespace(): ChainNamespace { + return WagmiAdapter.supportedNamespace; + } + + override setConnector(_connector: WalletConnector): void { + super.setConnector(_connector); + + if (_connector && this.wagmiChains) { + if (!this.wagmiConfigConnector) { + // Manually add the connector to the wagmiConfig + const connectorInstance = this.wagmiConfig._internal.connectors.setup( + UniversalConnector(_connector) + ); + + this.wagmiConfig._internal.connectors.setState(prev => [...prev, connectorInstance]); + this.wagmiConfigConnector = connectorInstance; + + connectorInstance.emitter.on('message', ({ type }: { type: string }) => { + if (type === 'externalDisconnect') { + this.onDisconnect(); + + this.wagmiConfigConnector = undefined; + } + }); + + try { + connectWagmi(this.wagmiConfig, { connector: connectorInstance }); + } catch (error) { + this.wagmiConfigConnector = undefined; + } + } + } + } +} diff --git a/packages/wagmi/src/client.ts b/packages/wagmi/src/client.ts deleted file mode 100644 index 5c4a4aac..00000000 --- a/packages/wagmi/src/client.ts +++ /dev/null @@ -1,644 +0,0 @@ -import { formatUnits, type Hex, parseUnits } from 'viem'; -import { - type GetAccountReturnType, - type GetEnsAddressReturnType, - type Connector as WagmiConnector, - connect, - reconnect, - disconnect, - signMessage, - getAccount, - switchChain, - watchAccount, - watchConnectors, - getEnsName, - getEnsAvatar as wagmiGetEnsAvatar, - getEnsAddress as wagmiGetEnsAddress, - getBalance, - prepareTransactionRequest, - estimateGas as wagmiEstimateGas, - sendTransaction as wagmiSendTransaction, - waitForTransactionReceipt, - writeContract as wagmiWriteContract -} from '@wagmi/core'; -import { normalize } from 'viem/ens'; -import { mainnet, type Chain } from '@wagmi/core/chains'; -import EthereumProvider, { OPTIONAL_METHODS } from '@walletconnect/ethereum-provider'; -import { type JsonRpcError } from '@walletconnect/jsonrpc-types'; -import { - type CaipAddress, - type CaipNetwork, - type CaipNetworkId, - type ConnectionControllerClient, - type Connector, - type LibraryOptions, - type NetworkControllerClient, - type PublicStateControllerState, - type SendTransactionArgs, - type Token, - AppKitScaffold, - type WriteContractArgs, - type AppKitFrameProvider, - type EstimateGasTransactionArgs -} from '@reown/appkit-scaffold-react-native'; -import { HelpersUtil, StorageUtil } from '@reown/appkit-scaffold-utils-react-native'; -import { - NetworkUtil, - NamesUtil, - ErrorUtil, - ConstantsUtil, - PresetsUtil, - type ConnectorType -} from '@reown/appkit-common-react-native'; -import { - SIWEController, - getDidChainId, - getDidAddress, - type AppKitSIWEClient -} from '@reown/appkit-siwe-react-native'; -import { - getCaipDefaultChain, - getAuthCaipNetworks, - getWalletConnectCaipNetworks, - requireCaipAddress -} from './utils/helpers'; -import { defaultWagmiConfig } from './utils/defaultWagmiConfig'; - -// -- Types --------------------------------------------------------------------- -type WagmiConfig = ReturnType; - -export interface AppKitClientOptions extends Omit { - wagmiConfig: WagmiConfig; - siweConfig?: AppKitSIWEClient; - defaultChain?: Chain; - chainImages?: Record; - connectorImages?: Record; - tokens?: Record; -} - -export type AppKitOptions = Omit; - -// @ts-expect-error: Overriden state type is correct -interface AppKitState extends PublicStateControllerState { - selectedNetworkId: number | undefined; -} - -// -- Client -------------------------------------------------------------------- -export class AppKit extends AppKitScaffold { - private hasSyncedConnectedAccount = false; - - private options: AppKitClientOptions | undefined = undefined; - - private wagmiConfig: WagmiConfig; - - public constructor(options: AppKitClientOptions) { - const { wagmiConfig, siweConfig, defaultChain, tokens, _sdkVersion, ...appKitOptions } = - options; - - if (!wagmiConfig) { - throw new Error('appkit:constructor - wagmiConfig is undefined'); - } - - if (!appKitOptions.projectId) { - throw new Error(ErrorUtil.ALERT_ERRORS.PROJECT_ID_NOT_CONFIGURED.shortMessage); - } - - const networkControllerClient: NetworkControllerClient = { - switchCaipNetwork: async caipNetwork => { - const chainId = NetworkUtil.caipNetworkIdToNumber(caipNetwork?.id); - if (chainId) { - await switchChain(wagmiConfig, { chainId }); - } - }, - - async getApprovedCaipNetworksData() { - const walletChoice = await StorageUtil.getConnectedConnector(); - const walletConnectType = - PresetsUtil.ConnectorTypesMap[ConstantsUtil.WALLET_CONNECT_CONNECTOR_ID]!; - - const authType = PresetsUtil.ConnectorTypesMap[ConstantsUtil.AUTH_CONNECTOR_ID]!; - - if (walletChoice?.includes(walletConnectType)) { - const connector = wagmiConfig.connectors.find( - c => c.id === ConstantsUtil.WALLET_CONNECT_CONNECTOR_ID - ); - - return getWalletConnectCaipNetworks(connector); - } else if (authType) { - return getAuthCaipNetworks(); - } - - return { approvedCaipNetworkIds: undefined, supportsAllNetworks: true }; - } - }; - - const connectionControllerClient: ConnectionControllerClient = { - connectWalletConnect: async (onUri, walletUniversalLink) => { - const connector = wagmiConfig.connectors.find( - c => c.id === ConstantsUtil.WALLET_CONNECT_CONNECTOR_ID - ); - if (!connector) { - throw new Error( - 'connectionControllerClient:getWalletConnectUri - connector is undefined' - ); - } - - const provider = (await connector.getProvider()) as Awaited< - ReturnType<(typeof EthereumProvider)['init']> - >; - - provider.on('display_uri', data => { - onUri(data); - }); - - // When connecting through walletconnect, we need to set the clientId in the store - const clientId = await provider.signer?.client?.core?.crypto?.getClientId(); - if (clientId) { - this.setClientId(clientId); - } - - const chainId = NetworkUtil.caipNetworkIdToNumber(this.getCaipNetwork()?.id); - - // SIWE - const siweParams = await siweConfig?.getMessageParams?.(); - // Make sure client uses ethereum provider version that supports `authenticate` - if ( - siweConfig?.options?.enabled && - typeof provider?.authenticate === 'function' && - siweParams && - Object.keys(siweParams || {}).length > 0 - ) { - // @ts-expect-error - setting requested chains beforehand avoids wagmi auto disconnecting the session when `connect` is called because it things chains are stale - await connector.setRequestedChainsIds(siweParams.chains); - const result = await provider.authenticate( - { - nonce: await siweConfig.getNonce(), - methods: [...OPTIONAL_METHODS], - ...siweParams - }, - walletUniversalLink - ); - - // Auths is an array of signed CACAO objects https://github.com/ChainAgnostic/CAIPs/blob/main/CAIPs/caip-74.md - const signedCacao = result?.auths?.[0]; - if (signedCacao) { - const { p, s } = signedCacao; - const cacaoChainId = getDidChainId(p.iss) || ''; - const address = getDidAddress(p.iss); - try { - // Kicks off verifyMessage and populates external states - const message = provider.signer.client.formatAuthMessage({ - request: p, - iss: p.iss - }); - - await SIWEController.verifyMessage({ - message, - signature: s.s, - cacao: signedCacao - }); - - if (address && chainId) { - const session = { - address, - chainId: parseInt(cacaoChainId, 10) - }; - - SIWEController.setSession(session); - SIWEController.onSignIn?.(session); - } - } catch (error) { - // eslint-disable-next-line no-console - console.error('Error verifying message', error); - // eslint-disable-next-line no-console - await provider.disconnect().catch(console.error); - // eslint-disable-next-line no-console - await SIWEController.signOut().catch(console.error); - throw error; - } - /* - * Unassign the connector from the wagmiConfig and allow connect() to reassign it in the next step - * this avoids case where wagmi throws because the connector is already connected - * what we need connect() to do is to only setup internal event listeners - */ - this.wagmiConfig.state.current = ''; - } - } - - await connect(this.wagmiConfig, { connector, chainId }); - }, - - connectExternal: async ({ id }) => { - const connector = wagmiConfig.connectors.find(c => c.id === id); - if (!connector) { - throw new Error('connectionControllerClient:connectExternal - connector is undefined'); - } - - // If connecting with something else than walletconnect, we need to clear the clientId in the store - this.setClientId(null); - - const chainId = NetworkUtil.caipNetworkIdToNumber(this.getCaipNetwork()?.id); - await connect(this.wagmiConfig, { connector, chainId }); - }, - - signMessage: async message => signMessage(this.wagmiConfig, { message }), - - disconnect: async () => { - await disconnect(this.wagmiConfig); - this.setClientId(null); - - if (siweConfig?.options?.signOutOnDisconnect) { - await SIWEController.signOut(); - } - }, - - sendTransaction: async (data: SendTransactionArgs) => { - const { chainId } = getAccount(this.wagmiConfig); - - const txParams = { - account: data.address, - to: data.to, - value: data.value, - gas: data.gas, - gasPrice: data.gasPrice, - data: data.data, - chainId, - type: 'legacy' as const - }; - - await prepareTransactionRequest(this.wagmiConfig, txParams); - const tx = await wagmiSendTransaction(this.wagmiConfig, txParams); - - await waitForTransactionReceipt(this.wagmiConfig, { hash: tx, timeout: 25000 }); - - return tx; - }, - - writeContract: async (data: WriteContractArgs) => { - const caipAddress = this.getCaipAddress() || ''; - const account = requireCaipAddress(caipAddress); - const chainId = NetworkUtil.caipNetworkIdToNumber(this.getCaipNetwork()?.id); - - const tx = await wagmiWriteContract(wagmiConfig, { - chainId, - address: data.tokenAddress, - account, - abi: data.abi, - functionName: data.method, - args: [data.receiverAddress, data.tokenAmount] - }); - - return tx; - }, - - estimateGas: async ({ - address, - to, - data, - chainNamespace - }: EstimateGasTransactionArgs): Promise => { - if (chainNamespace && chainNamespace !== 'eip155') { - throw new Error('estimateGas - chainNamespace is not eip155'); - } - - try { - const result = await wagmiEstimateGas(this.wagmiConfig, { - account: address as Hex, - to: to as Hex, - data: data as Hex, - type: 'legacy' - }); - - return result; - } catch (error) { - throw new Error('WagmiAdapter:estimateGas - error estimating gas'); - } - }, - - parseUnits, - - formatUnits, - - getEnsAddress: async (value: string) => { - try { - if (!this.wagmiConfig) { - throw new Error( - 'networkControllerClient:getApprovedCaipNetworksData - wagmiConfig is undefined' - ); - } - const chainId = Number(NetworkUtil.caipNetworkIdToNumber(this.getCaipNetwork()?.id)); - let ensName: boolean | GetEnsAddressReturnType = false; - let wcName: boolean | string = false; - if (NamesUtil.isReownName(value)) { - wcName = (await this.resolveReownName(value)) || false; - } - if (chainId === 1) { - ensName = await wagmiGetEnsAddress(this.wagmiConfig, { - name: normalize(value), - chainId - }); - } - - return ensName || wcName || false; - } catch { - return false; - } - }, - getEnsAvatar: async (value: string) => { - const chainId = Number(NetworkUtil.caipNetworkIdToNumber(this.getCaipNetwork()?.id)); - if (chainId !== mainnet.id) { - return false; - } - const avatar = await wagmiGetEnsAvatar(this.wagmiConfig, { - name: normalize(value), - chainId - }); - - return avatar || false; - } - }; - - super({ - networkControllerClient, - connectionControllerClient, - siweControllerClient: siweConfig, - defaultChain: getCaipDefaultChain(defaultChain), - tokens: HelpersUtil.getCaipTokens(tokens), - _sdkVersion: _sdkVersion ?? `react-native-wagmi-${ConstantsUtil.VERSION}`, - ...appKitOptions - }); - - this.options = options; - this.wagmiConfig = wagmiConfig; - - this.syncRequestedNetworks([...wagmiConfig.chains]); - this.syncConnectors([...wagmiConfig.connectors]); - - watchConnectors(wagmiConfig, { - onChange: connectors => this.syncConnectors([...connectors]) - }); - - watchAccount(wagmiConfig, { - onChange: (accountData, prevAccountData) => { - this.syncAccount({ ...accountData }); - - if (accountData.status === 'disconnected' && prevAccountData.status === 'connected') { - this.close(); - } - } - }); - } - - // -- Public ------------------------------------------------------------------ - - // @ts-expect-error: Overriden state type is correct - public override getState() { - const state = super.getState(); - - return { - ...state, - selectedNetworkId: NetworkUtil.caipNetworkIdToNumber(state.selectedNetworkId) - }; - } - - // @ts-expect-error: Overriden state type is correct - public override subscribeState(callback: (state: AppKitState) => void) { - return super.subscribeState(state => - callback({ - ...state, - selectedNetworkId: NetworkUtil.caipNetworkIdToNumber(state.selectedNetworkId) - }) - ); - } - - // -- Private ----------------------------------------------------------------- - private syncRequestedNetworks(chains: Chain[]) { - const requestedCaipNetworks = chains?.map( - chain => - ({ - id: `${ConstantsUtil.EIP155}:${chain.id}`, - name: chain.name, - imageId: PresetsUtil.EIP155NetworkImageIds[chain.id], - imageUrl: this.options?.chainImages?.[chain.id] - }) as CaipNetwork - ); - this.setRequestedCaipNetworks(requestedCaipNetworks ?? []); - } - - private async syncAccount({ - address, - isConnected, - chainId, - connector, - isConnecting, - isReconnecting - }: Pick< - GetAccountReturnType, - 'address' | 'isConnected' | 'chainId' | 'connector' | 'isConnecting' | 'isReconnecting' - >) { - this.syncNetwork(address, chainId, isConnected); - this.setLoading(!!connector && (isConnecting || isReconnecting)); - - if (isConnected && address && chainId) { - const caipAddress: CaipAddress = `${ConstantsUtil.EIP155}:${chainId}:${address}`; - this.setIsConnected(isConnected); - this.setCaipAddress(caipAddress); - await Promise.all([ - this.syncProfile(address, chainId), - this.syncBalance(address, chainId), - this.syncConnectedWalletInfo(connector), - this.getApprovedCaipNetworksData() - ]); - this.hasSyncedConnectedAccount = true; - } else if (!isConnected && !isConnecting && !isReconnecting && this.hasSyncedConnectedAccount) { - this.resetAccount(); - this.resetWcConnection(); - this.resetNetwork(); - } - } - - private async syncNetwork(address?: Hex, chainId?: number, isConnected?: boolean) { - const chain = this.wagmiConfig.chains.find((c: Chain) => c.id === chainId); - - if (chain || chainId) { - const name = chain?.name ?? chainId?.toString(); - const id = Number(chain?.id ?? chainId); - const caipChainId: CaipNetworkId = `${ConstantsUtil.EIP155}:${id}`; - this.setCaipNetwork({ - id: caipChainId, - name, - imageId: PresetsUtil.EIP155NetworkImageIds[id], - imageUrl: this.options?.chainImages?.[id] - }); - if (isConnected && address && chainId) { - const caipAddress: CaipAddress = `${ConstantsUtil.EIP155}:${id}:${address}`; - this.setCaipAddress(caipAddress); - if (chain?.blockExplorers?.default?.url) { - const url = `${chain.blockExplorers.default.url}/address/${address}`; - this.setAddressExplorerUrl(url); - } else { - this.setAddressExplorerUrl(undefined); - } - if (this.hasSyncedConnectedAccount) { - await this.syncProfile(address, chainId); - await this.syncBalance(address, chainId); - } - } - } - } - - private async syncProfile(address: Hex, chainId: number) { - try { - const response = await this.fetchIdentity({ address }); - - if (!response) { - throw new Error('Couldnt fetch idendity'); - } - - this.setProfileName(response.name); - this.setProfileImage(response.avatar); - } catch { - if (chainId === mainnet.id) { - const profileName = await getEnsName(this.wagmiConfig, { address, chainId }); - if (profileName) { - this.setProfileName(profileName); - const profileImage = await wagmiGetEnsAvatar(this.wagmiConfig, { - name: profileName, - chainId - }); - if (profileImage) { - this.setProfileImage(profileImage); - } - } - } else { - this.setProfileName(undefined); - this.setProfileImage(undefined); - } - } - } - - private async syncBalance(address: Hex, chainId: number) { - const chain = this.wagmiConfig.chains.find((c: Chain) => c.id === chainId); - try { - if (chain) { - const balance = await getBalance(this.wagmiConfig, { - address, - chainId: chain.id, - token: this.options?.tokens?.[chainId]?.address as Hex - }); - const formattedBalance = formatUnits(balance.value, balance.decimals); - this.setBalance(formattedBalance, balance.symbol); - - return; - } - this.setBalance(undefined, undefined); - } catch { - this.setBalance(undefined, undefined); - } - } - - private async syncConnectedWalletInfo(connector: GetAccountReturnType['connector']) { - if (!connector) { - throw Error('syncConnectedWalletInfo - connector is undefined'); - } - - if (connector.id === ConstantsUtil.WALLET_CONNECT_CONNECTOR_ID && connector.getProvider) { - const walletConnectProvider = (await connector.getProvider()) as Awaited< - ReturnType<(typeof EthereumProvider)['init']> - >; - if (walletConnectProvider.session) { - this.setConnectedWalletInfo({ - ...walletConnectProvider.session.peer.metadata, - name: walletConnectProvider.session.peer.metadata.name, - icon: walletConnectProvider.session.peer.metadata.icons?.[0] - }); - } - } else { - this.setConnectedWalletInfo({ - id: connector.id, - name: connector.name, - icon: this.options?.connectorImages?.[connector.id] ?? connector.icon - }); - } - } - - private syncConnectors(connectors: AppKitClientOptions['wagmiConfig']['connectors']) { - const uniqueIds = new Set(); - const filteredConnectors = connectors.filter( - item => !uniqueIds.has(item.id) && uniqueIds.add(item.id) - ); - - const excludedConnectors = [ConstantsUtil.AUTH_CONNECTOR_ID]; - - const _connectors: Connector[] = []; - filteredConnectors.forEach(({ id, name, icon }) => { - if (!excludedConnectors.includes(id)) { - _connectors.push({ - id, - explorerId: PresetsUtil.ConnectorExplorerIds[id], - imageId: PresetsUtil.ConnectorImageIds[id] ?? icon, - imageUrl: this.options?.connectorImages?.[id], - name: PresetsUtil.ConnectorNamesMap[id] ?? name, - type: PresetsUtil.ConnectorTypesMap[id] ?? 'EXTERNAL' - }); - } - }); - - this.setConnectors(_connectors); - this.syncWalletConnectListeners(filteredConnectors); - this.syncAuthConnector(filteredConnectors); - } - - private async syncWalletConnectListeners( - connectors: AppKitClientOptions['wagmiConfig']['connectors'] - ) { - const connector = connectors.find(({ id }) => id === ConstantsUtil.WALLET_CONNECT_CONNECTOR_ID); - if (connector) { - const provider = (await connector.getProvider()) as EthereumProvider; - - provider.signer.client.core.relayer.on('relayer_connect', () => { - provider.signer.client.core.relayer?.provider?.on('payload', (payload: JsonRpcError) => { - if (payload?.error) { - this.handleAlertError(payload?.error.message); - } - }); - }); - } - } - - private async syncAuthConnector(connectors: AppKitClientOptions['wagmiConfig']['connectors']) { - const authConnector = connectors.find(({ id }) => id === ConstantsUtil.AUTH_CONNECTOR_ID); - if (authConnector) { - const provider = await authConnector.getProvider(); - this.addConnector({ - id: ConstantsUtil.AUTH_CONNECTOR_ID, - type: PresetsUtil.ConnectorTypesMap[ConstantsUtil.AUTH_CONNECTOR_ID]!, - name: PresetsUtil.ConnectorNamesMap[ConstantsUtil.AUTH_CONNECTOR_ID], - provider - }); - this.addAuthListeners(authConnector); - } - } - - private async addAuthListeners(connector: WagmiConnector) { - const connectedConnector: ConnectorType | undefined = await StorageUtil.getItem( - '@w3m/connected_connector' - ); - - if (connectedConnector === 'AUTH') { - // Set loader until it reconnects - super.setLoading(true); - } - - const provider = (await connector.getProvider()) as AppKitFrameProvider; - - provider.onSetPreferredAccount(async () => { - await reconnect(this.wagmiConfig, { connectors: [connector] }); - }); - - provider.setOnTimeout(async () => { - this.handleAlertError(ErrorUtil.ALERT_ERRORS.SOCIALS_TIMEOUT); - this.setLoading(false); - }); - } -} diff --git a/packages/wagmi/src/connectors/UniversalConnector.ts b/packages/wagmi/src/connectors/UniversalConnector.ts new file mode 100644 index 00000000..d9e13c48 --- /dev/null +++ b/packages/wagmi/src/connectors/UniversalConnector.ts @@ -0,0 +1,228 @@ +import type { + Provider, + RequestArguments, + WalletConnector +} from '@reown/appkit-common-react-native'; +import { + getAddress, + numberToHex, + SwitchChainError, + UserRejectedRequestError, + type Chain, + type Hex +} from 'viem'; +import { ChainNotConfiguredError, createConnector, ProviderNotFoundError } from 'wagmi'; + +export function UniversalConnector(appKitProvidedConnector: WalletConnector) { + let provider: Provider | undefined; + + let accountsChangedHandler: ((accounts: string[]) => void) | undefined; + let chainChangedHandler: ((chainId: string | number) => void) | undefined; + let disconnectHandler: ((error?: Error) => void) | undefined; + + type AppKitConnectorProperties = { ready: boolean }; + + return createConnector(config => ({ + id: 'walletconnect', + name: 'WalletConnect', + type: 'walletconnect' as const, + ready: !!appKitProvidedConnector.getProvider(), + + async setup() { + provider = appKitProvidedConnector.getProvider(); + if (provider?.on) { + accountsChangedHandler = (accounts: string[]) => { + const hexAccounts = accounts.map(acc => getAddress(acc)); + config.emitter.emit('change', { accounts: hexAccounts }); + if (hexAccounts.length === 0) { + config.emitter.emit('disconnect'); + } + }; + chainChangedHandler = (chainId: string | number) => { + const newChainId = typeof chainId === 'string' ? parseInt(chainId, 10) : chainId; + config.emitter.emit('change', { chainId: newChainId }); + }; + disconnectHandler = (error?: Error) => { + config.emitter.emit('disconnect'); + if (error) config.emitter.emit('error', { error }); + }; + + if (accountsChangedHandler) provider.on('accountsChanged', accountsChangedHandler); + if (chainChangedHandler) provider.on('chainChanged', chainChangedHandler); + if (disconnectHandler) provider.on('disconnect', disconnectHandler); + if (disconnectHandler) provider.on('session_delete', disconnectHandler); + } + }, + + async connect({ chainId } = {}) { + try { + const _provider = await this.getProvider(); + if (!_provider) throw new ProviderNotFoundError(); + + // AppKit connector is already connected or handles its own connection. + // We just need to sync its state with Wagmi. + const accountAddresses = await this.getAccounts(); + if (!accountAddresses || accountAddresses.length === 0) { + throw new UserRejectedRequestError( + new Error('No accounts found or user rejected connection via AppKit.') + ); + } + + let currentChainId = await this.getChainId(); + + // Handle chain switching if requested and different + if (chainId && currentChainId !== chainId) { + await this.switchChain?.({ chainId }); + currentChainId = chainId; + } + + this.ready = true; + + return { accounts: accountAddresses, chainId: currentChainId }; + } catch (error) { + if (error instanceof UserRejectedRequestError) throw error; + throw new UserRejectedRequestError(error as Error); // Generalize other errors as user rejection for simplicity + } + }, + + async disconnect() { + await appKitProvidedConnector.disconnect(); + config.emitter.emit('message', { type: 'externalDisconnect' }); + if (provider?.off && accountsChangedHandler && chainChangedHandler && disconnectHandler) { + provider.off('accountsChanged', accountsChangedHandler); + provider.off('chainChanged', chainChangedHandler); + provider.off('disconnect', disconnectHandler); + provider.off('session_delete', disconnectHandler); + accountsChangedHandler = undefined; + chainChangedHandler = undefined; + disconnectHandler = undefined; + } + this.ready = false; + }, + + async getAccounts() { + const namespaces = appKitProvidedConnector.getNamespaces(); + // @ts-ignore + const eip155Accounts = namespaces?.eip155?.accounts; + if (!eip155Accounts) return [] as readonly Hex[]; + + return eip155Accounts + .map((caipAddr: string) => { + const parts = caipAddr.split(':'); + + return parts.length === 3 ? parts[2] : null; + }) + .filter((addrPart): addrPart is string => !!addrPart) + .map((addrPart: string) => getAddress(addrPart)) as readonly Hex[]; + }, + + async getChainId() { + const chainId = appKitProvidedConnector.getChainId('eip155')?.split(':')[1]; + + if (chainId) return parseInt(chainId, 10); + + // Fallback: Try to get from CAIP accounts if available + const namespaces = appKitProvidedConnector.getNamespaces(); + // @ts-ignore + const eip155Accounts = namespaces?.eip155?.accounts; + if (eip155Accounts && eip155Accounts.length > 0) { + const parts = eip155Accounts[0]?.split(':'); + if (parts && parts.length > 1 && typeof parts[1] === 'string') { + const chainIdNum = parseInt(parts[1], 10); + if (!isNaN(chainIdNum)) { + return chainIdNum; + } + } + } + if (config.chains && config.chains.length > 0) return config.chains[0].id; + throw new Error('Unable to determine chainId.'); + }, + + async getProvider() { + if (!provider) { + provider = appKitProvidedConnector.getProvider(); + } + + const chainId = await this.getChainId(); + + //TODO: Review this with gancho + const _provider = { + ...provider, + request: (args: RequestArguments) => { + return provider?.request(args, `eip155:${chainId}`); + } + }; + + return Promise.resolve(_provider as Provider); + }, + + async isAuthorized() { + try { + const accounts = await this.getAccounts(); + + return !!(accounts && accounts.length > 0); + } catch { + return false; + } + }, + + async switchChain({ chainId }) { + const _provider = await this.getProvider(); + if (!_provider) throw new Error('Provider not available for switching chain.'); + const newChain = config.chains.find(c => c.id === chainId) as Chain; + + if (!newChain) throw new SwitchChainError(new ChainNotConfiguredError()); + + try { + await _provider.request({ + method: 'wallet_switchEthereumChain', + params: [{ chainId: numberToHex(chainId) }] + }); + + config.emitter.emit('change', { chainId }); + + return newChain; + } catch (error) { + // Try to add chain if switch failed (common pattern) + //4902 in MetaMask: Unrecognized chain ID + if ((error as any)?.code === 4902 || (error as any)?.data?.originalError?.code === 4902) { + try { + await _provider.request({ + method: 'wallet_addEthereumChain', + params: [ + { + chainId: numberToHex(chainId), + chainName: newChain.name, + nativeCurrency: newChain.nativeCurrency, + rpcUrls: [newChain.rpcUrls.default?.http[0] ?? ''], // Take first default HTTP RPC URL + blockExplorerUrls: [newChain.blockExplorers?.default?.url] + } + ] + }); + await appKitProvidedConnector.switchNetwork(newChain); + config.emitter.emit('change', { chainId }); + + return newChain; + } catch (addError) { + throw new UserRejectedRequestError(addError as Error); + } + } + throw new SwitchChainError(error as Error); + } + }, + + onAccountsChanged(accounts: string[]) { + if (accounts.length === 0) this.onDisconnect(); + else config.emitter.emit('change', { accounts: accounts.map(x => getAddress(x)) }); + }, + + onChainChanged(chain: string) { + const chainId = Number(chain); + config.emitter.emit('change', { chainId }); + }, + + onDisconnect: () => { + config.emitter.emit('disconnect'); + } + })); +} diff --git a/packages/wagmi/src/connectors/WalletConnectConnector.ts b/packages/wagmi/src/connectors/WalletConnectConnector.ts deleted file mode 100644 index 4c82bbb4..00000000 --- a/packages/wagmi/src/connectors/WalletConnectConnector.ts +++ /dev/null @@ -1,469 +0,0 @@ -import { type EthereumProviderOptions } from '@walletconnect/ethereum-provider/dist/types/EthereumProvider'; -import { - type Address, - type ProviderConnectInfo, - ProviderRpcError, - SwitchChainError, - UserRejectedRequestError, - getAddress, - numberToHex, - RpcError, - type AddEthereumChainParameter -} from 'viem'; - -import { - ChainNotConfiguredError, - ProviderNotFoundError, - createConnector, - type Connector -} from '@wagmi/core'; - -import { EthereumProvider } from '@walletconnect/ethereum-provider'; - -/**** Types ****/ - -type WalletConnectConnector = Connector & { - onDisplayUri(uri: string): void; - onSessionDelete(data: { topic: string }): void; -}; - -export type WalletConnectParameters = { - /** - * Reown Cloud Project ID. - * @link https://cloud.reown.com/sign-in. - */ - projectId: EthereumProviderOptions['projectId']; - /** - * If a new chain is added to a previously existing configured connector `chains`, this flag - * will determine if that chain should be considered as stale. A stale chain is a chain that - * WalletConnect has yet to establish a relationship with (e.g. the user has not approved or - * rejected the chain). - * - * This flag mainly affects the behavior when a wallet does not support dynamic chain authorization - * with WalletConnect v2. - * - * If `true` (default), the new chain will be treated as a stale chain. If the user - * has yet to establish a relationship (approved/rejected) with this chain in their WalletConnect - * session, the connector will disconnect upon the dapp auto-connecting, and the user will have to - * reconnect to the dapp (revalidate the chain) in order to approve the newly added chain. - * This is the default behavior to avoid an unexpected error upon switching chains which may - * be a confusing user experience (e.g. the user will not know they have to reconnect - * unless the dapp handles these types of errors). - * - * If `false`, the new chain will be treated as a potentially valid chain. This means that if the user - * has yet to establish a relationship with the chain in their WalletConnect session, wagmi will successfully - * auto-connect the user. This comes with the trade-off that the connector will throw an error - * when attempting to switch to the unapproved chain if the wallet does not support dynamic session updates. - * This may be useful in cases where a dapp constantly - * modifies their configured chains, and they do not want to disconnect the user upon - * auto-connecting. If the user decides to switch to the unapproved chain, it is important that the - * dapp handles this error and prompts the user to reconnect to the dapp in order to approve - * the newly added chain. - * - * @default true - */ - isNewChainsStale?: boolean; - /** - * Metadata for your app. - * @link https://docs.reown.com/appkit/react-native/core/installation#implementation - */ - metadata: EthereumProviderOptions['metadata']; -} & Omit< - EthereumProviderOptions, - | 'chains' - | 'events' - | 'optionalChains' - | 'optionalEvents' - | 'optionalMethods' - | 'methods' - | 'rpcMap' - | 'showQrModal' - | 'qrModalOptions' - | 'storageOptions' ->; - -type Provider = Awaited>; - -type NamespaceMethods = 'wallet_addEthereumChain' | 'wallet_switchEthereumChain'; - -type Properties = { - connect(parameters?: { chainId?: number; pairingTopic?: string }): Promise<{ - accounts: readonly Address[]; - chainId: number; - }>; - getNamespaceChainsIds(): number[]; - getNamespaceMethods(): NamespaceMethods[]; - getRequestedChainsIds(): Promise; - isChainsStale(): Promise; - onConnect(connectInfo: ProviderConnectInfo): void; - onDisplayUri(uri: string): void; - onSessionDelete(data: { topic: string }): void; - setRequestedChainsIds(chains: number[]): void; - requestedChainsStorageKey: `${string}.requestedChains`; -}; - -type StorageItem = { - [_ in Properties['requestedChainsStorageKey']]: number[]; -}; - -walletConnect.type = 'walletConnect' as const; -export function walletConnect(parameters: WalletConnectParameters) { - const isNewChainsStale = parameters.isNewChainsStale ?? true; - - let provider_: Provider | undefined; - let providerPromise: Promise; - const NAMESPACE = 'eip155'; - - let accountsChanged: WalletConnectConnector['onAccountsChanged'] | undefined; - let chainChanged: WalletConnectConnector['onChainChanged'] | undefined; - let connect: WalletConnectConnector['onConnect'] | undefined; - let displayUri: WalletConnectConnector['onDisplayUri'] | undefined; - let sessionDelete: WalletConnectConnector['onSessionDelete'] | undefined; - let disconnect: WalletConnectConnector['onDisconnect'] | undefined; - - return createConnector(config => ({ - id: 'walletConnect', - name: 'WalletConnect', - type: walletConnect.type, - async setup() { - const provider = await this.getProvider().catch(() => null); - if (!provider) return; - if (!connect) { - connect = this.onConnect.bind(this); - provider.on('connect', connect); - } - if (!sessionDelete) { - sessionDelete = this.onSessionDelete.bind(this); - provider.on('session_delete', sessionDelete); - } - }, - async connect({ chainId, ...rest } = {}) { - try { - const provider = await this.getProvider(); - if (!provider) throw new ProviderNotFoundError(); - if (!displayUri) { - displayUri = this.onDisplayUri; - provider.on('display_uri', displayUri); - } - - let targetChainId = chainId; - if (!targetChainId) { - const state = (await config.storage?.getItem('state')) ?? {}; - const isChainSupported = config.chains.some(x => x.id === state.chainId); - if (isChainSupported) targetChainId = state.chainId; - else targetChainId = config.chains[0]?.id; - } - if (!targetChainId) throw new Error('No chains found on connector.'); - - const isChainsStale = await this.isChainsStale(); - // If there is an active session with stale chains, disconnect current session. - if (provider.session && isChainsStale) await provider.disconnect(); - - // If there isn't an active session or chains are stale, connect. - if (!provider.session || isChainsStale) { - const optionalChains = config.chains - .filter(chain => chain.id !== targetChainId) - .map(optionalChain => optionalChain.id); - await provider.connect({ - optionalChains: [targetChainId, ...optionalChains], - ...('pairingTopic' in rest ? { pairingTopic: rest.pairingTopic } : {}) - }); - - this.setRequestedChainsIds(config.chains.map(x => x.id)); - } - - // If session exists and chains are authorized, enable provider for required chain - const accounts: Address[] = (await provider.enable()).map(getAddress); - const currentChainId = await this.getChainId(); - - if (displayUri) { - provider.removeListener('display_uri', displayUri); - displayUri = undefined; - } - if (connect) { - provider.removeListener('connect', connect); - connect = undefined; - } - if (!accountsChanged) { - accountsChanged = this.onAccountsChanged.bind(this); - provider.on('accountsChanged', accountsChanged); - } - if (!chainChanged) { - chainChanged = this.onChainChanged.bind(this); - provider.on('chainChanged', chainChanged); - } - if (!disconnect) { - disconnect = this.onDisconnect.bind(this); - provider.on('disconnect', disconnect); - } - if (!sessionDelete) { - sessionDelete = this.onSessionDelete.bind(this); - provider.on('session_delete', sessionDelete); - } - - return { accounts, chainId: currentChainId }; - } catch (error) { - if ( - /(user rejected|connection request reset)/i.test((error as ProviderRpcError)?.message) - ) { - throw new UserRejectedRequestError(error as Error); - } - throw error; - } - }, - async disconnect() { - const provider = await this.getProvider(); - try { - await provider?.disconnect(); - } catch (error) { - if (!/No matching key/i.test((error as Error).message)) throw error; - } finally { - if (chainChanged) { - provider?.removeListener('chainChanged', chainChanged); - chainChanged = undefined; - } - if (disconnect) { - provider?.removeListener('disconnect', disconnect); - disconnect = undefined; - } - if (!connect) { - connect = this.onConnect.bind(this); - provider?.on('connect', connect); - } - if (accountsChanged) { - provider?.removeListener('accountsChanged', accountsChanged); - accountsChanged = undefined; - } - if (sessionDelete) { - provider?.removeListener('session_delete', sessionDelete); - sessionDelete = undefined; - } - - this.setRequestedChainsIds([]); - } - }, - async getAccounts() { - const provider: Provider = await this.getProvider(); - - return provider.accounts.map(getAddress); - }, - async getProvider({ chainId } = {}) { - async function initProvider() { - const optionalChains = config.chains.map(x => x.id) as [number]; - if (!optionalChains.length) return Promise.resolve(undefined); - - const { projectId, metadata, ...params } = parameters; - - return await EthereumProvider.init({ - optionalChains, - projectId, - rpcMap: Object.fromEntries( - config.chains.map(chain => [chain.id, chain.rpcUrls.default.http[0]!]) - ), - showQrModal: false, - qrModalOptions: undefined, - disableProviderPing: true, - metadata, - ...params - }); - } - if (!provider_) { - if (!providerPromise) providerPromise = initProvider(); - provider_ = await providerPromise; - provider_?.events.setMaxListeners(Number.POSITIVE_INFINITY); - } - if (chainId) await this.switchChain?.({ chainId }); - - return provider_!; - }, - async getChainId() { - const provider = await this.getProvider(); - - return provider.chainId; - }, - async isAuthorized() { - try { - const [accounts, provider] = await Promise.all([this.getAccounts(), this.getProvider()]); - - // If an account does not exist on the session, then the connector is unauthorized. - if (!accounts.length) return false; - - // If the chains are stale on the session, then the connector is unauthorized. - const isChainsStale = await this.isChainsStale(); - if (isChainsStale && provider.session) { - await provider.disconnect().catch(() => {}); - - return false; - } - - return true; - } catch { - return false; - } - }, - async switchChain({ addEthereumChainParameter, chainId }) { - const provider = await this.getProvider(); - if (!provider) throw new ProviderNotFoundError(); - - const chain = config.chains.find(c => c.id === chainId); - if (!chain) throw new SwitchChainError(new ChainNotConfiguredError()); - - try { - await Promise.all([ - new Promise(resolve => { - const listener = ({ chainId: currentChainId }: { chainId?: number }) => { - if (currentChainId === chainId) { - config.emitter.off('change', listener); - resolve(); - } - }; - config.emitter.on('change', listener); - }), - provider.request({ - method: 'wallet_switchEthereumChain', - params: [{ chainId: numberToHex(chainId) }] - }) - ]); - - const requestedChains = await this.getRequestedChainsIds(); - if (!requestedChains.includes(chainId)) { - this.setRequestedChainsIds([...requestedChains, chainId]); - } - - return chain; - } catch (err) { - const error = err as RpcError; - - if (/(user rejected)/i.test(error.message)) throw new UserRejectedRequestError(error); - - // Indicates chain is not added to provider - try { - let blockExplorerUrls: string[] | undefined; - if (addEthereumChainParameter?.blockExplorerUrls) - blockExplorerUrls = addEthereumChainParameter.blockExplorerUrls; - else - blockExplorerUrls = chain.blockExplorers?.default.url - ? [chain.blockExplorers?.default.url] - : []; - - let rpcUrls: readonly string[]; - if (addEthereumChainParameter?.rpcUrls?.length) - rpcUrls = addEthereumChainParameter.rpcUrls; - else rpcUrls = [...chain.rpcUrls.default.http]; - - const addEthereumChain = { - blockExplorerUrls, - chainId: numberToHex(chainId), - chainName: addEthereumChainParameter?.chainName ?? chain.name, - iconUrls: addEthereumChainParameter?.iconUrls, - nativeCurrency: addEthereumChainParameter?.nativeCurrency ?? chain.nativeCurrency, - rpcUrls - } satisfies AddEthereumChainParameter; - - await provider.request({ - method: 'wallet_addEthereumChain', - params: [addEthereumChain] - }); - - const requestedChains = await this.getRequestedChainsIds(); - this.setRequestedChainsIds([...requestedChains, chainId]); - - return chain; - } catch (e) { - throw new UserRejectedRequestError(e as Error); - } - } - }, - onAccountsChanged(accounts) { - if (accounts.length === 0) this.onDisconnect(); - else config.emitter.emit('change', { accounts: accounts.map(x => getAddress(x)) }); - }, - onChainChanged(chain) { - const chainId = Number(chain); - config.emitter.emit('change', { chainId }); - }, - async onConnect(connectInfo) { - const chainId = Number(connectInfo.chainId); - const accounts = await this.getAccounts(); - config.emitter.emit('connect', { accounts, chainId }); - }, - async onDisconnect(_error) { - this.setRequestedChainsIds([]); - config.emitter.emit('disconnect'); - - const provider = await this.getProvider(); - if (accountsChanged) { - provider.removeListener('accountsChanged', accountsChanged); - accountsChanged = undefined; - } - if (chainChanged) { - provider.removeListener('chainChanged', chainChanged); - chainChanged = undefined; - } - if (disconnect) { - provider.removeListener('disconnect', disconnect); - disconnect = undefined; - } - if (sessionDelete) { - provider.removeListener('session_delete', sessionDelete); - sessionDelete = undefined; - } - if (!connect) { - connect = this.onConnect.bind(this); - provider.on('connect', connect); - } - }, - onDisplayUri(uri) { - config.emitter.emit('message', { type: 'display_uri', data: uri }); - }, - onSessionDelete() { - this.onDisconnect(); - }, - getNamespaceChainsIds() { - if (!provider_) return []; - const chainIds = provider_.session?.namespaces[NAMESPACE]?.accounts?.map(account => - parseInt(account.split(':')[1] || '') - ); - - return chainIds ?? []; - }, - getNamespaceMethods() { - if (!provider_) return []; - const methods = provider_.session?.namespaces[NAMESPACE]?.methods as NamespaceMethods[]; - - return methods ?? []; - }, - async getRequestedChainsIds() { - return (await config.storage?.getItem(this.requestedChainsStorageKey)) ?? []; - }, - /** - * Checks if the target chains match the chains that were - * initially requested by the connector for the WalletConnect session. - * If there is a mismatch, this means that the chains on the connector - * are considered stale, and need to be revalidated at a later point (via - * connection). - * - * There may be a scenario where a dapp adds a chain to the - * connector later on, however, this chain will not have been approved or rejected - * by the wallet. In this case, the chain is considered stale. - */ - async isChainsStale() { - if (!isNewChainsStale) return false; - - const connectorChains = config.chains.map(x => x.id); - const namespaceChains = this.getNamespaceChainsIds(); - if (namespaceChains.length && !namespaceChains.some(id => connectorChains.includes(id))) - return false; - - const requestedChains = await this.getRequestedChainsIds(); - - return !connectorChains.every(id => requestedChains.includes(id)); - }, - async setRequestedChainsIds(chains) { - await config.storage?.setItem(this.requestedChainsStorageKey, chains); - }, - get requestedChainsStorageKey() { - return `${this.id}.requestedChains` as Properties['requestedChainsStorageKey']; - } - })); -} diff --git a/packages/wagmi/src/index.tsx b/packages/wagmi/src/index.tsx index 51872665..b2f47a68 100644 --- a/packages/wagmi/src/index.tsx +++ b/packages/wagmi/src/index.tsx @@ -1,123 +1,3 @@ -import '@walletconnect/react-native-compat'; -import { useEffect, useState, useSyncExternalStore } from 'react'; -export { - AccountButton, - AppKitButton, - ConnectButton, - NetworkButton, - AppKit -} from '@reown/appkit-scaffold-react-native'; -import type { EventName, EventsControllerState } from '@reown/appkit-scaffold-react-native'; -import { ConstantsUtil } from '@reown/appkit-common-react-native'; +import { WagmiAdapter } from './adapter'; -export { defaultWagmiConfig } from './utils/defaultWagmiConfig'; -import type { AppKitOptions } from './client'; -import { AppKit } from './client'; - -// -- Types ------------------------------------------------------------------- -export type { AppKitOptions } from './client'; - -type OpenOptions = Parameters[0]; - -// -- Setup ------------------------------------------------------------------- -let modal: AppKit | undefined; - -export function createAppKit(options: AppKitOptions) { - if (!modal) { - modal = new AppKit({ - ...options, - _sdkVersion: `react-native-wagmi-${ConstantsUtil.VERSION}` - }); - } - - return modal; -} - -// -- Hooks ------------------------------------------------------------------- -export function useAppKit() { - if (!modal) { - throw new Error('Please call "createAppKit" before using "useAppKit" hook'); - } - - async function open(options?: OpenOptions) { - await modal?.open(options); - } - - async function close() { - await modal?.close(); - } - - return { open, close }; -} - -export function useAppKitState() { - if (!modal) { - throw new Error('Please call "createAppKit" before using "useAppKitState" hook'); - } - - const [state, setState] = useState(modal.getState()); - - useEffect(() => { - const unsubscribe = modal?.subscribeState(newState => { - if (newState) setState({ ...newState }); - }); - - return () => { - unsubscribe?.(); - }; - }, []); - - return state; -} - -export function useWalletInfo() { - if (!modal) { - throw new Error('Please call "createAppKit" before using "useWalletInfo" hook'); - } - - const walletInfo = useSyncExternalStore( - modal.subscribeWalletInfo, - modal.getWalletInfo, - modal.getWalletInfo - ); - - return { walletInfo }; -} - -export function useAppKitEvents(callback?: (newEvent: EventsControllerState) => void) { - if (!modal) { - throw new Error('Please call "createAppKit" before using "useAppKitEvents" hook'); - } - - const [event, setEvents] = useState(modal.getEvent()); - - useEffect(() => { - const unsubscribe = modal?.subscribeEvents(newEvent => { - setEvents({ ...newEvent }); - callback?.(newEvent); - }); - - return () => { - unsubscribe?.(); - }; - }, [callback]); - - return event; -} - -export function useAppKitEventSubscription( - event: EventName, - callback: (newEvent: EventsControllerState) => void -) { - if (!modal) { - throw new Error('Please call "createAppKit" before using "useAppKitEventSubscription" hook'); - } - - useEffect(() => { - const unsubscribe = modal?.subscribeEvent(event, callback); - - return () => { - unsubscribe?.(); - }; - }, [callback, event]); -} +export { WagmiAdapter }; diff --git a/packages/wagmi/src/utils/defaultWagmiConfig.ts b/packages/wagmi/src/utils/defaultWagmiConfig.ts deleted file mode 100644 index fc3a06c2..00000000 --- a/packages/wagmi/src/utils/defaultWagmiConfig.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { - createConfig, - createStorage, - type CreateConnectorFn, - type CreateConfigParameters -} from 'wagmi'; -import type { EthereumProviderOptions } from '@walletconnect/ethereum-provider/dist/types/EthereumProvider'; -import { StorageUtil } from '@reown/appkit-scaffold-utils-react-native'; - -import { walletConnect } from '../connectors/WalletConnectConnector'; -import { getTransport } from './helpers'; - -export type ConfigOptions = Partial & { - projectId: string; - metadata: Exclude; - chains: CreateConfigParameters['chains']; - enableWalletConnect?: boolean; - extraConnectors?: CreateConnectorFn[]; -}; - -export function defaultWagmiConfig({ - projectId, - chains, - metadata, - enableWalletConnect = true, - extraConnectors, - ...wagmiConfig -}: ConfigOptions) { - const connectors: CreateConnectorFn[] = []; - const transportsArr = chains.map(chain => [ - chain.id, - getTransport({ chainId: chain.id, projectId }) - ]); - const transports = Object.fromEntries(transportsArr); - const storage = createStorage({ storage: StorageUtil }); - - if (enableWalletConnect) { - connectors.push(walletConnect({ projectId, metadata })); - } - - if (extraConnectors) { - connectors.push(...extraConnectors); - } - - return createConfig({ - chains, - connectors, - transports, - storage, - multiInjectedProviderDiscovery: false, - ...wagmiConfig - }); -} diff --git a/packages/wagmi/src/utils/helpers.ts b/packages/wagmi/src/utils/helpers.ts index 78a325b9..2b1efa49 100644 --- a/packages/wagmi/src/utils/helpers.ts +++ b/packages/wagmi/src/utils/helpers.ts @@ -1,51 +1,6 @@ -import { - CoreHelperUtil, - type CaipNetwork, - type CaipNetworkId -} from '@reown/appkit-scaffold-react-native'; +import { CoreHelperUtil } from '@reown/appkit-react-native'; import { PresetsUtil, ConstantsUtil } from '@reown/appkit-common-react-native'; -import type { Connector } from '@wagmi/core'; -import { EthereumProvider } from '@walletconnect/ethereum-provider'; -import type { AppKitClientOptions } from '../client'; -import { http, type Hex } from 'viem'; - -export function getCaipDefaultChain(chain?: AppKitClientOptions['defaultChain']) { - if (!chain) { - return undefined; - } - - return { - id: `${ConstantsUtil.EIP155}:${chain.id}`, - name: chain.name, - imageId: PresetsUtil.EIP155NetworkImageIds[chain.id] - } as CaipNetwork; -} - -export async function getWalletConnectCaipNetworks(connector?: Connector) { - if (!connector) { - throw new Error('networkControllerClient:getApprovedCaipNetworks - connector is undefined'); - } - const provider = (await connector?.getProvider()) as Awaited< - ReturnType<(typeof EthereumProvider)['init']> - >; - const ns = provider?.signer?.session?.namespaces; - const nsMethods = ns?.[ConstantsUtil.EIP155]?.methods; - const nsChains = ns?.[ConstantsUtil.EIP155]?.chains as CaipNetworkId[]; - - return { - supportsAllNetworks: Boolean(nsMethods?.includes(ConstantsUtil.ADD_CHAIN_METHOD)), - approvedCaipNetworkIds: nsChains - }; -} - -export function getAuthCaipNetworks() { - return { - supportsAllNetworks: false, - approvedCaipNetworkIds: PresetsUtil.RpcChainIds.map( - id => `${ConstantsUtil.EIP155}:${id}` - ) as CaipNetworkId[] - }; -} +import { http } from 'viem'; export function getTransport({ chainId, projectId }: { chainId: number; projectId: string }) { const RPC_URL = CoreHelperUtil.getBlockchainApiUrl(); @@ -56,15 +11,3 @@ export function getTransport({ chainId, projectId }: { chainId: number; projectI return http(`${RPC_URL}/v1/?chainId=${ConstantsUtil.EIP155}:${chainId}&projectId=${projectId}`); } - -export function requireCaipAddress(caipAddress: string) { - if (!caipAddress) { - throw new Error('No CAIP address provided'); - } - const account = caipAddress.split(':')[2] as Hex; - if (!account) { - throw new Error('Invalid CAIP address'); - } - - return account; -} diff --git a/tsconfig.json b/tsconfig.json index d7ff00e5..3ab69898 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -7,7 +7,7 @@ "isolatedModules": true, "forceConsistentCasingInFileNames": true, "jsx": "react-jsx", - "lib": ["esnext"], + "lib": ["esnext", "dom"], "module": "esnext", "moduleResolution": "node", "noFallthroughCasesInSwitch": true, diff --git a/yarn.lock b/yarn.lock index 3c48d4a9..b6170264 100644 --- a/yarn.lock +++ b/yarn.lock @@ -38,6 +38,13 @@ __metadata: languageName: node linkType: hard +"@adraffy/ens-normalize@npm:1.10.1": + version: 1.10.1 + resolution: "@adraffy/ens-normalize@npm:1.10.1" + checksum: fdd647604e8fac6204921888aaf5a6bc65eabf0d2921bc5f93b64d01f4bc33ead167c1445f7de05468d05cd92ac31b74c68d2be840c62b79d73693308f885c06 + languageName: node + linkType: hard + "@adraffy/ens-normalize@npm:^1.10.1": version: 1.11.0 resolution: "@adraffy/ens-normalize@npm:1.11.0" @@ -95,11 +102,16 @@ __metadata: resolution: "@apps/native@workspace:apps/native" dependencies: "@babel/core": "npm:^7.24.0" + "@bitcoinerlab/secp256k1": "npm:1.2.0" "@expo/metro-runtime": "npm:~4.0.1" "@playwright/test": "npm:^1.49.1" "@react-native-async-storage/async-storage": "npm:2.1.2" "@react-native-community/netinfo": "npm:11.4.1" "@reown/appkit-auth-wagmi-react-native": "npm:1.2.3" + "@reown/appkit-bitcoin-react-native": "npm:1.2.3" + "@reown/appkit-ethers-react-native": "npm:1.2.3" + "@reown/appkit-react-native": "npm:1.2.3" + "@reown/appkit-solana-react-native": "npm:1.2.3" "@reown/appkit-wagmi-react-native": "npm:1.2.3" "@tanstack/query-async-storage-persister": "npm:^5.40.0" "@tanstack/react-query": "npm:5.56.2" @@ -107,8 +119,10 @@ __metadata: "@types/gh-pages": "npm:^6" "@types/node": "npm:^22.10.1" "@types/react": "npm:~18.2.79" - "@walletconnect/react-native-compat": "npm:2.19.1" + "@walletconnect/react-native-compat": "npm:2.20.2" babel-plugin-module-resolver: "npm:^5.0.0" + bitcoinjs-lib: "npm:7.0.0-rc.0" + ethers: "npm:6.13.5" expo: "npm:^52.0.38" expo-application: "npm:~6.0.2" expo-clipboard: "npm:~7.0.1" @@ -126,8 +140,8 @@ __metadata: react-native-webview: "npm:13.12.5" typescript: "npm:~5.3.3" uuid: "npm:^11.1.0" - viem: "npm:2.23.10" - wagmi: "npm:2.14.13" + viem: "npm:2.28.3" + wagmi: "npm:2.15.1" languageName: unknown linkType: soft @@ -3889,6 +3903,15 @@ __metadata: languageName: node linkType: hard +"@bitcoinerlab/secp256k1@npm:1.2.0": + version: 1.2.0 + resolution: "@bitcoinerlab/secp256k1@npm:1.2.0" + dependencies: + "@noble/curves": "npm:^1.7.0" + checksum: ab5196e6052b60cbfee347434105dee59ecd93cb73473706252d35581b63dec2f4241b9e5ce7d5bbb062fb3fa9898a78660c886be8ae2d375480080c30a3a4b3 + languageName: node + linkType: hard + "@changesets/apply-release-plan@npm:^7.0.4": version: 7.0.4 resolution: "@changesets/apply-release-plan@npm:7.0.4" @@ -5751,19 +5774,19 @@ __metadata: languageName: node linkType: hard -"@lit-labs/ssr-dom-shim@npm:^1.0.0, @lit-labs/ssr-dom-shim@npm:^1.1.0": - version: 1.1.1 - resolution: "@lit-labs/ssr-dom-shim@npm:1.1.1" - checksum: bc530a6d390a71e44a74f0d79ab78df0c3cf814f5a69e64c60271d626f4b871d0269c82f2b1bcaf9ef1a84f361f50a1fc70c790873cded769e8f0e4f1fa01ff8 +"@lit-labs/ssr-dom-shim@npm:^1.2.0": + version: 1.3.0 + resolution: "@lit-labs/ssr-dom-shim@npm:1.3.0" + checksum: 743a9b295ef2f186712f08883da553c9990be291409615309c99aa4946cfe440a184e4213c790c24505c80beb86b9cfecf10b5fb30ce17c83698f8424f48678d languageName: node linkType: hard -"@lit/reactive-element@npm:^1.3.0, @lit/reactive-element@npm:^1.6.0": - version: 1.6.3 - resolution: "@lit/reactive-element@npm:1.6.3" +"@lit/reactive-element@npm:^2.0.0, @lit/reactive-element@npm:^2.1.0": + version: 2.1.0 + resolution: "@lit/reactive-element@npm:2.1.0" dependencies: - "@lit-labs/ssr-dom-shim": "npm:^1.0.0" - checksum: 10f1d25e24e32feb21c4c6f9e11d062901241602e12c4ecf746b3138f87fed4d8394194645514d5c1bfd5f33f3fd56ee8ef41344e2cb4413c40fe4961ec9d419 + "@lit-labs/ssr-dom-shim": "npm:^1.2.0" + checksum: 3cd61c4e7cc8effeb2c246d5dada8fbe0a730e9e0dd488eb38c91a4f63b773e3b7f86f8384051677298e73de470c7ca6b5634df3ca190b307f8bb8e0d51bb91c languageName: node linkType: hard @@ -6031,91 +6054,6 @@ __metadata: languageName: node linkType: hard -"@motionone/animation@npm:^10.15.1, @motionone/animation@npm:^10.16.3": - version: 10.16.3 - resolution: "@motionone/animation@npm:10.16.3" - dependencies: - "@motionone/easing": "npm:^10.16.3" - "@motionone/types": "npm:^10.16.3" - "@motionone/utils": "npm:^10.16.3" - tslib: "npm:^2.3.1" - checksum: c1bb7a03acc9c09647321a4653bf53878ea05ce91305507cb4000d75641dcad85faa8696ef12d0c28fa52d4b3708bc7ae34334c95ef532567a26082f0176ea4a - languageName: node - linkType: hard - -"@motionone/dom@npm:^10.16.2, @motionone/dom@npm:^10.16.4": - version: 10.16.4 - resolution: "@motionone/dom@npm:10.16.4" - dependencies: - "@motionone/animation": "npm:^10.16.3" - "@motionone/generators": "npm:^10.16.4" - "@motionone/types": "npm:^10.16.3" - "@motionone/utils": "npm:^10.16.3" - hey-listen: "npm:^1.0.8" - tslib: "npm:^2.3.1" - checksum: 1efaa29a18471c18dbe7f849a7c83b12c27edf85209cb366856720e051870302c27567f5eab2a1aef3aa7ae1438c6fbc3a7e686077f5ed4e173e4cca8d22e0d5 - languageName: node - linkType: hard - -"@motionone/easing@npm:^10.16.3": - version: 10.16.3 - resolution: "@motionone/easing@npm:10.16.3" - dependencies: - "@motionone/utils": "npm:^10.16.3" - tslib: "npm:^2.3.1" - checksum: df98a643f0b2955afd16b78063899d050b22cfcf3db1bb86ecdbde831614f24c41143d5d887bc287f6de979baa20a00e8e1dca39ef7b2dfb67c0ec1b1ca0bcaa - languageName: node - linkType: hard - -"@motionone/generators@npm:^10.16.4": - version: 10.16.4 - resolution: "@motionone/generators@npm:10.16.4" - dependencies: - "@motionone/types": "npm:^10.16.3" - "@motionone/utils": "npm:^10.16.3" - tslib: "npm:^2.3.1" - checksum: cef71d1236a625b3579791d480ebd1875bec2a62e249771eb2af883981074016cc6f2ef112c2bf27f93d05d19830893f3f486944cd68d2fbf35a990c41729152 - languageName: node - linkType: hard - -"@motionone/svelte@npm:^10.16.2": - version: 10.16.4 - resolution: "@motionone/svelte@npm:10.16.4" - dependencies: - "@motionone/dom": "npm:^10.16.4" - tslib: "npm:^2.3.1" - checksum: a3f91d3ac5617ac8a2847abc0c8fad417cdc2cd9d814d60f7de2c909e4beeaf834b45a4288c8af6d26f62958a6c69714313b37ea6cd5aa2a9d1ad5198ec5881f - languageName: node - linkType: hard - -"@motionone/types@npm:^10.15.1, @motionone/types@npm:^10.16.3": - version: 10.16.3 - resolution: "@motionone/types@npm:10.16.3" - checksum: a792acd8bacd7949c29fd47fda1d3d7919b86ab209499a374a1f3c85f57a92d16f7a05f94edc6d46831c55180da2ff5e1193fa538bcb76e0ff38a24e25da2e87 - languageName: node - linkType: hard - -"@motionone/utils@npm:^10.15.1, @motionone/utils@npm:^10.16.3": - version: 10.16.3 - resolution: "@motionone/utils@npm:10.16.3" - dependencies: - "@motionone/types": "npm:^10.16.3" - hey-listen: "npm:^1.0.8" - tslib: "npm:^2.3.1" - checksum: c5a1cce9bf5d1e8c5051a4636bd6a7030bf67f5662a94a8ec1524a72de3baca3f4c59e46cee9a41b111806fdd2956256c65c7e99b7de260803f2e44840bbae11 - languageName: node - linkType: hard - -"@motionone/vue@npm:^10.16.2": - version: 10.16.4 - resolution: "@motionone/vue@npm:10.16.4" - dependencies: - "@motionone/dom": "npm:^10.16.4" - tslib: "npm:^2.3.1" - checksum: 0f3096c0956848cb67c4926e65b7034d854cf704573a277679713c5a8045347c3c043f50adad0c84ee3e88c046d35ab88ec4380e5acd729f81900381e0b1fd0d - languageName: node - linkType: hard - "@nicolo-ribaudo/eslint-scope-5-internals@npm:5.1.1-v1": version: 5.1.1-v1 resolution: "@nicolo-ribaudo/eslint-scope-5-internals@npm:5.1.1-v1" @@ -6184,6 +6122,15 @@ __metadata: languageName: node linkType: hard +"@noble/curves@npm:1.8.2": + version: 1.8.2 + resolution: "@noble/curves@npm:1.8.2" + dependencies: + "@noble/hashes": "npm:1.7.2" + checksum: e7ef119b114681d6b7530b29a21f9bbea6fa6973bc369167da2158d05054cc6e6dbfb636ba89fad7707abacc150de30188b33192f94513911b24bdb87af50bbd + languageName: node + linkType: hard + "@noble/curves@npm:^1.4.0, @noble/curves@npm:~1.4.0": version: 1.4.2 resolution: "@noble/curves@npm:1.4.2" @@ -6202,6 +6149,15 @@ __metadata: languageName: node linkType: hard +"@noble/curves@npm:^1.7.0": + version: 1.9.0 + resolution: "@noble/curves@npm:1.9.0" + dependencies: + "@noble/hashes": "npm:1.8.0" + checksum: a76d57444b4d136f43363eb19229d990df15a00fb0e2efbf08a7a4cbaee655f73e46eb29b6ad07b8749be5f7b890c0a7a06a19f4324a4b149b06b3da1def8593 + languageName: node + linkType: hard + "@noble/hashes@npm:1.3.1": version: 1.3.1 resolution: "@noble/hashes@npm:1.3.1" @@ -6244,6 +6200,20 @@ __metadata: languageName: node linkType: hard +"@noble/hashes@npm:1.7.2": + version: 1.7.2 + resolution: "@noble/hashes@npm:1.7.2" + checksum: b1411eab3c0b6691d847e9394fe7f1fcd45eeb037547c8f97e7d03c5068a499b4aef188e8e717eee67389dca4fee17d69d7e0f58af6c092567b0b76359b114b2 + languageName: node + linkType: hard + +"@noble/hashes@npm:1.8.0, @noble/hashes@npm:^1.2.0": + version: 1.8.0 + resolution: "@noble/hashes@npm:1.8.0" + checksum: 06a0b52c81a6fa7f04d67762e08b2c476a00285858150caeaaff4037356dd5e119f45b2a530f638b77a5eeca013168ec1b655db41bae3236cb2e9d511484fc77 + languageName: node + linkType: hard + "@noble/hashes@npm:~1.3.1": version: 1.3.3 resolution: "@noble/hashes@npm:1.3.3" @@ -7152,6 +7122,14 @@ __metadata: languageName: unknown linkType: soft +"@reown/appkit-bitcoin-react-native@npm:1.2.3, @reown/appkit-bitcoin-react-native@workspace:packages/bitcoin": + version: 0.0.0-use.local + resolution: "@reown/appkit-bitcoin-react-native@workspace:packages/bitcoin" + dependencies: + "@reown/appkit-common-react-native": "npm:1.2.3" + languageName: unknown + linkType: soft + "@reown/appkit-coinbase-ethers-react-native@workspace:packages/coinbase-ethers": version: 0.0.0-use.local resolution: "@reown/appkit-coinbase-ethers-react-native@workspace:packages/coinbase-ethers" @@ -7183,6 +7161,30 @@ __metadata: languageName: unknown linkType: soft +"@reown/appkit-common@npm:1.7.3": + version: 1.7.3 + resolution: "@reown/appkit-common@npm:1.7.3" + dependencies: + big.js: "npm:6.2.2" + dayjs: "npm:1.11.13" + viem: "npm:>=2.23.11" + checksum: c938dffc42494daa0e970a22c7b5282da378c08e585d0d2e5c774faa59143f881e24deb0dcc0eb933b3d7b057edf39ef804c5c08439147ff952b1508735bc638 + languageName: node + linkType: hard + +"@reown/appkit-controllers@npm:1.7.3": + version: 1.7.3 + resolution: "@reown/appkit-controllers@npm:1.7.3" + dependencies: + "@reown/appkit-common": "npm:1.7.3" + "@reown/appkit-wallet": "npm:1.7.3" + "@walletconnect/universal-provider": "npm:2.19.2" + valtio: "npm:1.13.2" + viem: "npm:>=2.23.11" + checksum: 775c25f7697a0ff59720cfd17c7317ac87284416d2b5e187bd05fba42f6f1294d6cab45c0509fb6faec9477ee60ce3b7cd72b78ae6c393df14fabd1874858650 + languageName: node + linkType: hard + "@reown/appkit-core-react-native@npm:1.2.3, @reown/appkit-core-react-native@workspace:packages/core": version: 0.0.0-use.local resolution: "@reown/appkit-core-react-native@workspace:packages/core" @@ -7198,21 +7200,19 @@ __metadata: languageName: unknown linkType: soft -"@reown/appkit-ethers-react-native@workspace:packages/ethers": +"@reown/appkit-ethers-react-native@npm:1.2.3, @reown/appkit-ethers-react-native@workspace:packages/ethers": version: 0.0.0-use.local resolution: "@reown/appkit-ethers-react-native@workspace:packages/ethers" dependencies: "@reown/appkit-common-react-native": "npm:1.2.3" - "@reown/appkit-scaffold-react-native": "npm:1.2.3" + "@reown/appkit-react-native": "npm:1.2.3" "@reown/appkit-scaffold-utils-react-native": "npm:1.2.3" "@reown/appkit-siwe-react-native": "npm:1.2.3" - "@walletconnect/ethereum-provider": "npm:2.17.3" - ethers: "npm:6.10.0" + "@walletconnect/ethereum-provider": "npm:2.20.2" peerDependencies: "@react-native-async-storage/async-storage": ">=1.17.0" "@react-native-community/netinfo": "*" "@walletconnect/react-native-compat": ">=2.13.1" - ethers: ">=6.0.0" react: ">=17" react-native: ">=0.68.5" react-native-get-random-values: "*" @@ -7227,7 +7227,7 @@ __metadata: "@reown/appkit-scaffold-react-native": "npm:1.2.3" "@reown/appkit-scaffold-utils-react-native": "npm:1.2.3" "@reown/appkit-siwe-react-native": "npm:1.2.3" - "@walletconnect/ethereum-provider": "npm:2.17.3" + "@walletconnect/ethereum-provider": "npm:2.20.2" ethers: "npm:5.7.2" peerDependencies: "@react-native-async-storage/async-storage": ">=1.17.0" @@ -7240,14 +7240,25 @@ __metadata: languageName: unknown linkType: soft -"@reown/appkit-scaffold-react-native@npm:1.2.3, @reown/appkit-scaffold-react-native@workspace:packages/scaffold": +"@reown/appkit-polyfills@npm:1.7.3": + version: 1.7.3 + resolution: "@reown/appkit-polyfills@npm:1.7.3" + dependencies: + buffer: "npm:6.0.3" + checksum: c2f347ba0dbfc435ca05e53abcc38ec0114478fe6aaaf198a58bf0a938eb44fd18ba4ed5c955f76fa79df7d4e1a15276afc7864573dd2b9d251f58bc33567e6d + languageName: node + linkType: hard + +"@reown/appkit-react-native@npm:1.2.3, @reown/appkit-react-native@workspace:packages/appkit": version: 0.0.0-use.local - resolution: "@reown/appkit-scaffold-react-native@workspace:packages/scaffold" + resolution: "@reown/appkit-react-native@workspace:packages/appkit" dependencies: "@reown/appkit-common-react-native": "npm:1.2.3" "@reown/appkit-core-react-native": "npm:1.2.3" "@reown/appkit-siwe-react-native": "npm:1.2.3" "@reown/appkit-ui-react-native": "npm:1.2.3" + "@walletconnect/universal-provider": "npm:2.20.2" + valtio: "npm:^1.13.2" peerDependencies: react: ">=17" react-native: ">=0.68.5" @@ -7255,12 +7266,42 @@ __metadata: languageName: unknown linkType: soft +"@reown/appkit-scaffold-react-native@npm:1.2.3": + version: 1.2.3 + resolution: "@reown/appkit-scaffold-react-native@npm:1.2.3" + dependencies: + "@reown/appkit-common-react-native": "npm:1.2.3" + "@reown/appkit-core-react-native": "npm:1.2.3" + "@reown/appkit-siwe-react-native": "npm:1.2.3" + "@reown/appkit-ui-react-native": "npm:1.2.3" + peerDependencies: + react: ">=17" + react-native: ">=0.68.5" + react-native-modal: ">=13" + checksum: a8cd99392bc1b2afa69adf904d9b970bbf707b1aea41d147ff63d884e49a77da3cb6482ffde9f61244196d884c906d99c9c90a164af3146c4e68ad5f4f1bd730 + languageName: node + linkType: hard + +"@reown/appkit-scaffold-ui@npm:1.7.3": + version: 1.7.3 + resolution: "@reown/appkit-scaffold-ui@npm:1.7.3" + dependencies: + "@reown/appkit-common": "npm:1.7.3" + "@reown/appkit-controllers": "npm:1.7.3" + "@reown/appkit-ui": "npm:1.7.3" + "@reown/appkit-utils": "npm:1.7.3" + "@reown/appkit-wallet": "npm:1.7.3" + lit: "npm:3.1.0" + checksum: e1511b06ef44da380cd5ff2f11dec65d920f32569de72a862d0ae36cce00e591dd73287df20e16af93734723086340057fbcb156429707cf9cf29a989964a9e0 + languageName: node + linkType: hard + "@reown/appkit-scaffold-utils-react-native@npm:1.2.3, @reown/appkit-scaffold-utils-react-native@workspace:packages/scaffold-utils": version: 0.0.0-use.local resolution: "@reown/appkit-scaffold-utils-react-native@workspace:packages/scaffold-utils" dependencies: "@reown/appkit-common-react-native": "npm:1.2.3" - "@reown/appkit-scaffold-react-native": "npm:1.2.3" + "@reown/appkit-core-react-native": "npm:1.2.3" languageName: unknown linkType: soft @@ -7277,6 +7318,14 @@ __metadata: languageName: unknown linkType: soft +"@reown/appkit-solana-react-native@npm:1.2.3, @reown/appkit-solana-react-native@workspace:packages/solana": + version: 0.0.0-use.local + resolution: "@reown/appkit-solana-react-native@workspace:packages/solana" + dependencies: + "@reown/appkit-common-react-native": "npm:1.2.3" + languageName: unknown + linkType: soft + "@reown/appkit-ui-react-native@npm:1.2.3, @reown/appkit-ui-react-native@workspace:packages/ui": version: 0.0.0-use.local resolution: "@reown/appkit-ui-react-native@workspace:packages/ui" @@ -7290,12 +7339,43 @@ __metadata: languageName: unknown linkType: soft +"@reown/appkit-ui@npm:1.7.3": + version: 1.7.3 + resolution: "@reown/appkit-ui@npm:1.7.3" + dependencies: + "@reown/appkit-common": "npm:1.7.3" + "@reown/appkit-controllers": "npm:1.7.3" + "@reown/appkit-wallet": "npm:1.7.3" + lit: "npm:3.1.0" + qrcode: "npm:1.5.3" + checksum: d70c1ad9a143cb831c1d005ce1c72a0b8ce1c6cd8aa4a3bc1f515902386873544905ca86b431419bc8b68e4ef608b405538619a55ff4db490020766bf84edbf3 + languageName: node + linkType: hard + +"@reown/appkit-utils@npm:1.7.3": + version: 1.7.3 + resolution: "@reown/appkit-utils@npm:1.7.3" + dependencies: + "@reown/appkit-common": "npm:1.7.3" + "@reown/appkit-controllers": "npm:1.7.3" + "@reown/appkit-polyfills": "npm:1.7.3" + "@reown/appkit-wallet": "npm:1.7.3" + "@walletconnect/logger": "npm:2.1.2" + "@walletconnect/universal-provider": "npm:2.19.2" + valtio: "npm:1.13.2" + viem: "npm:>=2.23.11" + peerDependencies: + valtio: 1.13.2 + checksum: dbcf4e2b8dc2edf653ba4e9725addd4aed8f36fed7c24d875afcf26300100cc88fa0b3a8048fcdd3ed21d3371d5a63fb43276aa5fefd779f30186b69fe2ad6c1 + languageName: node + linkType: hard + "@reown/appkit-wagmi-react-native@npm:1.2.3, @reown/appkit-wagmi-react-native@workspace:packages/wagmi": version: 0.0.0-use.local resolution: "@reown/appkit-wagmi-react-native@workspace:packages/wagmi" dependencies: "@reown/appkit-common-react-native": "npm:1.2.3" - "@reown/appkit-scaffold-react-native": "npm:1.2.3" + "@reown/appkit-react-native": "npm:1.2.3" "@reown/appkit-scaffold-utils-react-native": "npm:1.2.3" "@reown/appkit-siwe-react-native": "npm:1.2.3" peerDependencies: @@ -7323,13 +7403,45 @@ __metadata: languageName: unknown linkType: soft -"@safe-global/safe-apps-provider@npm:0.18.5": - version: 0.18.5 - resolution: "@safe-global/safe-apps-provider@npm:0.18.5" +"@reown/appkit-wallet@npm:1.7.3": + version: 1.7.3 + resolution: "@reown/appkit-wallet@npm:1.7.3" + dependencies: + "@reown/appkit-common": "npm:1.7.3" + "@reown/appkit-polyfills": "npm:1.7.3" + "@walletconnect/logger": "npm:2.1.2" + zod: "npm:3.22.4" + checksum: 8468fa16a0fb64d7c45e4e7ed400f49aacf58e7aa74033b68c54b3fdbd74f934f6156c5c95839189b5a9adb746f2611dc2d57ba2ab87efbed4017e286edff50f + languageName: node + linkType: hard + +"@reown/appkit@npm:1.7.3": + version: 1.7.3 + resolution: "@reown/appkit@npm:1.7.3" + dependencies: + "@reown/appkit-common": "npm:1.7.3" + "@reown/appkit-controllers": "npm:1.7.3" + "@reown/appkit-polyfills": "npm:1.7.3" + "@reown/appkit-scaffold-ui": "npm:1.7.3" + "@reown/appkit-ui": "npm:1.7.3" + "@reown/appkit-utils": "npm:1.7.3" + "@reown/appkit-wallet": "npm:1.7.3" + "@walletconnect/types": "npm:2.19.2" + "@walletconnect/universal-provider": "npm:2.19.2" + bs58: "npm:6.0.0" + valtio: "npm:1.13.2" + viem: "npm:>=2.23.11" + checksum: 0dd83161b3468ffda5c76503540f69eb8f9c9c77ce9e4efb2d5f0bf94127815f44d2b32d0bfb9d30db6e29877905b64379e1d035c6d0a40f5445ac94827714a4 + languageName: node + linkType: hard + +"@safe-global/safe-apps-provider@npm:0.18.6": + version: 0.18.6 + resolution: "@safe-global/safe-apps-provider@npm:0.18.6" dependencies: "@safe-global/safe-apps-sdk": "npm:^9.1.0" events: "npm:^3.3.0" - checksum: 5699b4abd63d1042aca299cddb466ebf79b0e6709a22b277c7320343edce36e50f4d5356c4eda4497e1c2f4d6a92b14b29c7aefe0cf673f5614752f5ff6fbac5 + checksum: e8567a97e43740bfe21b6f8a7759cabed2bc96eb50fd494118cab13a20f14797fbca3e02d18f0395054fcfbf2fd86315e5433d5b26f73bed6c3c86881087716c languageName: node linkType: hard @@ -7541,176 +7653,6 @@ __metadata: languageName: node linkType: hard -"@stablelib/aead@npm:^1.0.1": - version: 1.0.1 - resolution: "@stablelib/aead@npm:1.0.1" - checksum: 8ec16795a6f94264f93514661e024c5b0434d75000ea133923c57f0db30eab8ddc74fa35f5ff1ae4886803a8b92e169b828512c9e6bc02c818688d0f5b9f5aef - languageName: node - linkType: hard - -"@stablelib/binary@npm:^1.0.1": - version: 1.0.1 - resolution: "@stablelib/binary@npm:1.0.1" - dependencies: - "@stablelib/int": "npm:^1.0.1" - checksum: 154cb558d8b7c20ca5dc2e38abca2a3716ce36429bf1b9c298939cea0929766ed954feb8a9c59245ac64c923d5d3466bb7d99f281debd3a9d561e1279b11cd35 - languageName: node - linkType: hard - -"@stablelib/bytes@npm:^1.0.1": - version: 1.0.1 - resolution: "@stablelib/bytes@npm:1.0.1" - checksum: ee99bb15dac2f4ae1aa4e7a571e76483617a441feff422442f293993bc8b2c7ef021285c98f91a043bc05fb70502457799e28ffd43a8564a17913ee5ce889237 - languageName: node - linkType: hard - -"@stablelib/chacha20poly1305@npm:1.0.1": - version: 1.0.1 - resolution: "@stablelib/chacha20poly1305@npm:1.0.1" - dependencies: - "@stablelib/aead": "npm:^1.0.1" - "@stablelib/binary": "npm:^1.0.1" - "@stablelib/chacha": "npm:^1.0.1" - "@stablelib/constant-time": "npm:^1.0.1" - "@stablelib/poly1305": "npm:^1.0.1" - "@stablelib/wipe": "npm:^1.0.1" - checksum: fe202aa8aface111c72bc9ec099f9c36a7b1470eda9834e436bb228618a704929f095b937f04e867fe4d5c40216ff089cbfeb2eeb092ab33af39ff333eb2c1e6 - languageName: node - linkType: hard - -"@stablelib/chacha@npm:^1.0.1": - version: 1.0.1 - resolution: "@stablelib/chacha@npm:1.0.1" - dependencies: - "@stablelib/binary": "npm:^1.0.1" - "@stablelib/wipe": "npm:^1.0.1" - checksum: 4d70b484ae89416d21504024f977f5517bf16b344b10fb98382c9e3e52fe8ca77ac65f5d6a358d8b152f2c9ffed101a1eb15ed1707cdf906e1b6624db78d2d16 - languageName: node - linkType: hard - -"@stablelib/constant-time@npm:^1.0.1": - version: 1.0.1 - resolution: "@stablelib/constant-time@npm:1.0.1" - checksum: 694a282441215735a1fdfa3d06db5a28ba92423890967a154514ef28e0d0298ce7b6a2bc65ebc4273573d6669a6b601d330614747aa2e69078c1d523d7069e12 - languageName: node - linkType: hard - -"@stablelib/ed25519@npm:^1.0.2": - version: 1.0.3 - resolution: "@stablelib/ed25519@npm:1.0.3" - dependencies: - "@stablelib/random": "npm:^1.0.2" - "@stablelib/sha512": "npm:^1.0.1" - "@stablelib/wipe": "npm:^1.0.1" - checksum: b4a05e3c24dabd8a9e0b5bd72dea761bfb4b5c66404308e9f0529ef898e75d6f588234920762d5372cb920d9d47811250160109f02d04b6eed53835fb6916eb9 - languageName: node - linkType: hard - -"@stablelib/hash@npm:^1.0.1": - version: 1.0.1 - resolution: "@stablelib/hash@npm:1.0.1" - checksum: 58b5572a4067820b77a1606ed2d4a6dc4068c5475f68ba0918860a5f45adf60b33024a0cea9532dcd8b7345c53b3c9636a23723f5f8ae83e0c3648f91fb5b5cc - languageName: node - linkType: hard - -"@stablelib/hkdf@npm:1.0.1": - version: 1.0.1 - resolution: "@stablelib/hkdf@npm:1.0.1" - dependencies: - "@stablelib/hash": "npm:^1.0.1" - "@stablelib/hmac": "npm:^1.0.1" - "@stablelib/wipe": "npm:^1.0.1" - checksum: 722d30e36afa8029fda2a9e8c65ad753deff92a234e708820f9fd39309d2494e1c035a4185f29ae8d7fbf8a74862b27128c66a1fb4bd7a792bd300190080dbe9 - languageName: node - linkType: hard - -"@stablelib/hmac@npm:^1.0.1": - version: 1.0.1 - resolution: "@stablelib/hmac@npm:1.0.1" - dependencies: - "@stablelib/constant-time": "npm:^1.0.1" - "@stablelib/hash": "npm:^1.0.1" - "@stablelib/wipe": "npm:^1.0.1" - checksum: a111d5e687966b62c81f7dbd390f13582b027edee9bd39df6474a6472e5ad89d705e735af32bae2c9280a205806649f54b5ff8c4e8c8a7b484083a35b257e9e6 - languageName: node - linkType: hard - -"@stablelib/int@npm:^1.0.1": - version: 1.0.1 - resolution: "@stablelib/int@npm:1.0.1" - checksum: e1a6a7792fc2146d65de56e4ef42e8bc385dd5157eff27019b84476f564a1a6c43413235ed0e9f7c9bb8907dbdab24679467aeb10f44c92e6b944bcd864a7ee0 - languageName: node - linkType: hard - -"@stablelib/keyagreement@npm:^1.0.1": - version: 1.0.1 - resolution: "@stablelib/keyagreement@npm:1.0.1" - dependencies: - "@stablelib/bytes": "npm:^1.0.1" - checksum: 18c9e09772a058edee265c65992ec37abe4ab5118171958972e28f3bbac7f2a0afa6aaf152ec1d785452477bdab5366b3f5b750e8982ae9ad090f5fa2e5269ba - languageName: node - linkType: hard - -"@stablelib/poly1305@npm:^1.0.1": - version: 1.0.1 - resolution: "@stablelib/poly1305@npm:1.0.1" - dependencies: - "@stablelib/constant-time": "npm:^1.0.1" - "@stablelib/wipe": "npm:^1.0.1" - checksum: 080185ffa92f5111e6ecfeab7919368b9984c26d048b9c09a111fbc657ea62bb5dfe6b56245e1804ce692a445cc93ab6625936515fa0e7518b8f2d86feda9630 - languageName: node - linkType: hard - -"@stablelib/random@npm:1.0.2, @stablelib/random@npm:^1.0.1, @stablelib/random@npm:^1.0.2": - version: 1.0.2 - resolution: "@stablelib/random@npm:1.0.2" - dependencies: - "@stablelib/binary": "npm:^1.0.1" - "@stablelib/wipe": "npm:^1.0.1" - checksum: ebb217cfb76db97d98ec07bd7ce03a650fa194b91f0cb12382738161adff1830f405de0e9bad22bbc352422339ff85f531873b6a874c26ea9b59cfcc7ea787e0 - languageName: node - linkType: hard - -"@stablelib/sha256@npm:1.0.1": - version: 1.0.1 - resolution: "@stablelib/sha256@npm:1.0.1" - dependencies: - "@stablelib/binary": "npm:^1.0.1" - "@stablelib/hash": "npm:^1.0.1" - "@stablelib/wipe": "npm:^1.0.1" - checksum: e29ee9bc76eece4345e9155ce4bdeeb1df8652296be72bd2760523ad565e3b99dca85b81db3b75ee20b34837077eb8542ca88f153f162154c62ba1f75aecc24a - languageName: node - linkType: hard - -"@stablelib/sha512@npm:^1.0.1": - version: 1.0.1 - resolution: "@stablelib/sha512@npm:1.0.1" - dependencies: - "@stablelib/binary": "npm:^1.0.1" - "@stablelib/hash": "npm:^1.0.1" - "@stablelib/wipe": "npm:^1.0.1" - checksum: 84549070a383f4daf23d9065230eb81bc8f590c68bf5f7968f1b78901236b3bb387c14f63773dc6c3dc78e823b1c15470d2a04d398a2506391f466c16ba29b58 - languageName: node - linkType: hard - -"@stablelib/wipe@npm:^1.0.1": - version: 1.0.1 - resolution: "@stablelib/wipe@npm:1.0.1" - checksum: c5a54f769c286a5b3ecff979471dfccd4311f2e84a959908e8c0e3aa4eed1364bd9707f7b69d1384b757e62cc295c221fa27286c7f782410eb8a690f30cfd796 - languageName: node - linkType: hard - -"@stablelib/x25519@npm:1.0.3": - version: 1.0.3 - resolution: "@stablelib/x25519@npm:1.0.3" - dependencies: - "@stablelib/keyagreement": "npm:^1.0.1" - "@stablelib/random": "npm:^1.0.2" - "@stablelib/wipe": "npm:^1.0.1" - checksum: d8afe8a120923a434359d7d1c6759780426fed117a84a6c0f84d1a4878834cb4c2d7da78a1fa7cf227ce3924fdc300cd6ed6e46cf2508bf17b1545c319ab8418 - languageName: node - linkType: hard - "@storybook/addon-actions@npm:8.3.0": version: 8.3.0 resolution: "@storybook/addon-actions@npm:8.3.0" @@ -8707,10 +8649,12 @@ __metadata: languageName: node linkType: hard -"@types/node@npm:18.15.13": - version: 18.15.13 - resolution: "@types/node@npm:18.15.13" - checksum: 6e5f61c559e60670a7a8fb88e31226ecc18a21be103297ca4cf9848f0a99049dae77f04b7ae677205f2af494f3701b113ba8734f4b636b355477a6534dbb8ada +"@types/node@npm:22.7.5": + version: 22.7.5 + resolution: "@types/node@npm:22.7.5" + dependencies: + undici-types: "npm:~6.19.2" + checksum: cf11f74f1a26053ec58066616e3a8685b6bcd7259bc569738b8f752009f9f0f7f85a1b2d24908e5b0f752482d1e8b6babdf1fbb25758711ec7bb9500bfcd6e60 languageName: node linkType: hard @@ -9184,30 +9128,30 @@ __metadata: languageName: node linkType: hard -"@wagmi/connectors@npm:5.7.9": - version: 5.7.9 - resolution: "@wagmi/connectors@npm:5.7.9" +"@wagmi/connectors@npm:5.8.0": + version: 5.8.0 + resolution: "@wagmi/connectors@npm:5.8.0" dependencies: "@coinbase/wallet-sdk": "npm:4.3.0" "@metamask/sdk": "npm:0.32.0" - "@safe-global/safe-apps-provider": "npm:0.18.5" + "@safe-global/safe-apps-provider": "npm:0.18.6" "@safe-global/safe-apps-sdk": "npm:9.1.0" - "@walletconnect/ethereum-provider": "npm:2.19.0" + "@walletconnect/ethereum-provider": "npm:2.20.0" cbw-sdk: "npm:@coinbase/wallet-sdk@3.9.3" peerDependencies: - "@wagmi/core": 2.16.5 + "@wagmi/core": 2.17.0 typescript: ">=5.0.4" viem: 2.x peerDependenciesMeta: typescript: optional: true - checksum: 28c50b6fe52a131418eebed01c0219b4583a06580d0bec7147adfa7c09bf0bb9b67d3dab43aae57febf68f057b72fdb0c08055c2fe016c0f2ba2ee99d85c525c + checksum: 620f668843e8799dd990d3f1c1645462f04f6ddb96b958e6d449f0a3c7ca05330ba44be2551238fec8b3f6a11f0b1967cb246d31669b6c504535f268b6641178 languageName: node linkType: hard -"@wagmi/core@npm:2.16.5": - version: 2.16.5 - resolution: "@wagmi/core@npm:2.16.5" +"@wagmi/core@npm:2.17.0": + version: 2.17.0 + resolution: "@wagmi/core@npm:2.17.0" dependencies: eventemitter3: "npm:5.0.1" mipd: "npm:0.0.7" @@ -9221,38 +9165,13 @@ __metadata: optional: true typescript: optional: true - checksum: 3c58155071fa2aa8a2941f2e4e80c68958f78f6d8a3462112d772ccf7e030261ec1b641243aeab2b21df0d4e89b583321c1771efe18b23b58357b64918b1c801 + checksum: cf691f134b3335302f3230bca064b587b5b085b36b6bc6f0a96e32888b7f5220fe4def2b0727fddef0f07fd11c0241ccd352980ae761cd0fa8c9010315621739 languageName: node linkType: hard -"@walletconnect/core@npm:2.17.3": - version: 2.17.3 - resolution: "@walletconnect/core@npm:2.17.3" - dependencies: - "@walletconnect/heartbeat": "npm:1.2.2" - "@walletconnect/jsonrpc-provider": "npm:1.0.14" - "@walletconnect/jsonrpc-types": "npm:1.0.4" - "@walletconnect/jsonrpc-utils": "npm:1.0.8" - "@walletconnect/jsonrpc-ws-connection": "npm:1.0.16" - "@walletconnect/keyvaluestorage": "npm:1.1.1" - "@walletconnect/logger": "npm:2.1.2" - "@walletconnect/relay-api": "npm:1.0.11" - "@walletconnect/relay-auth": "npm:1.0.4" - "@walletconnect/safe-json": "npm:1.0.2" - "@walletconnect/time": "npm:1.0.2" - "@walletconnect/types": "npm:2.17.3" - "@walletconnect/utils": "npm:2.17.3" - "@walletconnect/window-getters": "npm:1.0.1" - events: "npm:3.3.0" - lodash.isequal: "npm:4.5.0" - uint8arrays: "npm:3.1.0" - checksum: e6a841a0d5b27922b83fbb7a1dbcb519b825d70489f9bd6a909cf0b3c543ab3a6c209a0775a95c5dc452a875757f04c9ca27d02c6f002c39974d2ce2061e5887 - languageName: node - linkType: hard - -"@walletconnect/core@npm:2.19.0": - version: 2.19.0 - resolution: "@walletconnect/core@npm:2.19.0" +"@walletconnect/core@npm:2.20.2": + version: 2.20.2 + resolution: "@walletconnect/core@npm:2.20.2" dependencies: "@walletconnect/heartbeat": "npm:1.2.2" "@walletconnect/jsonrpc-provider": "npm:1.0.14" @@ -9265,13 +9184,13 @@ __metadata: "@walletconnect/relay-auth": "npm:1.1.0" "@walletconnect/safe-json": "npm:1.0.2" "@walletconnect/time": "npm:1.0.2" - "@walletconnect/types": "npm:2.19.0" - "@walletconnect/utils": "npm:2.19.0" + "@walletconnect/types": "npm:2.20.2" + "@walletconnect/utils": "npm:2.20.2" "@walletconnect/window-getters": "npm:1.0.1" + es-toolkit: "npm:1.33.0" events: "npm:3.3.0" - lodash.isequal: "npm:4.5.0" uint8arrays: "npm:3.1.0" - checksum: c0ac9eeb576af7ed31edbfff10fcd80d2917c03e6962747977c31ec7ce0734c9dc26c7c2481e975f42615749c8e34122fe1dc81ec3d361573ae85fee3185121e + checksum: 2ed3737b4cfc22df5fbca5d8c551f82eb5811865300f8990ee5b035fde0e90894c0a63172b021736ebc97ed7913f39e89e1c87bfaccae34db18eb5bf4dd4fd92 languageName: node linkType: hard @@ -9284,41 +9203,22 @@ __metadata: languageName: node linkType: hard -"@walletconnect/ethereum-provider@npm:2.17.3": - version: 2.17.3 - resolution: "@walletconnect/ethereum-provider@npm:2.17.3" +"@walletconnect/ethereum-provider@npm:2.20.2": + version: 2.20.2 + resolution: "@walletconnect/ethereum-provider@npm:2.20.2" dependencies: + "@reown/appkit": "npm:1.7.3" "@walletconnect/jsonrpc-http-connection": "npm:1.0.8" "@walletconnect/jsonrpc-provider": "npm:1.0.14" "@walletconnect/jsonrpc-types": "npm:1.0.4" "@walletconnect/jsonrpc-utils": "npm:1.0.8" "@walletconnect/keyvaluestorage": "npm:1.1.1" - "@walletconnect/modal": "npm:2.7.0" - "@walletconnect/sign-client": "npm:2.17.3" - "@walletconnect/types": "npm:2.17.3" - "@walletconnect/universal-provider": "npm:2.17.3" - "@walletconnect/utils": "npm:2.17.3" + "@walletconnect/sign-client": "npm:2.20.2" + "@walletconnect/types": "npm:2.20.2" + "@walletconnect/universal-provider": "npm:2.20.2" + "@walletconnect/utils": "npm:2.20.2" events: "npm:3.3.0" - checksum: 6ca5aaf5f72dfe0c8edd54f4bd30a55ee22e28cf766a6fe1052a22ad252f0aab4d41c9e105b97e1a4ce29f25fbb8aaed3081a447ecb1759664306b4725948774 - languageName: node - linkType: hard - -"@walletconnect/ethereum-provider@npm:2.19.0": - version: 2.19.0 - resolution: "@walletconnect/ethereum-provider@npm:2.19.0" - dependencies: - "@walletconnect/jsonrpc-http-connection": "npm:1.0.8" - "@walletconnect/jsonrpc-provider": "npm:1.0.14" - "@walletconnect/jsonrpc-types": "npm:1.0.4" - "@walletconnect/jsonrpc-utils": "npm:1.0.8" - "@walletconnect/keyvaluestorage": "npm:1.1.1" - "@walletconnect/modal": "npm:2.7.0" - "@walletconnect/sign-client": "npm:2.19.0" - "@walletconnect/types": "npm:2.19.0" - "@walletconnect/universal-provider": "npm:2.19.0" - "@walletconnect/utils": "npm:2.19.0" - events: "npm:3.3.0" - checksum: a7f356c469bdd8ba68a037a3facbc60580a702a9615d37475e2d996c4ecb373da42e42fb93ad3129e23e0d93e94043f7f41474caea7279c89f68dfd4d3d98b17 + checksum: e1a809f91abef108cec52621144bd48adc9408db11f5b741c2607a1ca6abfbb81f6120f68b4be2728733168ce653ffc68689d8e067ec4fb12d9c53ff0f872420 languageName: node linkType: hard @@ -9435,40 +9335,9 @@ __metadata: languageName: node linkType: hard -"@walletconnect/modal-core@npm:2.7.0": - version: 2.7.0 - resolution: "@walletconnect/modal-core@npm:2.7.0" - dependencies: - valtio: "npm:1.11.2" - checksum: 84b11735c005e37e661aa0f08b2e8c8098db3b2cacd957c4a73f4d3de11b2d5e04dd97ab970f8d22fc3e8269fea3297b9487e177343bbab8dd69b3b917fb7f60 - languageName: node - linkType: hard - -"@walletconnect/modal-ui@npm:2.7.0": - version: 2.7.0 - resolution: "@walletconnect/modal-ui@npm:2.7.0" - dependencies: - "@walletconnect/modal-core": "npm:2.7.0" - lit: "npm:2.8.0" - motion: "npm:10.16.2" - qrcode: "npm:1.5.3" - checksum: b717f1fc9854b7d14a4364720fce2d44167f547533340704644ed2fdf9d861b3798ffd19a3b51062a366a8bc39f84b9a8bb3dd04e9e33da742192359be00b051 - languageName: node - linkType: hard - -"@walletconnect/modal@npm:2.7.0": - version: 2.7.0 - resolution: "@walletconnect/modal@npm:2.7.0" - dependencies: - "@walletconnect/modal-core": "npm:2.7.0" - "@walletconnect/modal-ui": "npm:2.7.0" - checksum: 2f3074eebbca41a46e29680dc2565bc762133508774f05db0075a82b0b66ecc8defca40a94ad63669676090a7e3ef671804592b10e91636ab1cdeac014a1eb11 - languageName: node - linkType: hard - -"@walletconnect/react-native-compat@npm:2.19.1": - version: 2.19.1 - resolution: "@walletconnect/react-native-compat@npm:2.19.1" +"@walletconnect/react-native-compat@npm:2.20.2": + version: 2.20.2 + resolution: "@walletconnect/react-native-compat@npm:2.20.2" dependencies: events: "npm:3.3.0" fast-text-encoding: "npm:1.0.6" @@ -9482,7 +9351,7 @@ __metadata: peerDependenciesMeta: expo-application: optional: true - checksum: d8ae5291c47d277efd725d08b359a634ca5abd13929632edcda15a0e5185873d2d48935f624744703eebe78b39140795a2befc6cfd541e40bba8f5be5b23c915 + checksum: 09eb1ee3861b639ad2a5c5064d35eb19398855a29625f8f2ed60dbfd2eda2d0d663044e7fbe15c351839b9ed188b89488d29de54a484b2b79e4c74307125e739 languageName: node linkType: hard @@ -9495,20 +9364,6 @@ __metadata: languageName: node linkType: hard -"@walletconnect/relay-auth@npm:1.0.4": - version: 1.0.4 - resolution: "@walletconnect/relay-auth@npm:1.0.4" - dependencies: - "@stablelib/ed25519": "npm:^1.0.2" - "@stablelib/random": "npm:^1.0.1" - "@walletconnect/safe-json": "npm:^1.0.1" - "@walletconnect/time": "npm:^1.0.2" - tslib: "npm:1.14.1" - uint8arrays: "npm:^3.0.0" - checksum: e90294ff718c5c1e49751a28916aaac45dd07d694f117052506309eb05b68cc2c72d9b302366e40d79ef952c22bd0bbea731d09633a6663b0ab8e18b4804a832 - languageName: node - linkType: hard - "@walletconnect/relay-auth@npm:1.1.0": version: 1.1.0 resolution: "@walletconnect/relay-auth@npm:1.1.0" @@ -9531,37 +9386,20 @@ __metadata: languageName: node linkType: hard -"@walletconnect/sign-client@npm:2.17.3": - version: 2.17.3 - resolution: "@walletconnect/sign-client@npm:2.17.3" - dependencies: - "@walletconnect/core": "npm:2.17.3" - "@walletconnect/events": "npm:1.0.1" - "@walletconnect/heartbeat": "npm:1.2.2" - "@walletconnect/jsonrpc-utils": "npm:1.0.8" - "@walletconnect/logger": "npm:2.1.2" - "@walletconnect/time": "npm:1.0.2" - "@walletconnect/types": "npm:2.17.3" - "@walletconnect/utils": "npm:2.17.3" - events: "npm:3.3.0" - checksum: 454afa3c933ec11f651c4cd275af88eef7da65b5d4bcf8987f768f340557492cf436d662ca42baa54ad8136e4b16f5269e0bc3e212580df09e0ee49873718b96 - languageName: node - linkType: hard - -"@walletconnect/sign-client@npm:2.19.0": - version: 2.19.0 - resolution: "@walletconnect/sign-client@npm:2.19.0" +"@walletconnect/sign-client@npm:2.20.2": + version: 2.20.2 + resolution: "@walletconnect/sign-client@npm:2.20.2" dependencies: - "@walletconnect/core": "npm:2.19.0" + "@walletconnect/core": "npm:2.20.2" "@walletconnect/events": "npm:1.0.1" "@walletconnect/heartbeat": "npm:1.2.2" "@walletconnect/jsonrpc-utils": "npm:1.0.8" "@walletconnect/logger": "npm:2.1.2" "@walletconnect/time": "npm:1.0.2" - "@walletconnect/types": "npm:2.19.0" - "@walletconnect/utils": "npm:2.19.0" + "@walletconnect/types": "npm:2.20.2" + "@walletconnect/utils": "npm:2.20.2" events: "npm:3.3.0" - checksum: 0364d8f1ae4cfa08a598623f4b5a9c70c6c4b10ba8266eb57f272a90ed590f3fb5a6feeba4082461c1e9e0fe38652a51e8078f7691b70267683bb2299e901ae9 + checksum: 12c27037591179553b9d5fc374dfe9980c10a04ec6d5d8418ebfd7e72c466577e88295b209da9fd429c648a7ee8a8cc64a9e1a2c320e8aa55f21e65b85714e31 languageName: node linkType: hard @@ -9574,9 +9412,9 @@ __metadata: languageName: node linkType: hard -"@walletconnect/types@npm:2.17.3": - version: 2.17.3 - resolution: "@walletconnect/types@npm:2.17.3" +"@walletconnect/types@npm:2.19.2": + version: 2.19.2 + resolution: "@walletconnect/types@npm:2.19.2" dependencies: "@walletconnect/events": "npm:1.0.1" "@walletconnect/heartbeat": "npm:1.2.2" @@ -9584,13 +9422,13 @@ __metadata: "@walletconnect/keyvaluestorage": "npm:1.1.1" "@walletconnect/logger": "npm:2.1.2" events: "npm:3.3.0" - checksum: 6e50f1f3d64f32d0fa697bb61340191b153aa0a77b8a483cacaeb62aefa190524e10f78188260b591eaae877d6bfa5ea9ffab5ed905c286151300577f2e0101f + checksum: aa539e73851c0d744982119bf137555d1649f4b9aae6c4f2e296c85fe0a92b371334bb137329a0eb1c828de22f81991c91ce8e5975ee6a381bc03b864ed0dd9d languageName: node linkType: hard -"@walletconnect/types@npm:2.19.0": - version: 2.19.0 - resolution: "@walletconnect/types@npm:2.19.0" +"@walletconnect/types@npm:2.20.2": + version: 2.20.2 + resolution: "@walletconnect/types@npm:2.20.2" dependencies: "@walletconnect/events": "npm:1.0.1" "@walletconnect/heartbeat": "npm:1.2.2" @@ -9598,13 +9436,13 @@ __metadata: "@walletconnect/keyvaluestorage": "npm:1.1.1" "@walletconnect/logger": "npm:2.1.2" events: "npm:3.3.0" - checksum: 16e6006ba27a75b0e7d1cd120a275eb10c3493bacf8205808462dfb369b4b97b652f776bff35bf6da7087fd0ef67af401e40aedd4c986ee4b17864e85fba2ee6 + checksum: 6695cc03a68aa66692000373f44a2844cb6b782748524c5ea6ac7c64a2a133558579a7f0d4300b2d0b1210ada40119d328f7734a9da163f65356148313f3ae18 languageName: node linkType: hard -"@walletconnect/universal-provider@npm:2.17.3": - version: 2.17.3 - resolution: "@walletconnect/universal-provider@npm:2.17.3" +"@walletconnect/universal-provider@npm:2.20.2": + version: 2.20.2 + resolution: "@walletconnect/universal-provider@npm:2.20.2" dependencies: "@walletconnect/events": "npm:1.0.1" "@walletconnect/jsonrpc-http-connection": "npm:1.0.8" @@ -9613,66 +9451,18 @@ __metadata: "@walletconnect/jsonrpc-utils": "npm:1.0.8" "@walletconnect/keyvaluestorage": "npm:1.1.1" "@walletconnect/logger": "npm:2.1.2" - "@walletconnect/sign-client": "npm:2.17.3" - "@walletconnect/types": "npm:2.17.3" - "@walletconnect/utils": "npm:2.17.3" + "@walletconnect/sign-client": "npm:2.20.2" + "@walletconnect/types": "npm:2.20.2" + "@walletconnect/utils": "npm:2.20.2" + es-toolkit: "npm:1.33.0" events: "npm:3.3.0" - lodash: "npm:4.17.21" - checksum: a577099e5b40fc254df56f9fa3335ff064af24804ec7db9e213ef74261076b2e92194251f56f44de3a7d980deb7cef14f76ca961399e6f6671d1a7dccbdea8d9 - languageName: node - linkType: hard - -"@walletconnect/universal-provider@npm:2.19.0": - version: 2.19.0 - resolution: "@walletconnect/universal-provider@npm:2.19.0" - dependencies: - "@walletconnect/events": "npm:1.0.1" - "@walletconnect/jsonrpc-http-connection": "npm:1.0.8" - "@walletconnect/jsonrpc-provider": "npm:1.0.14" - "@walletconnect/jsonrpc-types": "npm:1.0.4" - "@walletconnect/jsonrpc-utils": "npm:1.0.8" - "@walletconnect/keyvaluestorage": "npm:1.1.1" - "@walletconnect/logger": "npm:2.1.2" - "@walletconnect/sign-client": "npm:2.19.0" - "@walletconnect/types": "npm:2.19.0" - "@walletconnect/utils": "npm:2.19.0" - events: "npm:3.3.0" - lodash: "npm:4.17.21" - checksum: 9c473c925cfe172397f85995c895d058e07ea6eb41060f7aafb0d2c6eac22afcccf726f5ee2267bec04dc7dc98758a2e2ddf2870c1767603b3d6d1136655589f - languageName: node - linkType: hard - -"@walletconnect/utils@npm:2.17.3": - version: 2.17.3 - resolution: "@walletconnect/utils@npm:2.17.3" - dependencies: - "@ethersproject/hash": "npm:5.7.0" - "@ethersproject/transactions": "npm:5.7.0" - "@stablelib/chacha20poly1305": "npm:1.0.1" - "@stablelib/hkdf": "npm:1.0.1" - "@stablelib/random": "npm:1.0.2" - "@stablelib/sha256": "npm:1.0.1" - "@stablelib/x25519": "npm:1.0.3" - "@walletconnect/jsonrpc-utils": "npm:1.0.8" - "@walletconnect/keyvaluestorage": "npm:1.1.1" - "@walletconnect/relay-api": "npm:1.0.11" - "@walletconnect/relay-auth": "npm:1.0.4" - "@walletconnect/safe-json": "npm:1.0.2" - "@walletconnect/time": "npm:1.0.2" - "@walletconnect/types": "npm:2.17.3" - "@walletconnect/window-getters": "npm:1.0.1" - "@walletconnect/window-metadata": "npm:1.0.1" - detect-browser: "npm:5.3.0" - elliptic: "npm:6.6.1" - query-string: "npm:7.1.3" - uint8arrays: "npm:3.1.0" - checksum: ab08f625786eb55e0ae41075a3ccee9804750b1f20745f2d7a81569a6741d022463b250958124925e6b5f51d3a5b3ec783a23233391d8d937c4bcd76e7a8cc8c + checksum: d5876a490bfc207f00a0573aea129c153a959dbea33243efc71180129ba6f4d9bd103773f37759b72d8a27d30053fdfda7f4b58254ed1b663555809fc86dc685 languageName: node linkType: hard -"@walletconnect/utils@npm:2.19.0": - version: 2.19.0 - resolution: "@walletconnect/utils@npm:2.19.0" +"@walletconnect/utils@npm:2.20.2": + version: 2.20.2 + resolution: "@walletconnect/utils@npm:2.20.2" dependencies: "@noble/ciphers": "npm:1.2.1" "@noble/curves": "npm:1.8.1" @@ -9683,15 +9473,15 @@ __metadata: "@walletconnect/relay-auth": "npm:1.1.0" "@walletconnect/safe-json": "npm:1.0.2" "@walletconnect/time": "npm:1.0.2" - "@walletconnect/types": "npm:2.19.0" + "@walletconnect/types": "npm:2.20.2" "@walletconnect/window-getters": "npm:1.0.1" "@walletconnect/window-metadata": "npm:1.0.1" + bs58: "npm:6.0.0" detect-browser: "npm:5.3.0" - elliptic: "npm:6.6.1" query-string: "npm:7.1.3" uint8arrays: "npm:3.1.0" viem: "npm:2.23.2" - checksum: 80b2b8ff925670764561f1b4cc006915bf173237058488e0a94c62a4f3ab9071a118f699ee3016e56d22ed7dee5f84cd625e0b183330123f8b65e1a30f0a9571 + checksum: 114e9da7a5b477bc845aea4c18a4b7ad4cac45e89bf3cf6a35eba38f4435303ceb7e024b7d283fc67ff24a2b5b9fe7a3f2ac537c05c5a53e249dee611168a2b1 languageName: node linkType: hard @@ -10256,7 +10046,7 @@ __metadata: "@types/jest": "npm:29.5.7" "@types/qrcode": "npm:1.5.5" "@types/react": "npm:18.2.79" - "@walletconnect/react-native-compat": "npm:2.19.1" + "@walletconnect/react-native-compat": "npm:2.20.2" babel-jest: "npm:^29.7.0" eslint: "npm:^8.46.0" eslint-plugin-ft-flow: "npm:2.0.3" @@ -10277,8 +10067,8 @@ __metadata: tsconfig: "npm:*" turbo: "npm:2.1.1" typescript: "npm:5.2.2" - viem: "npm:2.23.10" - wagmi: "npm:2.14.13" + viem: "npm:2.28.3" + wagmi: "npm:2.15.1" languageName: unknown linkType: soft @@ -10849,6 +10639,13 @@ __metadata: languageName: node linkType: hard +"base-x@npm:^5.0.0": + version: 5.0.1 + resolution: "base-x@npm:5.0.1" + checksum: 4ab6b02262b4fd499b147656f63ce7328bd5f895450401ce58a2f9e87828aea507cf0c320a6d8725389f86e8a48397562661c0bca28ef3276a22821b30f7a713 + languageName: node + linkType: hard + "base64-js@npm:^1.2.3, base64-js@npm:^1.3.1, base64-js@npm:^1.5.1": version: 1.5.1 resolution: "base64-js@npm:1.5.1" @@ -10863,6 +10660,13 @@ __metadata: languageName: node linkType: hard +"bech32@npm:^2.0.0": + version: 2.0.0 + resolution: "bech32@npm:2.0.0" + checksum: 45e7cc62758c9b26c05161b4483f40ea534437cf68ef785abadc5b62a2611319b878fef4f86ddc14854f183b645917a19addebc9573ab890e19194bc8f521942 + languageName: node + linkType: hard + "better-opn@npm:~3.0.2": version: 3.0.2 resolution: "better-opn@npm:3.0.2" @@ -10888,6 +10692,13 @@ __metadata: languageName: node linkType: hard +"big.js@npm:6.2.2": + version: 6.2.2 + resolution: "big.js@npm:6.2.2" + checksum: 58d204f6a1a92508dc2eb98d964e2cc6dabb37a3d9fc8a1f0b77a34dead7c11e17b173d9a6df2d5a7a0f78d5c80853a9ce6df29852da59ab10b088e981195165 + languageName: node + linkType: hard + "bignumber.js@npm:9.1.2": version: 9.1.2 resolution: "bignumber.js@npm:9.1.2" @@ -10902,6 +10713,31 @@ __metadata: languageName: node linkType: hard +"bip174@npm:^3.0.0-rc.0": + version: 3.0.0-rc.1 + resolution: "bip174@npm:3.0.0-rc.1" + dependencies: + uint8array-tools: "npm:^0.0.9" + varuint-bitcoin: "npm:^2.0.0" + checksum: d4fc26a4ec3dc6f4ce5b5cf38acc0825570c96a2bea536bf857a743ebfca5061ea9bc5c0cb21466a47c82e757190e7f00149eb6c6ccbba3238a48e853341b945 + languageName: node + linkType: hard + +"bitcoinjs-lib@npm:7.0.0-rc.0": + version: 7.0.0-rc.0 + resolution: "bitcoinjs-lib@npm:7.0.0-rc.0" + dependencies: + "@noble/hashes": "npm:^1.2.0" + bech32: "npm:^2.0.0" + bip174: "npm:^3.0.0-rc.0" + bs58check: "npm:^4.0.0" + uint8array-tools: "npm:^0.0.9" + valibot: "npm:^0.38.0" + varuint-bitcoin: "npm:^2.0.0" + checksum: 9185d2b59a3a75d34a715dd0f654019cba4a274042c376c6ff130469fb5577de6dbe224c0c1a482fa842dfd35d3b0d25de263a5c1a79762cf46ed325194b6614 + languageName: node + linkType: hard + "bl@npm:^4.1.0": version: 4.1.0 resolution: "bl@npm:4.1.0" @@ -11141,6 +10977,25 @@ __metadata: languageName: node linkType: hard +"bs58@npm:6.0.0, bs58@npm:^6.0.0": + version: 6.0.0 + resolution: "bs58@npm:6.0.0" + dependencies: + base-x: "npm:^5.0.0" + checksum: 61910839746625ee4f69369f80e2634e2123726caaa1da6b3bcefcf7efcd9bdca86603360fed9664ffdabe0038c51e542c02581c72ca8d44f60329fe1a6bc8f4 + languageName: node + linkType: hard + +"bs58check@npm:^4.0.0": + version: 4.0.0 + resolution: "bs58check@npm:4.0.0" + dependencies: + "@noble/hashes": "npm:^1.2.0" + bs58: "npm:^6.0.0" + checksum: a4e695202711daffa157ada2044bb55ff21adcfe22c92ede12111d55570e170dd4cb8cd058db12980dca6bd51733f17f7534cddc19ea1f7dfa9852583f888eea + languageName: node + linkType: hard + "bser@npm:2.1.1": version: 2.1.1 resolution: "bser@npm:2.1.1" @@ -12248,6 +12103,13 @@ __metadata: languageName: node linkType: hard +"dayjs@npm:1.11.13": + version: 1.11.13 + resolution: "dayjs@npm:1.11.13" + checksum: a3caf6ac8363c7dade9d1ee797848ddcf25c1ace68d9fe8678ecf8ba0675825430de5d793672ec87b24a69bf04a1544b176547b2539982275d5542a7955f35b7 + languageName: node + linkType: hard + "dayjs@npm:^1.8.15": version: 1.11.9 resolution: "dayjs@npm:1.11.9" @@ -12503,6 +12365,15 @@ __metadata: languageName: node linkType: hard +"derive-valtio@npm:0.1.0": + version: 0.1.0 + resolution: "derive-valtio@npm:0.1.0" + peerDependencies: + valtio: "*" + checksum: c64ed74e2bc140dafe080a58fd499f803cebaa89774b5d2bd0fea8054728912f1c715c5c370b4ff01ab9908b64828a7f8f0c968dc9efd0aee037e5679dd804d8 + languageName: node + linkType: hard + "destr@npm:^2.0.1, destr@npm:^2.0.2": version: 2.0.2 resolution: "destr@npm:2.0.2" @@ -13127,6 +12998,18 @@ __metadata: languageName: node linkType: hard +"es-toolkit@npm:1.33.0": + version: 1.33.0 + resolution: "es-toolkit@npm:1.33.0" + dependenciesMeta: + "@trivago/prettier-plugin-sort-imports@4.3.0": + unplugged: true + prettier-plugin-sort-re-exports@0.0.1: + unplugged: true + checksum: 4c8dea3167a813070812e5c3f827fb677b4729b622c209cfad68dd5b449a008df6f3b515e675a4a8519618f52b87fe1d157c320668be871165f934a15c1d2f37 + languageName: node + linkType: hard + "esbuild-register@npm:^3.5.0": version: 3.6.0 resolution: "esbuild-register@npm:3.6.0" @@ -13684,18 +13567,18 @@ __metadata: languageName: node linkType: hard -"ethers@npm:6.10.0": - version: 6.10.0 - resolution: "ethers@npm:6.10.0" +"ethers@npm:6.13.5": + version: 6.13.5 + resolution: "ethers@npm:6.13.5" dependencies: - "@adraffy/ens-normalize": "npm:1.10.0" + "@adraffy/ens-normalize": "npm:1.10.1" "@noble/curves": "npm:1.2.0" "@noble/hashes": "npm:1.3.2" - "@types/node": "npm:18.15.13" + "@types/node": "npm:22.7.5" aes-js: "npm:4.0.0-beta.5" - tslib: "npm:2.4.0" - ws: "npm:8.5.0" - checksum: 8816249426609a10aadef1a45cab5e2c34db533317557f29fa69ce02cb04be5018079e6d8685f8967d654d375917bae00288f74a13873f93058e5ef39b8a6106 + tslib: "npm:2.7.0" + ws: "npm:8.17.1" + checksum: 64bc7b8907de199392b8a88c15c9a085892919cff7efa2e5326abc7fe5c426001726c51d91e10c74e5fc5e2547188297ce4127f6e52ea42a97ade0b2ae474677 languageName: node linkType: hard @@ -15285,13 +15168,6 @@ __metadata: languageName: node linkType: hard -"hey-listen@npm:^1.0.8": - version: 1.0.8 - resolution: "hey-listen@npm:1.0.8" - checksum: 38db3028b4756f3d536c0f6a92da53bad577ab649b06dddfd0a4d953f9a46bbc6a7f693c8c5b466a538d6d23dbc469260c848427f0de14198a2bbecbac37b39e - languageName: node - linkType: hard - "hmac-drbg@npm:^1.0.1": version: 1.0.1 resolution: "hmac-drbg@npm:1.0.1" @@ -17246,34 +17122,34 @@ __metadata: languageName: node linkType: hard -"lit-element@npm:^3.3.0": - version: 3.3.3 - resolution: "lit-element@npm:3.3.3" +"lit-element@npm:^4.0.0": + version: 4.2.0 + resolution: "lit-element@npm:4.2.0" dependencies: - "@lit-labs/ssr-dom-shim": "npm:^1.1.0" - "@lit/reactive-element": "npm:^1.3.0" - lit-html: "npm:^2.8.0" - checksum: f44c12fa3423a4e9ca5b84651410687e14646bb270ac258325e6905affac64a575f041f8440377e7ebaefa3910b6f0d6b8b1e902cb1aa5d0849b3fdfbf4fb3b6 + "@lit-labs/ssr-dom-shim": "npm:^1.2.0" + "@lit/reactive-element": "npm:^2.1.0" + lit-html: "npm:^3.3.0" + checksum: 20577f2092ac1e1bd82fba2bbc9ce0122b35dc2495906d3fbcb437c3727b9c8ed1c0691b8b859f65a51e910db1341d95233c117e1e1c88c450b30e2d3b62fdb8 languageName: node linkType: hard -"lit-html@npm:^2.8.0": - version: 2.8.0 - resolution: "lit-html@npm:2.8.0" +"lit-html@npm:^3.1.0, lit-html@npm:^3.3.0": + version: 3.3.0 + resolution: "lit-html@npm:3.3.0" dependencies: "@types/trusted-types": "npm:^2.0.2" - checksum: 90057dee050803823ac884c1355b0213ab8c05fbe2ec63943c694b61aade5d36272068f3925f45a312835e504f9c9784738ef797009f0a756a750351eafb52d5 + checksum: c1065048d89d93df6a46cdeed9abd637ae9bcc0847ee108dccbb2e1627a4074074e1d3ac9360e08a736d76f8c76b2c88166dbe465406da123b9137e29c2e0034 languageName: node linkType: hard -"lit@npm:2.8.0": - version: 2.8.0 - resolution: "lit@npm:2.8.0" +"lit@npm:3.1.0": + version: 3.1.0 + resolution: "lit@npm:3.1.0" dependencies: - "@lit/reactive-element": "npm:^1.6.0" - lit-element: "npm:^3.3.0" - lit-html: "npm:^2.8.0" - checksum: bf33c26b1937ee204aed1adbfa4b3d43a284e85aad8ea9763c7865365917426eded4e5888158b4136095ea42054812561fe272862b61775f1198fad3588b071f + "@lit/reactive-element": "npm:^2.0.0" + lit-element: "npm:^4.0.0" + lit-html: "npm:^3.1.0" + checksum: 7ca12c1b1593373d16b51b2220677d8936b4061de4f278ef2a85f15726bb4365a8eed89a0294816a10d6124dca81f02e83b5dfed9a6031e135a7bc68924eea6b languageName: node linkType: hard @@ -17354,13 +17230,6 @@ __metadata: languageName: node linkType: hard -"lodash.isequal@npm:4.5.0": - version: 4.5.0 - resolution: "lodash.isequal@npm:4.5.0" - checksum: dfdb2356db19631a4b445d5f37868a095e2402292d59539a987f134a8778c62a2810c2452d11ae9e6dcac71fc9de40a6fedcb20e2952a15b431ad8b29e50e28f - languageName: node - linkType: hard - "lodash.memoize@npm:4.x": version: 4.1.2 resolution: "lodash.memoize@npm:4.1.2" @@ -17389,7 +17258,7 @@ __metadata: languageName: node linkType: hard -"lodash@npm:4.17.21, lodash@npm:^4.17.20, lodash@npm:^4.17.21": +"lodash@npm:^4.17.20, lodash@npm:^4.17.21": version: 4.17.21 resolution: "lodash@npm:4.17.21" checksum: d8cbea072bb08655bb4c989da418994b073a608dffa608b09ac04b43a791b12aeae7cd7ad919aa4c925f33b48490b5cfe6c1f71d827956071dae2e7bb3a6b74c @@ -18483,20 +18352,6 @@ __metadata: languageName: node linkType: hard -"motion@npm:10.16.2": - version: 10.16.2 - resolution: "motion@npm:10.16.2" - dependencies: - "@motionone/animation": "npm:^10.15.1" - "@motionone/dom": "npm:^10.16.2" - "@motionone/svelte": "npm:^10.16.2" - "@motionone/types": "npm:^10.15.1" - "@motionone/utils": "npm:^10.15.1" - "@motionone/vue": "npm:^10.16.2" - checksum: ea3fa2c7ce881824bcefa39b96b5e2b802d4b664b8a64644cded11197c9262e2a5b14b2e9516940e06cec37d3c39e4c79b26825c447f71ba1cfd7e3370efbe61 - languageName: node - linkType: hard - "mri@npm:^1.2.0": version: 1.2.0 resolution: "mri@npm:1.2.0" @@ -20018,6 +19873,13 @@ __metadata: languageName: node linkType: hard +"proxy-compare@npm:2.6.0": + version: 2.6.0 + resolution: "proxy-compare@npm:2.6.0" + checksum: afd82ddc83f34af6116a5e222399fb7e626a1a443feb9d70e7a1af65561c97f670c5c8c4bde53bfe12a7cda7ef00f9863d265f3a0e949ff031a9869ecc5feb0c + languageName: node + linkType: hard + "pump@npm:^3.0.0": version: 3.0.0 resolution: "pump@npm:3.0.0" @@ -22653,10 +22515,10 @@ __metadata: languageName: node linkType: hard -"tslib@npm:2.4.0": - version: 2.4.0 - resolution: "tslib@npm:2.4.0" - checksum: eb19bda3ae545b03caea6a244b34593468e23d53b26bf8649fbc20fce43e9b21a71127fd6d2b9662c0fe48ee6ff668ead48fd00d3b88b2b716b1c12edae25b5d +"tslib@npm:2.7.0": + version: 2.7.0 + resolution: "tslib@npm:2.7.0" + checksum: 469e1d5bf1af585742128827000711efa61010b699cb040ab1800bcd3ccdd37f63ec30642c9e07c4439c1db6e46345582614275daca3e0f4abae29b0083f04a6 languageName: node linkType: hard @@ -22667,13 +22529,6 @@ __metadata: languageName: node linkType: hard -"tslib@npm:^2.3.1": - version: 2.6.2 - resolution: "tslib@npm:2.6.2" - checksum: e03a8a4271152c8b26604ed45535954c0a45296e32445b4b87f8a5abdb2421f40b59b4ca437c4346af0f28179780d604094eb64546bee2019d903d01c6c19bdb - languageName: node - linkType: hard - "tsutils@npm:^3.21.0": version: 3.21.0 resolution: "tsutils@npm:3.21.0" @@ -22925,6 +22780,20 @@ __metadata: languageName: node linkType: hard +"uint8array-tools@npm:^0.0.8": + version: 0.0.8 + resolution: "uint8array-tools@npm:0.0.8" + checksum: ffc01a50aaed4ce7d9c30260b23465c79ffe6e4d0fe1ba4605611e59feabbaff81b42ddf7896a747f07aafcbb5a4252d1b39f2325bacb21454212c42c954d74d + languageName: node + linkType: hard + +"uint8array-tools@npm:^0.0.9": + version: 0.0.9 + resolution: "uint8array-tools@npm:0.0.9" + checksum: 1f3692aa60f87b84ebd3254bea2024ee9b8c1dc226ac906a879190298c736b3c942a7a12d20996d179d3918a65d4613fc2494837e8959329ac0747e12a18f90c + languageName: node + linkType: hard + "uint8arrays@npm:3.1.0": version: 3.1.0 resolution: "uint8arrays@npm:3.1.0" @@ -23415,6 +23284,18 @@ __metadata: languageName: node linkType: hard +"valibot@npm:^0.38.0": + version: 0.38.0 + resolution: "valibot@npm:0.38.0" + peerDependencies: + typescript: ">=5" + peerDependenciesMeta: + typescript: + optional: true + checksum: dd61a2299879fa644e6192ec5c67fd036b27c023b77146369ca2d720368f096ca6c9a8711f2e4b7cbac1716df5fe0e2d3eeee5028f233f87de04c08b93e98d81 + languageName: node + linkType: hard + "validate-npm-package-name@npm:^5.0.0": version: 5.0.1 resolution: "validate-npm-package-name@npm:5.0.1" @@ -23440,6 +23321,34 @@ __metadata: languageName: node linkType: hard +"valtio@npm:1.13.2, valtio@npm:^1.13.2": + version: 1.13.2 + resolution: "valtio@npm:1.13.2" + dependencies: + derive-valtio: "npm:0.1.0" + proxy-compare: "npm:2.6.0" + use-sync-external-store: "npm:1.2.0" + peerDependencies: + "@types/react": ">=16.8" + react: ">=16.8" + peerDependenciesMeta: + "@types/react": + optional: true + react: + optional: true + checksum: 514b8509308056e474c7d3ccfbbd6ac8e589740a92c53c53c78591a217e14da0694bd67f54195d8ec46920b6aab89eebab3c78c98c33d814b3606cdaacb6489b + languageName: node + linkType: hard + +"varuint-bitcoin@npm:^2.0.0": + version: 2.0.0 + resolution: "varuint-bitcoin@npm:2.0.0" + dependencies: + uint8array-tools: "npm:^0.0.8" + checksum: 63048ddcf85ef728ec610d234a1de010ce81204751d7d1a54eca9f140a86c30bb187cd4871ee042ce9e656d76ee50093a7370c56114ae6716297ef32de4a8b26 + languageName: node + linkType: hard + "vary@npm:~1.1.2": version: 1.1.2 resolution: "vary@npm:1.1.2" @@ -23447,9 +23356,9 @@ __metadata: languageName: node linkType: hard -"viem@npm:2.23.10": - version: 2.23.10 - resolution: "viem@npm:2.23.10" +"viem@npm:2.23.2": + version: 2.23.2 + resolution: "viem@npm:2.23.2" dependencies: "@noble/curves": "npm:1.8.1" "@noble/hashes": "npm:1.7.1" @@ -23457,35 +23366,35 @@ __metadata: "@scure/bip39": "npm:1.5.4" abitype: "npm:1.0.8" isows: "npm:1.0.6" - ox: "npm:0.6.9" - ws: "npm:8.18.1" + ox: "npm:0.6.7" + ws: "npm:8.18.0" peerDependencies: typescript: ">=5.0.4" peerDependenciesMeta: typescript: optional: true - checksum: 85f2f36ca586c5f6a0c50a052170b5c7a1a07fa7336fd0a1daa1fc627d9724202c66b3ddb7ccc76d6eb0f8402210ed154a74db942122cee44b1da778b366e07c + checksum: 39332d008d2ab0700aa57f541bb199350daecdfb722ae1b262404b02944e11205368fcc696cc0ab8327b9f90bf7172014687ae3e5d9091978e9d174885ccff2d languageName: node linkType: hard -"viem@npm:2.23.2": - version: 2.23.2 - resolution: "viem@npm:2.23.2" +"viem@npm:2.28.3, viem@npm:>=2.23.11": + version: 2.28.3 + resolution: "viem@npm:2.28.3" dependencies: - "@noble/curves": "npm:1.8.1" - "@noble/hashes": "npm:1.7.1" + "@noble/curves": "npm:1.8.2" + "@noble/hashes": "npm:1.7.2" "@scure/bip32": "npm:1.6.2" "@scure/bip39": "npm:1.5.4" abitype: "npm:1.0.8" isows: "npm:1.0.6" - ox: "npm:0.6.7" - ws: "npm:8.18.0" + ox: "npm:0.6.9" + ws: "npm:8.18.1" peerDependencies: typescript: ">=5.0.4" peerDependenciesMeta: typescript: optional: true - checksum: 39332d008d2ab0700aa57f541bb199350daecdfb722ae1b262404b02944e11205368fcc696cc0ab8327b9f90bf7172014687ae3e5d9091978e9d174885ccff2d + checksum: d403b03d464ddae8a121c092e96371ede39ce393caefe1b89b2f34d39c1e7751ab56e20b89da9598d9ac52ee337d340e2f41344ae74f77444978f8ed24a75775 languageName: node linkType: hard @@ -23518,12 +23427,12 @@ __metadata: languageName: node linkType: hard -"wagmi@npm:2.14.13": - version: 2.14.13 - resolution: "wagmi@npm:2.14.13" +"wagmi@npm:2.15.1": + version: 2.15.1 + resolution: "wagmi@npm:2.15.1" dependencies: - "@wagmi/connectors": "npm:5.7.9" - "@wagmi/core": "npm:2.16.5" + "@wagmi/connectors": "npm:5.8.0" + "@wagmi/core": "npm:2.17.0" use-sync-external-store: "npm:1.4.0" peerDependencies: "@tanstack/react-query": ">=5.0.0" @@ -23533,7 +23442,7 @@ __metadata: peerDependenciesMeta: typescript: optional: true - checksum: 0f833c13020641a57b8a8f96bdf8f7ed44f9985e1263f59b82f8b85aa4ee9c8d10950dbc1426ab7fb5d2860f31dde1604ab38d40225bfc6b9d2d003b6294fc06 + checksum: 117a66fc132b68f25cc936058f419eb3d153d91424403e1db6622fa56b01370bb51f7b742f3e5f66466b78f26008198752d759ebc2005462604daaa31c88a39c languageName: node linkType: hard