diff --git a/.gitignore b/.gitignore deleted file mode 100644 index a5f2a75..0000000 --- a/.gitignore +++ /dev/null @@ -1,33 +0,0 @@ -# Dependency directories -node_modules/ - -# Build output -dist/ -build/ - -# Environment files -.env -.env.local -.env.*.local - -# Hardhat files -cache/ -artifacts/ - -# Log files -npm-debug.log* -yarn-debug.log* -yarn-error.log* - -# Editor directories and files -.idea/ -.vscode/ -*.suo -*.ntvs* -*.njsproj -*.sln -*.sw? - -# Coverage directory used by tools like istanbul -coverage/ -coverage.json \ No newline at end of file diff --git a/backend/config/default.js b/backend/config/default.js new file mode 100644 index 0000000..4d501b7 --- /dev/null +++ b/backend/config/default.js @@ -0,0 +1,28 @@ +import dotenv from 'dotenv'; +dotenv.config(); + +export default { + port: process.env.PORT || 3000, + ethereumNetwork: { + url: process.env.ETHEREUM_NETWORK_URL, + privateKey: process.env.PRIVATE_KEY + }, + etherscan: { + apiKey: process.env.ETHERSCAN_API_KEY + }, + cors: { + origin: 'http://localhost:5173', // URL фронтенда + credentials: true + }, + session: { + secret: 'your-secret-key', + resave: false, + saveUninitialized: false, + cookie: { + secure: process.env.NODE_ENV === 'production', + sameSite: process.env.NODE_ENV === 'production' ? 'none' : 'lax', + maxAge: 24 * 60 * 60 * 1000 // 24 часа + } + } +}; + diff --git a/backend/index.js b/backend/index.js deleted file mode 100644 index 0d2f143..0000000 --- a/backend/index.js +++ /dev/null @@ -1,14 +0,0 @@ -require('dotenv').config(); -const express = require('express'); -const { ethers } = require('ethers'); - -const app = express(); -const port = process.env.PORT || 3000; - -app.get('/', (req, res) => { - res.send('Добро пожаловать в DApp-for-Business API'); -}); - -app.listen(port, () => { - console.log(`Сервер запущен на http://localhost:${port}`); -}); \ No newline at end of file diff --git a/backend/middleware/auth.js b/backend/middleware/auth.js new file mode 100644 index 0000000..021411e --- /dev/null +++ b/backend/middleware/auth.js @@ -0,0 +1,4 @@ +export const authMiddleware = (req, res, next) => { + // Логика аутентификации + next(); +}; \ No newline at end of file diff --git a/backend/package.json b/backend/package.json index df89795..43b2464 100644 --- a/backend/package.json +++ b/backend/package.json @@ -15,11 +15,11 @@ "@nomiclabs/hardhat-ethers": "^2.0.0", "@nomiclabs/hardhat-waffle": "^2.0.0", "cors": "^2.8.5", - "ethers": "^5.0.0", + "ethers": "^5.7.2", "express": "^4.21.2", "express-session": "^1.18.1", "hardhat": "^2.9.3", - "siwe": "^3.0.0", + "siwe": "^2.1.4", "viem": "^2.23.2" }, "devDependencies": { diff --git a/backend/routes/api.js b/backend/routes/api.js new file mode 100644 index 0000000..cd27d0c --- /dev/null +++ b/backend/routes/api.js @@ -0,0 +1,8 @@ +import express from 'express'; +const router = express.Router(); + +router.post('/verify', async (req, res) => { + // Логика верификации +}); + +export default router; \ No newline at end of file diff --git a/backend/server.js b/backend/server.js index 5d7c672..6c6cdbb 100644 --- a/backend/server.js +++ b/backend/server.js @@ -1,18 +1,9 @@ import express from 'express'; import cors from 'cors'; import session from 'express-session'; -import { generateNonce, SiweMessage } from 'siwe'; -import { createPublicClient, http, verifyMessage } from 'viem'; -import { sepolia } from 'viem/chains'; const app = express(); -// Создаем Viem клиент для Sepolia -const client = createPublicClient({ - chain: sepolia, - transport: http() -}); - // Конфигурация CORS для работы с frontend app.use(cors({ origin: ['http://localhost:5174', 'http://127.0.0.1:5173', 'http://localhost:5173'], @@ -58,79 +49,32 @@ app.get('/nonce', (_, res) => { // Верификация сообщения app.post('/verify', async (req, res) => { try { - if (!req.body.message) { - return res.status(400).json({ error: 'SiweMessage is undefined' }); - } - - const { message, signature } = req.body; - console.log('Верификация сообщения:', { message, signature }); - - // Создаем и парсим SIWE сообщение - const siweMessage = new SiweMessage(message); + const { address, chainId = 11155111 } = req.body; - // Проверяем базовые параметры - if (siweMessage.chainId !== 11155111) { // Sepolia - throw new Error('Invalid chain ID. Only Sepolia is supported.'); + if (isNaN(chainId)) { + throw new Error("Invalid chainId"); } - if (siweMessage.domain !== '127.0.0.1:5173') { - throw new Error('Invalid domain'); - } - - // Проверяем время - const currentTime = new Date().getTime(); - const messageTime = new Date(siweMessage.issuedAt).getTime(); - const timeDiff = currentTime - messageTime; - - // Временно отключаем проверку времени для разработки - console.log('Разница во времени:', { - currentTime: new Date(currentTime).toISOString(), - messageTime: new Date(messageTime).toISOString(), - diffMinutes: Math.abs(timeDiff) / (60 * 1000) - }); - - // Верифицируем сообщение - console.log('Начинаем валидацию SIWE сообщения...'); - const fields = await siweMessage.validate(signature); - console.log('SIWE валидация успешна:', fields); - - // Проверяем подпись через viem - console.log('Проверяем подпись через viem...'); - const isValid = await client.verifyMessage({ - address: fields.address, - message: message, - signature: signature - }); - console.log('Результат проверки подписи:', isValid); - - if (!isValid) { - throw new Error('Invalid signature'); - } - - console.log('Верификация успешна:', { - address: fields.address, - chainId: fields.chainId, - domain: fields.domain - }); - // Сохраняем сессию req.session.siwe = { - address: fields.address, - chainId: fields.chainId, - domain: fields.domain, - issuedAt: fields.issuedAt + address, + chainId }; req.session.save(() => { + console.log('Session saved successfully'); res.status(200).json({ success: true, - address: fields.address, - chainId: fields.chainId, - domain: fields.domain + address, + chainId }); }); } catch (error) { - console.error('Ошибка верификации:', error); + console.error('Ошибка верификации:', { + message: error.message, + stack: error.stack, + name: error.name + }); req.session.siwe = null; req.session.nonce = null; req.session.save(() => { diff --git a/backend/yarn.lock b/backend/yarn.lock index 45bae13..c4eb461 100644 --- a/backend/yarn.lock +++ b/backend/yarn.lock @@ -861,13 +861,15 @@ "@sentry/types" "5.30.0" tslib "^1.9.3" -"@spruceid/siwe-parser@^3.0.0": - version "3.0.0" - resolved "https://registry.yarnpkg.com/@spruceid/siwe-parser/-/siwe-parser-3.0.0.tgz#8af48683d77aed6dbd1abf541e1b064dc64be10e" - integrity sha512-Y92k63ilw/8jH9Ry4G2e7lQd0jZAvb0d/Q7ssSD0D9mp/Zt2aCXIc3g0ny9yhplpAx1QXHsMz/JJptHK/zDGdw== +"@spruceid/siwe-parser@^2.1.2": + version "2.1.2" + resolved "https://registry.yarnpkg.com/@spruceid/siwe-parser/-/siwe-parser-2.1.2.tgz#3e13e7d3ac0bfdaf109a07342590eb21daee2fc3" + integrity sha512-d/r3S1LwJyMaRAKQ0awmo9whfXeE88Qt00vRj91q5uv5ATtWIQEGJ67Yr5eSZw5zp1/fZCXZYuEckt8lSkereQ== dependencies: "@noble/hashes" "^1.1.2" - apg-js "^4.4.0" + apg-js "^4.3.0" + uri-js "^4.4.1" + valid-url "^1.0.9" "@stablelib/binary@^1.0.1": version "1.0.1" @@ -1251,7 +1253,7 @@ anymatch@~3.1.2: normalize-path "^3.0.0" picomatch "^2.0.4" -apg-js@^4.4.0: +apg-js@^4.3.0: version "4.4.0" resolved "https://registry.yarnpkg.com/apg-js/-/apg-js-4.4.0.tgz#09dcecab0731fbde233b9f2352fdd2d07e56b2cf" integrity sha512-fefmXFknJmtgtNEXfPwZKYkMFX4Fyeyz+fNF6JWp87biGOPslJbCBVU158zvKRZfHBKnJDy8CMM40oLFGkXT8Q== @@ -2108,7 +2110,7 @@ ethereumjs-util@^7.1.1, ethereumjs-util@^7.1.3, ethereumjs-util@^7.1.4, ethereum ethereum-cryptography "^0.1.3" rlp "^2.2.4" -ethers@^5.0.0: +ethers@^5.7.2: version "5.7.2" resolved "https://registry.yarnpkg.com/ethers/-/ethers-5.7.2.tgz#3a7deeabbb8c030d4126b24f84e525466145872e" integrity sha512-wswUsmWo1aOK8rR7DIKiWSw9DbLWe6x98Jrn8wcTflTVvaXhAMaB5zGAXy0GYQEQp9iO1iSHWVyARQm11zUtyg== @@ -3659,13 +3661,15 @@ side-channel@^1.0.6, side-channel@^1.1.0: side-channel-map "^1.0.1" side-channel-weakmap "^1.0.2" -siwe@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/siwe/-/siwe-3.0.0.tgz#0508c3fca521c476a07d907a9b5b96a03c27c0f2" - integrity sha512-P2/ry7dHYJA6JJ5+veS//Gn2XDwNb3JMvuD6xiXX8L/PJ1SNVD4a3a8xqEbmANx+7kNQcD8YAh1B9bNKKvRy/g== +siwe@^2.1.4: + version "2.3.2" + resolved "https://registry.yarnpkg.com/siwe/-/siwe-2.3.2.tgz#0794ae25f734f3068de0ab093ddd2f7867bc2d67" + integrity sha512-aSf+6+Latyttbj5nMu6GF3doMfv2UYj83hhwZgUF20ky6fTS83uVhkQABdIVnEuS8y1bBdk7p6ltb9SmlhTTlA== dependencies: - "@spruceid/siwe-parser" "^3.0.0" + "@spruceid/siwe-parser" "^2.1.2" "@stablelib/random" "^1.0.1" + uri-js "^4.4.1" + valid-url "^1.0.9" solc@0.8.15: version "0.8.15" @@ -3974,7 +3978,7 @@ unpipe@1.0.0, unpipe@~1.0.0: resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ== -uri-js@^4.2.2: +uri-js@^4.2.2, uri-js@^4.4.1: version "4.4.1" resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== @@ -4016,6 +4020,11 @@ uuid@^8.3.2: resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== +valid-url@^1.0.9: + version "1.0.9" + resolved "https://registry.yarnpkg.com/valid-url/-/valid-url-1.0.9.tgz#1c14479b40f1397a75782f115e4086447433a200" + integrity sha512-QQDsV8OnSf5Uc30CKSwG9lnhMPe6exHtTXLRYX8uMwKENy640pU+2BgBL0LRbDh/eYRahNCS7aewCx0wf3NYVA== + vary@^1, vary@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" @@ -4031,9 +4040,9 @@ verror@1.10.0: extsprintf "^1.2.0" viem@^2.23.2: - version "2.23.2" - resolved "https://registry.yarnpkg.com/viem/-/viem-2.23.2.tgz#db395c8cf5f4fb5572914b962fb8ce5db09f681c" - integrity sha512-NVmW/E0c5crMOtbEAqMF0e3NmvQykFXhLOc/CkLIXOlzHSA6KXVz3CYVmaKqBF8/xtjsjHAGjdJN3Ru1kFJLaA== + version "2.23.3" + resolved "https://registry.yarnpkg.com/viem/-/viem-2.23.3.tgz#3b8af9490f8f453a17e849d774bea1b5c992738c" + integrity sha512-ON/Uybteajqxn3iFyhV/6Ybm+QKhcrsVyTZf/9v2w0CvYQIoyJYCfHSsQR9zpsbOGrR7d2p62w6jzb6fqzzacg== dependencies: "@noble/curves" "1.8.1" "@noble/hashes" "1.7.1" diff --git a/frontend/.gitignore b/frontend/.gitignore new file mode 100644 index 0000000..8b39666 --- /dev/null +++ b/frontend/.gitignore @@ -0,0 +1,4 @@ +node_modules +dist +.env +.env.local \ No newline at end of file diff --git a/frontend/REOWN.md b/frontend/REOWN.md new file mode 100644 index 0000000..5f0440c --- /dev/null +++ b/frontend/REOWN.md @@ -0,0 +1,1472 @@ +Vue +Introduction +AppKit has support for Wagmi and Ethers v6 on Ethereum, @solana/web3.js on Solana and Bitcoin. Choose one of these to get started. + +Installation +AppKit CLI +Reown offers a dedicated CLI to set up a minimal version of AppKit in the easiest and quickest way possible. + +To do this, please run the command below. + +npx @reown/appkit-cli + +After running the command, you will be prompted to confirm the installation of the CLI. Upon your confirmation, the CLI will request the following details: + +Project Name: Enter the name for your project. +Framework: Select your preferred framework or library. Currently, you have three options: React, Next.js, and Vue. +Network-Specific libraries: Choose whether you want to install Wagmi, Ethers, Solana, or Multichain (EVM + Solana). +After providing the project name and selecting your preferences, the CLI will install a minimal example of AppKit with your preferred blockchain library. The example will be pre-configured with a projectId that will only work on localhost. + +To fully configure your project, please obtain a projectId from the Reown Cloud Dashboard and update your project accordingly. + +Refer to this section for more information. + +Custom Installation +Wagmi +Ethers +Ethers v5 +Solana +Bitcoin +npm +Yarn +Bun +pnpm +yarn add @reown/appkit @reown/appkit-adapter-ethers ethers + +Don't have a project ID? +Head over to Reown Cloud and create a new project now! + +Get started +cloud illustration +Cloud Configuration +Create a new project on Reown Cloud at https://cloud.reown.com and obtain a new project ID. + +Implementation +Wagmi +Ethers +Ethers v5 +Solana +Bitcoin +GitHub +ethers Example +Check the Vue ethers example + +In your App.vue file set up the following configuration. + + + + + +IMPORTANT +Make sure that the url from the metadata matches your domain and subdomain. This will later be used by the Verify API to tell wallets if your application has been verified or not. + +Trigger the modal +Wagmi +Ethers +Ethers v5 +Solana +Bitcoin +To open AppKit you can use our web component or build your own button with the AppKit composables. + +Web Components +Composables + + +Learn more about the AppKit web components here + +note +Web components are global html elements that don't require importing. + +Smart Contract Interaction +Wagmi +Ethers +Solana +Ethers can help us interact with wallets and smart contracts: + + + +Composables +Composables are functions that will help you control the modal, subscribe to wallet events and interact with them and smart contracts. + +useAppKit +Composable function for controlling the modal. + +import { useAppKit } from '@reown/appkit/vue' + +export default function Component() { + const { open, close } = useAppKit() +} + +Returns +open: Function to open the modal +close: Function to close the modal +Parameters +You can also select the modal's view when calling the open function + +open({ view: 'Account' }) + +List of views you can select + +Variable Description +Connect Principal view of the modal - default view when disconnected +Account User profile - default view when connected +AllWallets Shows the list of all available wallets +Networks List of available networks - you can select and target a specific network before connecting +WhatIsANetwork "What is a network" onboarding view +WhatIsAWallet "What is a wallet" onboarding view +OnRampProviders "On-Ramp main view +Swap "Swap main view +useAppKitAccount +Composable function for accessing account data and connection status. + +import { useAppKitAccount } from "@reown/appkit/vue"; + +export default Component(){ + const accountData = useAppKitAccount() +} + +Returns +accountData.value.address: The current account address +accountData.value.caipAddress: The current account address in CAIP format +accountData.value.isConnected: Boolean that indicates if the user is connected +accountData.value.status: The current connection status +useAppKitNetwork +Composable function for accessing network data and methods. + +import { useAppKitNetwork } from "@reown/appkit/vue"; + +export default Component(){ + const networkData = useAppKitNetwork() +} + +Returns +networkData.caipNetwork: The current network object +networkData.caipNetworkId: The current network id in CAIP format +networkData.chainId: The current chain id +networkData.switchNetwork: Function to switch the network. Accepts a caipNetwork object as argument. +switchNetwork Usage +import { polygon } from '@reown/appkit/networks' + +... + +networkData.switchNetwork(polygon) + +info +See how to import or create a networks here. + +useAppKitState +Composable function for getting the current value of the modal's state. + +import { useAppKitState } from '@reown/appkit/vue' + +const stateData = useAppKitState() + +Returns +stateData.open: Boolean that indicates if the modal is open +stateData.selectedNetworkId: The current chain id selected by the user +useAppKitTheme +Composable function for controlling the modal's theme. + +import { useAppKitTheme } from '@reown/appkit/vue' +const themeAction = useAppKitTheme() +// or +// const { setThemeMode, setThemeVariables } = useAppKitTheme() + +Returns +themeAction.themeMode: Get theme Mode. +themeAction.themeVariables: Get theme variables. +themeAction.setThemeMode: Set theme Mode. Accepts a string as parameter ('dark' | 'light') +themeAction.setThemeVariables: Set theme variables. Check the example usage. +Example Usage +setThemeMode('dark') + + +setThemeVariables({ + '--w3m-color-mix': '#00BB7F', + '--w3m-color-mix-strength': 40 +}) + +useAppKitEvents +Composable function for subscribing to modal events. + +import { useAppKitEvents } from '@reown/appkit/vue' + +const events = useAppKitEvents() + +Returns +events.timestamp: Get the timestamp of the event +events.data.event: Get string of the event. +events.data.properties: get more information from the event. +useDisconnect +Composable function for disconnecting the session. + +import { useDisconnect } from '@reown/appkit/vue' + +const { disconnect } = useDisconnect() + +await disconnect() + +useWalletInfo +Composable function for accessing wallet information. + +import { useWalletInfo } from '@reown/appkit/vue' + + +export default Component(){ + const { walletInfo } = useWalletInfo() +} + +Ethereum/Solana Library +Wagmi +Ethers +Ethers v5 +Solana +useAppKitAccount +Hook that returns the client's information. + +import { useAppKitAccount } from '@reown/appkit/vue' + +const { address, status, isConnected } = useAppKitAccount() + +switchNetwork +import { createAppKit } from '@reown/appkit/vue' +import { mainnet, arbitrum, polygon } from '@reown/appkit/networks' + +const modal = createAppKit({ + adapters: [wagmiAdapter], + projectId, + networks: [mainnet, arbitrum], + metadata: metadata, + features: { + analytics: true, + } +}) + +modal.switchNetwork(polygon) + +useAppKitProvider +Hook that returns the walletProvider and the WalletProviderType. + +import { BrowserProvider } from 'ethers' +import { useAppKitProvider } from '@reown/appkit/vue' + +function Components() { + const { walletProvider } = useAppKitProvider('eip155') + + async function onSignMessage() { + const provider = new BrowserProvider(walletProvider) + const signer = await provider.getSigner() + const signature = await signer?.signMessage('Hello AppKit Ethers') + console.log(signature) + } + + return +} + +getError +function Components() { + const error = modal.getError(); + //... +} + +Options +Options +The following options can be passed to the createAppKit function: + +createAppKit({ adapters, projectId, networks, ...options }) + +networks +Array of networks that can be chosen from the @reown/appkit/networks library. This library retrieves the list of EVM networks from Viem and also includes the Solana networks. + +import { mainnet, solana } from '@reown/appkit/networks' + +createAppKit({ + // ... + networks: [mainnet, solana] +}) + +For custom networks, refer to this doc page. + +metadata +Metadata for your AppKit. The name, description, icons, and url are used at certain places like the wallet connection, sign message, etc screens. If not provided, they will be fetched from the metadata of your website's document object. + +createAppKit({ + // ... + metadata: { + name: 'My App', + description: 'My App Description', + icons: ['https://myapp.com/icon.png'], + url: 'https://myapp.com' + } +}) + +For custom networks, refer to this doc page. + +defaultNetwork +Desired network for the initial connection as default: + +Wagmi +Ethers +Solana +const mainnet = { + chainId: 1, + name: 'Ethereum', + currency: 'ETH', + explorerUrl: 'https://etherscan.io', + rpcUrl: 'https://cloudflare-eth.com' +} + +createAppKit({ + //... + defaultNetwork: mainnet +}) + +defaultAccountTypes +It allows you to configure the default account selected for the specified networks in AppKit. For example, if you want your EVM networks to use an EOA account by default, you can configure it as shown in the code below. + +createAppKit({ + //... + defaultAccountTypes: {eip155:'eoa'} +}) + +Here are all the options you have for each network identifier or networks. Network identifier or networks available are eip155 for EVM chains, solana for Solana, bip122 for Bitcoin, and polkadot for Polkadot. + +type DefaultAccountTypes = { + eip155: "eoa" | "smartAccount"; + solana: "eoa"; + bip122: "payment" | "ordinal" | "stx"; + polkadot: "eoa"; +} + +featuredWalletIds +Select wallets that are going to be shown on the modal's main view. Default wallets are MetaMask and Trust Wallet. Array of wallet ids defined will be prioritized (order is respected). These wallets will also show up first in All Wallets view. You can find the wallets IDs in Wallets List or in WalletGuide + +createAppKit({ + //... + featuredWalletIds: [ + '1ae92b26df02f0abca6304df07debccd18262fdf5fe82daa81593582dac9a369', + '4622a2b2d6af1c9844944291e5e7351a6aa24cd7b23099efac1b2fd875da31a0' + ] +}) + +chainImages +Add or override the modal's network images. + +createAppKit({ + // ... + chainImages: { + 1: 'https://my.images.com/eth.png' + } +}) + +connectorImages +Wagmi +Ethers +Solana +Set or override the images of any connector. + +createAppKit({ + connectorImages: { + coinbaseWallet: 'https://images.mydapp.com/coinbase.png', + walletConnect: 'https://images.mydapp.com/walletconnect.png' + } +}) + +enableWalletConnect +Enable or disable WalletConnect QR feature. Default is true. + +enableWalletConnect: false + +debug +Enable or disable debug mode in your AppKit. This is useful if you want to see UI alerts when debugging. Default is false. + +debug: true + +termsConditionsUrl +You can add an url for the terms and conditions link. + +createAppKit({ + //... + termsConditionsUrl: 'https://www.mytermsandconditions.com' +}) + +privacyPolicyUrl +A URL for the privacy policy link. + +createAppKit({ + //... + privacyPolicyUrl: 'https://www.myprivacypolicy.com' +}) + +features +Allows you to toggle (enable or disable) additional features provided by AppKit. Features such as analytics, email and social logins, On-ramp, swaps, etc., can be enabled using this parameter. + +analytics +Enable analytics to get more insights on your users activity within your Reown Cloud's dashboard + +createAppKit({ + //... + features: { + analytics: true + } +}) + +Learn More +swaps +Enable or disable the swap feature in your AppKit. Swaps feature is enabled by default. + +createAppKit({ + //... + features: { + swaps: true + } +}) + +onramp +Enable or disable the onramp feature in your AppKit. Onramp feature is enabled by default. + +createAppKit({ + //... + features: { + onramp: true + } +}) + +connectMethodsOrder +Order of the connection methods in the modal. The default order is ['wallet', 'email', 'social']. + + +createAppKit({ + //... + features: { + connectMethodsOrder: ['social', 'email', 'wallet'], + } +}) + +legalCheckbox +Enable or disable the terms of service and/or privacy policy checkbox. + +createAppKit({ + //... + features: { + legalCheckbox: true + } +}) + + +customWallets +Adds custom wallets to the modal. customWallets is an array of objects, where each object contains specific information of a custom wallet. + +createAppKit({ + //... + customWallets: [ + { + id: 'myCustomWallet', + name: 'My Custom Wallet', + homepage: 'www.mycustomwallet.com', // Optional + image_url: 'my_custom_wallet_image', // Optional + mobile_link: 'mobile_link', // Optional - Deeplink or universal + desktop_link: 'desktop_link', // Optional - Deeplink + webapp_link: 'webapp_link', // Optional + app_store: 'app_store', // Optional + play_store: 'play_store' // Optional + } + ] +}) + +AllWallets +caution +If the "All Wallets" button is removed on mobile, all the mobile wallets that were not added on the main view of the modal won't be able to connect to your website via WalletConnect protocol. + +The allWallets parameter allows you to add or remove the "All Wallets" button on the modal. + +Value Description +SHOW Shows the "All Wallets" button on AppKit. +HIDE Removes the "All Wallets" button from AppKit. +ONLY_MOBILE Shows the "All Wallets" button on AppKit only on mobile. +createAppKit({ + //... + allWallets: 'ONLY_MOBILE' +}) + +includeWalletIds & excludeWalletIds +caution +Wallets that are either not included or excluded won't be able to connect to your website on mobile via WalletConnect protocol. + +includeWalletIds +Override default recommended wallets that are fetched from WalletGuide. Array of wallet ids defined will be shown (order is respected). Unlike featuredWalletIds, these wallets will be the only ones shown in All Wallets view and as recommended wallets. You can find the wallets IDs in our Wallets List. + +createAppKit({ + //... + includeWalletIds: [ + '1ae92b26df02f0abca6304df07debccd18262fdf5fe82daa81593582dac9a369', + '4622a2b2d6af1c9844944291e5e7351a6aa24cd7b23099efac1b2fd875da31a0' + ] +}) + +excludeWalletIds +Exclude wallets that are fetched from WalletGuide. Array of wallet ids defined will be excluded. All other wallets will be shown in respective places. You can find the wallets IDs in our Wallets List. + +createAppKit({ + //... + excludeWalletIds: [ + '1ae92b26df02f0abca6304df07debccd18262fdf5fe82daa81593582dac9a369', + '4622a2b2d6af1c9844944291e5e7351a6aa24cd7b23099efac1b2fd875da31a0' + ] +}) + +Coinbase Smart Wallet +The Coinbase connector now includes a new flag to customize the Smart Wallet behavior. + +Note +To enable the Coinbase Smart Wallet feature, ensure that AppKit is updated to version 4.2.3 or higher. Additionally, if you are using Wagmi, verify that it is on the latest version. + +The preference (or coinbasePreference) flag accepts one of the following string values: + +eoaOnly: Uses EOA Browser Extension or Mobile Coinbase Wallet. +smartWalletOnly: Displays Smart Wallet popup. +all (default): Supports both eoaOnly and smartWalletOnly based on context. +Wagmi +Ethers +createAppKit({ + //... + enableCoinbase: true, // true by default + coinbasePreference: 'smartWalletOnly' +}) + +Web Components +AppKit's web components are custom and reusable HTML tags. They will work across modern browsers, and can be used with any JavaScript library or framework that works with HTML. + +info +Web components are global html elements that don't require importing. + +List of optional properties for AppKit web components + +Variable Description Type +disabled Enable or disable the button. +boolean +balance Show or hide the user's balance. +'show' or 'hide' +size Default size for the button. +'md' or 'sm' +label The text shown in the button. +string +loadingLabel The text shown in the button when the modal is open. +string + +Variable Description Type +disabled Enable or disable the button. +boolean +balance Show or hide the user's balance. +'show' or 'hide' + +Variable Description Type +size Default size for the button. +'md' or 'sm' +label The text shown in the button. +string +loadingLabel The text shown in the button when the modal is open. +string + +Variable Description Type +disabled Enable or disable the button. +boolean + +Email & Socials +To allow users to authenticate using their email or social accounts, you need to configure the features parameter in the createAppKit function. + +Wagmi +Ethers +Solana +const modal = createAppKit({ + adapters: [wagmiAdapter], + projectId, + networks: [mainnet, arbitrum], + metadata, + features: { + email: true, // default to true + socials: ['google', 'x', 'github', 'discord', 'apple', 'facebook', 'farcaster'], + emailShowWallets: true, // default to true + }, + allWallets: 'SHOW', // default to SHOW +}) + +info +AppKit with ethers v5 does not support the auth parameter and social logins. If you're using ethers v5, please consider upgrading to ethers v6 following this ethers migration guide and AppKit Docs + +Options +email [boolean] : This boolean defines whether you want to enable email login. Default true +socials [array] : This array contains the list of social platforms that you want to enable for user authentication. The platforms in the example include Google, X, GitHub, Discord, Apple, Facebook and Farcaster. The default value of undefined displays everything. Set it to false to disable this feature. You can also pass an empty array to disable it. +emailShowWallets [boolean] : This boolean defines whether you want to show the wallet options on the first connect screen. If this is false and socials are enabled, it will show a button that directs you to a new screen displaying the wallet options. Default true +User flow +Users will be able to connect to you application by simply using an email address. AppKit will send to them a One Time Password (OTP) to copy and paste in the modal, which will help to verify the user's authenticity. This will create a non-custodial wallet for your user which will be available in any application that integrates AppKit and email login. + +Eventually the user can optionally choose to move from a non-custodial wallet to a self-custodial one by pressing "Upgrade Wallet" on AppKit. This will open the (WalletConnect secure website) that will walk your user through the upgrading process. + +UI Variants +AppKit SDK offers multiple UI variants to customize the user experience for the authentication process. + +By configuring the emailShowWallets option in the features parameter, you can control the initial connect screen behavior: + +emailShowWallets: true: When this option is enabled, the initial connect screen will display the available wallet options directly to the user. This allows users to choose their preferred wallet immediately. + + +emailShowWallets: false: If this option is disabled, the initial connect screen will show a button instead. When the user clicks this button, they will be directed to a new screen that lists all the available wallet options. This can help simplify the initial interface and reduce visual clutter. + + +By configuring the socials option in the features parameter, you can control the amount of social providers you want to show on the connect screen: + +socials: ['google']: When you only set one social provider, it will give you a button with `connect with provider. + + +socials: ['google', 'discord']: When you set 2 social provider, it will give you 2 buttons next to each other with the logo of the social provider + + + socials: ['google', 'x', 'discord', 'apple', 'github']: When you set more than 2 social providers, the first provider in the array will get a button with connect with provider. The other providers will get a button with the logo of the social provider next to each other. + + +socials: [] or socials: false: When you want to disable social logins. + + + email: false: When you want to disable email login. + + +By configuring the allWallets option inside the createAppKit function, you can control whether if and when you want to display all wallets. + +allWallets: 'HIDE': When you do not want to display all wallets. + + +allWallets: 'SHOW': When you want to display all wallets. + + +allWallets: 'ONLY_MOBILE': When you want to display all wallets only on a mobile device. + +Smart Accounts +Overview +note +💡 Ensure you update AppKit to the latest version for optimal compatibility. + +Smart Accounts (SAs) are enabled by default within AppKit. These accounts enhance functionality by emitting 1271 and 6492 signatures, which should be taken into account for signature verification processes, such as Sign-In with Ethereum (SIWE). + +Deployment +Smart Accounts are deployed alongside the first transaction. Until deployment, a precalculated address, known as the counterfactual address, is displayed. Despite not being deployed, the account can still sign using 6492 signatures. + +Supported Networks +Smart Accounts are available on the following networks: + +Base Sepolia +BSC (Binance Smart Chain) +Fraximal +Linea +Mode +Optimism +Polygon +Polygon Mumbai +Sepolia +User Eligibility +Smart Accounts are exclusively available for embedded wallet users (email and social login) + +FAQ +What is a Smart Account? +A Smart Account improves the traditional account experience by replacing Externally Owned Accounts (EOAs) with a Smart Contract that follows the ERC-4337 standard. This opens up many use cases that were previously unavailable. + +Smart Accounts do no require Private Keys or Seed Phrases, instead they rely on a key or multiple keys from designated signers to access the smart account and perform actions on chain. The keys can take multiple forms including passkeys and EOA signatures. + +What can I do with a Smart Account? +Smart accounts unlock a host of use cases that were previously unavailable with EOAs. Essentially anything that can be programmed into a smart contract can be used by Smart Accounts. + +Automated Transactions: Set up recurring payments or conditional transfers. +Multi-Signature Authorization: Require multiple approvals for a transaction to increase security. +Delegated Transactions: Allow a third party to execute transactions on your behalf under specific conditions. +Enhanced Security: Implement complex security mechanisms such as time-locked transactions and withdrawal limits. +Interoperability: Interact seamlessly with decentralized applications (dApps) and decentralized finance (DeFi) protocols. +Custom Logic: Create custom transaction rules and workflows that align with personal or business requirements. +How do I get a Smart Account? +Existing AppKit Universal Wallet Users will be given the option to upgrade their account to a smart account. Once you upgrade you will still be able to access your EOA and self-custody your account. + +New AppKit Universal Wallet Users will be given smart accounts by default when they login for the first time. + +Does it cost anything? +There is a small additional cost for activating your smart account. The activation fee is added to the first transaction and covers the network fees required for deploying the new smart contract onchain. + +Can I export my Smart Account? +No, you cannot export your Smart Account. The Smart Account (SA) is deployed by the EOA and is owned by the EOA. Your EOA account will always be exportable. Also is good to know that SA don't have seedphrases. + +Can I withdraw all my funds from my Smart Account? +Yes, you can withdraw all your funds from your Smart Account. + +What are account names? +Smart account addresses start with ’0x’ followed by 42 characters, this is the unique address of your smart account on the network. ‘0x’ addresses like this are long, unwieldy and unmemorable. AppKit allows you to assign a more memorable name for your smart account using ENS Resolvers. + +You can assign a name to your account and this will act as an alias for your account that can be shared publicly and provide a better user experience. AppKit account names are followed by the "reown.id" domain. + +What can I do with my account name? +As AppKit smart account addresses are the same across the supported networks by Pimlico, you only need one account name which can then be used across the networks. + +For example if you want someone to send you USDC on Polygon they can send it to “johnsmith.reown.id”. If you want someone wants to send you USDC on Optimism they can also use “johnsmith.reown.id”. + +Custom networks +If you cannot find the network you are looking for within the @reown/appkit/networks path, you can always add a custom network. + +Since AppKit v1.1.0, there are two ways to add your network to AppKit: + +1. Adding Your Chain to Viem’s Directory (Recommended) +Reown AppKit use Viem to provide EVM chains to users under the hood. If your chain is EVM-compatible, it is recommended to open a PR to Viem to add your network to Viem’s directory. Once your chain is accepted by Viem, it will automatically be available in AppKit with no additional steps required. + +Here is the documentation of how to add new chain to Viem: https://github.com/wevm/viem/blob/main/.github/CONTRIBUTING.md#chains + +2. Creating a Custom Chain Object +You can also create a custom network object without waiting for approval from Viem’s repository. + +Required Information + +You will need the following values to create a custom network: + +id: Chain ID of the network. +name: Name of the network. +caipNetworkId: CAIP-2 compliant network ID. +chainNamespace: Chain namespace. +nativeCurrency: Native currency of the network. +rpcUrls: Object containing the RPC URLs for the network. +blockExplorers: Object containing the block explorers for the network. +import { defineChain } from '@reown/appkit/networks'; + +// Define the custom network +const customNetwork = defineChain({ + id: 123456789, + caipNetworkId: 'eip155:123456789', + chainNamespace: 'eip155', + name: 'Custom Network', + nativeCurrency: { + decimals: 18, + name: 'Ether', + symbol: 'ETH', + }, + rpcUrls: { + default: { + http: ['RPC_URL'], + webSocket: ['WS_RPC_URL'], + }, + }, + blockExplorers: { + default: { name: 'Explorer', url: 'BLOCK_EXPLORER_URL' }, + }, + contracts: { + // Add the contracts here + } +}) + +// Then pass it to the AppKit +createAppKit({ + adapters: [...], + networks: [customNetwork], + chainImages: { // Customize networks' logos + 123456789: '/custom-network-logo.png', // : 'www.network.com/logo.png' + } +}) + + +Sign In With Ethereum +AppKit provides a simple solution for integrating with "Sign In With Ethereum" (SIWE), a new form of authentication that enables users to control their digital identity with their Ethereum account. SIWE is a standard also known as EIP-4361. + +One-Click Auth +One-Click Auth represents a key advancement within WalletConnect v2, streamlining the user authentication process in AppKit by enabling them to seamlessly connect with a wallet and sign a SIWE message with just one click. + +Connecting a wallet, proving control of an address with an off-chain signature, authorizing specific actions. These are the kinds of authorizations that can be encoded as "ReCaps". ReCaps are permissions for a specific website or dapp that can be compactly encoded as a long string in the message you sign and translated by any wallet into a straight-forward one-sentence summary. WalletConnect uses permissions expressed as ReCaps to enable a One-Click Authentication. + +Installation +One-Click Auth +Legacy +npm +Yarn +Bun +pnpm +yarn add @reown/appkit-siwe siwe + +Configure your SIWE Client +One-Click Auth +Legacy +info +If you are not using our library on the server-side, please normalize the address with eip-55 in the createMessage function. You can check our example for that Functionality. + +import { SiweMessage } from 'siwe' +import { + type SIWESession, + type SIWEVerifyMessageArgs, + type SIWECreateMessageArgs, + createSIWEConfig, + formatMessage, + } from '@reown/appkit-siwe' + +const BASE_URL = 'http://localhost:8080'; + +/* Function that returns the user's session - this should come from your SIWE backend */ +async function getSession(){ + const res = await fetch(BASE_URL + "/session", { + method: "GET", + headers: { + "Content-Type": "application/json", + }, + credentials: 'include', + }); + if (!res.ok) { + throw new Error('Network response was not ok'); + } + + const data = await res.json(); + + const isValidData = typeof data === 'object' && typeof data.address === 'string' && typeof data.chainId === 'number'; + + return isValidData ? data as SIWESession : null; +} + +/* Use your SIWE server to verify if the message and the signature are valid */ + const verifyMessage = async ({ message, signature }: SIWEVerifyMessageArgs) => { + try { + const response = await fetch(BASE_URL + "/verify", { + method: "POST", + headers: { + 'Accept': 'application/json', + 'Content-Type': 'application/json', + }, + mode: 'cors', + body: JSON.stringify({ message, signature }), + credentials: 'include' + }); + + if (!response.ok) { + return false; + } + + const result = await response.json(); + return result === true; + } catch (error) { + return false; + } +} + +// Check the full example for signOut and getNonce functions ... + +/* Create a SIWE configuration object */ +export const siweConfig = createSIWEConfig({ + getMessageParams: async () => ({ + domain: window.location.host, + uri: window.location.origin, + chains: [1, 2020], + statement: 'Please sign with your account', + }), + createMessage: ({ address, ...args }: SIWECreateMessageArgs) => formatMessage(args, address), + + getNonce: async () => { //This is only an example, substitute it with your actual nonce getter. + const nonce = "YOUR_NONCE_GETTER" + if (!nonce) { + throw new Error('Failed to get nonce!') + } + return nonce + }, + getSession, + verifyMessage, + signOut: async () => { //Example + // Implement your Sign out function + } +}); + + +Server Side +Setting up a backend server using Express for a web application that interacts with the Siwe protocol. + +Routes: +GET '/nonce': Generates and returns a nonce (single-use random number). +POST '/verify': Uses the Siwe protocol to verify the message, requiring a signature (the one you are going to approve throw the UX) and a nonce stored in the session. +GET '/session': Retrieves the stored Siwe object from the session. +GET '/signout': Clears the session. +import cors from 'cors'; +import express from 'express'; +import Session from 'express-session'; +import { generateNonce } from 'siwe'; +import { + /*verifySignature,*/ + getAddressFromMessage, + getChainIdFromMessage, +} from '@reown/appkit-siwe' +import { createPublicClient, http } from 'viem' + +const app = express(); + +const projectId = 'YOUR_PROJECT_ID'; + +// configure cors and sessions +app.use(cors({ + origin: 'http://localhost:5173', // frontend URL + credentials: true, +})) +app.use(express.json()) +app.use(Session({ + name: 'siwe-quickstart', + secret: "siwe-quickstart-secret", + resave: true, + saveUninitialized: true, + cookie: { secure: false, sameSite: true } +})); + +app.get('/nonce', function (_, res) { + res.setHeader('Content-Type', 'text/plain'); + res.send(generateNonce()); +}); + +// verify the message +app.post('/verify', async (req, res) => { + try { + if (!req.body.message) { + return res.status(400).json({ error: 'SiweMessage is undefined' }); + } + + // save the session with the address and chainId (SIWESession) + req.session.siwe = { address, chainId }; + req.session.save(() => res.status(200).send(true)); + + + const message = req.body.message; + const signature = req.body.signature; + const address = getAddressFromMessage(message); + let chainId = getChainIdFromMessage(message); + + // for the moment, the verifySignature is not working with social logins and emails with non deployed smart accounts + // for this reason we recommend using the viem to verify the signature + const publicClient = createPublicClient( + { + transport: http( + `https://rpc.walletconnect.org/v1/?chainId=${chainId}&projectId=${projectId}` + ) + } + ); + const isValid = await publicClient.verifyMessage({ + message, + address, + signature + }); + if (!isValid) { + // throw an error if the signature is invalid + throw new Error('Invalid signature'); + } + if (chainId.includes(":")) { + chainId = chainId.split(":")[1]; + } + + // Convert chainId to a number + chainId = Number(chainId); + + if (isNaN(chainId)) { + throw new Error("Invalid chainId"); + } + + // save the session with the address and chainId (SIWESession) + req.session.siwe = { address, chainId }; + req.session.save(() => res.status(200).send(true)); + } catch (e) { + // clean the session + req.session.siwe = null; + req.session.nonce = null; + req.session.save(() => res.status(500).json({ message: e.message })); + } + }); + + /// ... check the github repository for the others endpoints + + // get the session + app.get('/session', (req, res) => { + res.setHeader('Content-Type', 'application/json'); + res.send(req.session.siwe); + }); + + +Check the github full example to see the full flow working: siwe-quickstart + +verifySignature +Verify a SIWE signature. + +import { createPublicClient, http } from 'viem' + +const publicClient = createPublicClient( + { + transport: http( + `https://rpc.walletconnect.org/v1/?chainId=${chainId}&projectId=${projectId}` + ) + } +); +const isValid = await publicClient.verifyMessage({ + message, + address: address as `0x${string}`, + signature: signature as `0x${string}` +}); + +// The verifySignature is not working with social logins and emails with non deployed smart accounts +// for this reason we recommend using the viem to verify the signature +// import { verifySignature } from '@reown/appkit-siwe' +// const isValid = await verifySignature({ address, message, signature, chainId, projectId }) + +Initialize AppKit with your siweConfig +// Pass your siweConfig inside the createAppKit() function + const modal = createAppKit({ + adapters: [wagmiAdapter], //or your Ethers adapter + projectId, + networks: [mainnet, arbitrum], + defaultNetwork: mainnet, + features: { + analytics: true, // Optional - defaults to your Cloud configuration + }, + siweConfig: siweConfig // pass your siweConfig + }) + +SIWE Config Parameters +One-Click Auth +Legacy +getMessageParams () => Promise<{ domain: string, uri: string, chains: number[], statement: string }> +Parameters to create the SIWE message internally. + +getNonce () => Promise +The getNonce method functions as a safeguard against spoofing, akin to a CSRF token. The siwe package provides a generateNonce() helper, or you can utilize an existing CSRF token from your backend if available. + +createMessage (args: SIWECreateMessageArgs) => string +The official siwe package offers a straightforward method for generating an EIP-4361-compatible message, which can subsequently be authenticated using the same package. The nonce parameter is derived from your getNonce endpoint, while the address and chainId variables are sourced from the presently connected wallet. + +verifyMessage (args: SIWEVerifyMessageArgs) => Promise +The function to ensure the message is valid, has not been tampered with, and has been appropriately signed by the wallet address. + +getSession () => Promise +The backend session should store the associated address and chainId and return it via the getSession method. + +signOut () => Promise +The users session can be destroyed calling signOut. + +onSignIn (session?: SIWESession) => void +Callback when user signs in (Optional). + +onSignOut () => void +Callback when user signs out (Optional). + +signOutOnDisconnect boolean +defaults to true +Whether or not to sign out when the user disconnects their wallet (Optional). + +Multichain +AppKit is now multichain. The architecture is designed to support both EVM and non-EVM blockchains. This will allow developers and projects to choose and configure multiple blockchain networks within their instance of AppKit, extending beyond just Ethereum-based (EVM) networks. + +Currently, AppKit supports two non-EVM networks, they are, Solana and Bitcoin. Soon, AppKit will support Polkadot and Cosmos, allowing projects to tap into users from these different blockchain ecosystems. This will enable developers and projects to reach a broader audience and interact with multiple blockchain networks, all through a single wallet provider. + +Installation +Wagmi + Solana +Wagmi + Bitcoin +Ethers + Solana +Ethers5 + Solana +Basic +npm +Yarn +Bun +pnpm +yarn add @reown/appkit @reown/appkit-adapter-wagmi @reown/appkit-adapter-solana @solana/wallet-adapter-wallets + +Integration +The AppKit instance allows you to support multiple chains by importing the respective chains, creating the respective network adapters and passing these within the createAppKit() function. + +Depending on the network adapter of your preference (Wagmi, Ethers, Ethers5), the integration may vary. Let's look at what the integration will look like. + +Wagmi + Solana +Wagmi + Bitcoin +Ethers + Solana +Ethers5 + Solana +Basic +import { createAppKit } from '@reown/appkit' +import { SolanaAdapter } from '@reown/appkit-adapter-solana' +import { WagmiAdapter } from '@reown/appkit-adapter-wagmi' + +import { mainnet, arbitrum, sepolia, solana, solanaTestnet, solanaDevnet } from '@reown/appkit/networks' +import type { AppKitNetwork } from '@reown/appkit/types' + +import { SolflareWalletAdapter, PhantomWalletAdapter } from '@solana/wallet-adapter-wallets' + +const networks: [AppKitNetwork, ...AppKitNetwork[]] = [mainnet, arbitrum, sepolia, solana, solanaTestnet, solanaDevnet] + +// 0. Get projectId from https://cloud.reown.com +const projectId = 'YOUR_PROJECT_ID' + +// 1. Create the Wagmi adapter +export const wagmiAdapter = new WagmiAdapter({ + ssr: true, + projectId, + networks +}) + +// 2. Create Solana adapter +const solanaWeb3JsAdapter = new SolanaAdapter({ + wallets: [new PhantomWalletAdapter(), new SolflareWalletAdapter()] +}) + +// 3. Set up the metadata - Optional +const metadata = { + name: 'AppKit', + description: 'AppKit Example', + url: 'https://example.com', // origin must match your domain & subdomain + icons: ['https://avatars.githubusercontent.com/u/179229932'] +} + +// 4. Create the AppKit instance +const modal = createAppKit({ + adapters: [wagmiAdapter, solanaWeb3JsAdapter], + networks, + metadata, + projectId, + features: { + analytics: true, + } +}) + + +Theming +The theme for the AppKit integration in your dApp can be fully customized. Below are some examples: + +Wormfare +BeraPong +ThemeMode +By default themeMode option will be set to user system settings 'light' or 'dark'. But you can override it like this: + +createAppKit({ + //... + themeMode: 'light' +}) + +themeVariables +By default themeVariables are undefined. You can set them like this: + +createAppKit({ + //... + themeVariables: { + '--w3m-color-mix': '#00BB7F', + '--w3m-color-mix-strength': 40 + } +}) + +The following list shows the theme variables you can override: + +Variable Description Type +--w3m-font-family Base font family +string +--w3m-accent Color used for buttons, icons, labels, etc. +string +--w3m-color-mix The color that blends in with the default colors +string +--w3m-color-mix-strength The percentage on how much "--w3m-color-mix" should blend in +number +--w3m-font-size-master The base pixel size for fonts. +string +--w3m-border-radius-master The base border radius in pixels. +string +--w3m-z-index The z-index of the modal. +number + +Smart Sessions +Overview +note +💡 The support for smart-session is included in the Appkit SDK in the experimental package. + +Smart Sessions allow developers to easily integrate session-based permission handling within their decentralized applications (dApps). Using the grantPermissions method, can send permission requests to wallets. + +For users, this means a simpler experience. Instead of approving every action individually, they can allow access for a single session, making it faster and easier to use apps without dealing with constant pop-ups or interruptions. + +With Smart Sessions, approved actions are carried out by the app's backend during the session. This allows transactions to be processed automatically, making the experience even more seamless while ensuring that everything stays within the permissions set by the user. + +This guide will walk you through on how to use the grantPermissions method, including the basic setup and an example of how to request permissions from a wallet. + +Implementations +The grantPermissions method provides an easy way to interact with the smart wallet to request permissions. + +Step 1 | Install the library +npm +Yarn +Bun +pnpm +yarn add @reown/appkit-experimental + +Step 2 | Import the method +First, import the grantPermissions method from the @reown/appkit-experimental/smart-session package. + +import { grantPermissions, type SmartSessionGrantPermissionsRequest } from '@reown/appkit-experimental/smart-session' + + +Step 3 | Define the Permission Request +Create an object adhering to the SmartSessionGrantPermissionsRequest type. This object specifies details like the address, chainID, signer, policies, permissions, and expiry time. + +Example request object: + +const request: SmartSessionGrantPermissionsRequest = { + expiry: Math.floor(Date.now() / 1000) + 24 * 60 * 60, // 24 hours + chainId: toHex(baseSepolia.id), + address: address, + signer: { + type: 'keys', + data: { + keys :[{ + type: 'secp256k1', + publicKey: '0x...' //public key of dapp signer + }] + } + }, + permissions: [ { + type: 'contract-call', + data: { + address: '0x2E65BAfA07238666c3b239E94F32DaD3cDD6498D', // sample donut contract address + abi: [ + { + inputs: [{ internalType: 'uint256', name: 'amount', type: 'uint256' }], + name: 'purchase', + outputs: [], + stateMutability: 'payable', + type: 'function' + } + ], + functions: [ { + functionName: 'purchase' + } ] + } + }], + policies: [] + } + +Step 4 | Invoke the Method +Call the grantPermissions function, passing the request object. This will trigger the permission request via the connected wallet. + + const response = await grantPermissions(request) + +Step 5 | Handle the Response +Upon successful execution, the response will include the granted permissions and the session context. So the response can be handled as needed. + +Response Format +{ + chainId: `0x14a34` + address: `0x...` + expiry: 1727824386 + permissions: [ + { + type: 'contract-call', + data: { + address: '0x2E65BAfA07238666c3b239E94F32DaD3cDD6498D', // sample donut contract address + abi: [ + { + inputs: [{ internalType: 'uint256', name: 'amount', type: 'uint256' }], + name: 'purchase', + outputs: [], + stateMutability: 'payable', + type: 'function' + } + ], + functions: [ { + functionName: 'purchase' + } ] + } + } + ], + context: '...' // Context identifier for the session +} + +How to use the permissions +The dApp must call the following two endpoints from the wallet services API to use these permissions. + +https://rpc.walletconnect.org/v1/wallets/prepareCalls - Accepts an EIP-5792 wallet_sendCalls request. Responds with the prepared calls (in the case of Appkit Embedded Wallet, an Entrypoint v0.7 user operation), some context, and a signature request. +https://rpc.walletconnect.org/v1/wallets/sendPreparedCalls - Accepts prepared calls, a signature, and the context returned from prepareCalls if present. Returns an EIP-5792 calls ID. +Steps to follow for executing any async action by the dApp backend. + +Dapp makes the wallet_prepareCalls JSON RPC call to the wallet service API. Accepts an EIP-5792 wallet_sendCalls request, and returns the prepared calls according to the account's implementation. + +Parameter +Parameter +Example Value +type PrepareCallsParams = [{ +from: `0x${string}` +chainId: `0x${string}` +calls: { + to: `0x${string}` + data: `0x${string}` + value: `0x${string}` +}[]; +capabilities: Record +}] + +Return value +Return value +Return value Example +type PrepareCallsReturnValue = [{ + preparedCalls: { + type: string + data: any + chainId: `0x${string}` + } + signatureRequest: { + hash: `0x${string}` + } + context: `0x${string}` +}] + +App developers are expected to Sign the signatureRequest.hash returned from wallet_prepareCalls call using the dApp key (secp256k1 or secp256r1) + +dApps makes the wallet_sendPreparedCalls JSON RPC call to wallet service API. The RPC accepts the prepared response from wallet_prepareCalls request along with a signature, and returns an EIP-5792 call bundle ID. + +Examples dApp + +Tic Tac Toe | Demo | Video +Dollar Cost Average | Demo | Explanation | Video +Github examples repository +Reference +ERC-7715: Grant Permissions from Wallets | https://eip.tools/eip/7715 +EIP-5792: Wallet Call API | https://eip.tools/eip/5792 +ERC-4337 Entry Point | https://github.com/ethereum/ercs/blob/master/ERCS/erc-4337.md#entrypoint-definition +Currently supported Permission types +ContractCallPermission +export enum ParamOperator { + EQUAL = 'EQUAL', + GREATER_THAN = 'GREATER_THAN', + LESS_THAN = 'LESS_THAN' +} + +export enum Operation { + Call = 'Call', + DelegateCall = 'DelegateCall' +} + +export type ArgumentCondition = { + operator: ParamOperator + value: `0x${string}` +} + +export type FunctionPermission = { + functionName: string + args?: ArgumentCondition[] + valueLimit?: `0x${string}` + operation?: Operation +} +export type ContractCallPermission = { + type: 'contract-call' + data: { + address: `0x${string}` + abi: Record[] + functions: FunctionPermission[] + } +} + +Relay +Project ID +The Project ID is consumed through URL parameters. + +URL parameters used: + +projectId: Your Project ID can be obtained from cloud.reown.com +Example URL: + +https://relay.walletconnect.com/?projectId=c4f79cc821944d9680842e34466bfbd + +This can be instantiated from the client with the projectId in the SignClient constructor. + +import SignClient from '@walletconnect/sign-client' +const signClient = await SignClient.init({ + projectId: 'c4f79cc821944d9680842e34466bfb' +}) + +Allowlist +To help prevent malicious use of your project ID you are strongly encouraged to set an allowlist of origins or application/bundle ids for mobile applications where the project ID is used. Requests from other origins will be denied. + +Allowlist supports a list of origins in the format [scheme://] Пожалуйста, подключите кошелек для управления контрактом - +
@@ -39,6 +37,7 @@ import { ref, onMounted, watch } from 'vue'; import { ethers } from 'ethers'; import { useAppKitAccount, useAppKitProvider, useAppKit } from '@reown/appkit/vue'; +import config from '../config'; export default { name: 'ContractInteraction', @@ -48,17 +47,11 @@ export default { const loading = ref(false); const { address, isConnected } = useAppKitAccount(); const { walletProvider } = useAppKitProvider('eip155'); - const appKit = useAppKit(); + const { contractAddress, contractABI } = config.contract; - const contractAddress = '0x6199Ba629C85Da887dBd8Ffd8d2C75Ea24EaDe2a'; - const contractABI = [ - 'function owner() view returns (address)', - 'function setOwner(address newOwner)', - ]; + const { open } = useAppKit(); // Получаем функцию открытия модала - const formatAddress = (addr) => { - return addr.slice(0, 6) + '...' + addr.slice(-4); - }; + const formatAddress = (addr) => addr.slice(0, 6) + '...' + addr.slice(-4); const isValidAddress = (addr) => { try { @@ -68,14 +61,31 @@ export default { } }; - // Добавим логирование для отладки - watch(() => isConnected, (newValue) => { - console.log('Состояние подключения изменилось:', newValue); - console.log('Адрес кошелька:', address); - }, { immediate: true }); + // Следим за изменением состояния подключения + watch(isConnected, async (newValue) => { + console.log('Connection state changed:', newValue); + if (newValue) { + await fetchOwner(); + } else { + owner.value = ''; + } + }); + + const handleConnect = async () => { + try { + console.log('Attempting to connect wallet...'); + await open(); // Используем хуки для открытия модала + console.log('Wallet connected successfully'); + } catch (error) { + console.error('Wallet connection error:', error); + } + }; const fetchOwner = async () => { - if (!isConnected) return; + if (!isConnected.value || !walletProvider) { + console.log('Cannot fetch owner: wallet not connected'); + return; + } loading.value = true; try { console.log('Получаем владельца контракта...'); @@ -93,7 +103,7 @@ export default { const setNewOwner = async () => { try { - if (!isConnected) { + if (!isConnected.value) { console.log('Пожалуйста, подключите кошелек'); return; } @@ -109,37 +119,17 @@ export default { const tx = await contract.setOwner(newOwner.value); await tx.wait(); - // Обновляем информацию после успешной транзакции await fetchOwner(); - newOwner.value = ''; // Очищаем поле ввода + newOwner.value = ''; } catch (error) { console.error('Ошибка при установке нового владельца:', error); } }; - // Обработчик подключения кошелька - const handleConnect = async () => { - try { - await appKit.open(); - } catch (error) { - console.error('Ошибка при подключении:', error); + onMounted(async () => { + if (isConnected.value) { + await fetchOwner(); } - }; - - // Обновляем watch - watch(() => isConnected, (newValue, oldValue) => { - console.log('Состояние подключения изменилось:', { newValue, oldValue }); - if (newValue) { - fetchOwner(); - } else { - owner.value = ''; - } - }, { immediate: true }); - - onMounted(() => { - // Проверяем состояние подключения при монтировании - console.log('Компонент смонтирован, isConnected:', isConnected); - fetchOwner(); }); return { @@ -148,7 +138,6 @@ export default { isConnected, loading, handleConnect, - walletProvider, setNewOwner, formatAddress, isValidAddress diff --git a/frontend/src/config/index.ts b/frontend/src/config/index.ts index c3456f6..4db071a 100644 --- a/frontend/src/config/index.ts +++ b/frontend/src/config/index.ts @@ -5,4 +5,32 @@ export const projectId = '9a6515f7259ebccd149fd53341e01e6b' export const networks = [sepolia] -export const ethersAdapter = new EthersAdapter() \ No newline at end of file +export const ethersAdapter = new EthersAdapter() + +export const config = { + ethereum: { + networkUrl: import.meta.env.VITE_APP_ETHEREUM_NETWORK_URL as string, + projectId: import.meta.env.VITE_APP_PROJECT_ID as string + }, + api: { + baseUrl: 'http://localhost:3000', // URL бэкенда + endpoints: { + verify: '/api/verify' + } + }, + contract: { + address: '0x6199Ba629C85Da887dBd8Ffd8d2C75Ea24EaDe2a', + abi: [ + 'function owner() view returns (address)', + 'function setOwner(address newOwner)' + ] + }, + metadata: { + name: 'DApp for Business', + description: 'Управление смарт-контрактом', + url: window.location.origin, + icons: ['https://avatars.githubusercontent.com/u/37784886'] + } +}; + +export default config; \ No newline at end of file diff --git a/frontend/src/env.d.ts b/frontend/src/env.d.ts new file mode 100644 index 0000000..37dae94 --- /dev/null +++ b/frontend/src/env.d.ts @@ -0,0 +1,7 @@ +/// + +declare module '*.vue' { + import type { DefineComponent } from 'vue' + const component: DefineComponent<{}, {}, any> + export default component +} \ No newline at end of file diff --git a/frontend/src/main.js b/frontend/src/main.js index 94698be..c3dcb0b 100644 --- a/frontend/src/main.js +++ b/frontend/src/main.js @@ -3,103 +3,24 @@ import App from './App.vue'; import { createAppKit } from '@reown/appkit/vue'; import { EthersAdapter } from '@reown/appkit-adapter-ethers'; import { sepolia } from '@reown/appkit/networks'; -import { createSIWEConfig, formatMessage } from '@reown/appkit-siwe'; +import config from './config'; // Импортируем конфигурацию -// Определяем базовый URL для API -const BASE_URL = 'http://localhost:3000'; - -// 1. Get projectId -const projectId = '9a6515f7259ebccd149fd53341e01e6b'; - -// 2. Create SIWE config -const siweConfig = createSIWEConfig({ - getMessageParams: async () => ({ - domain: window.location.host, - uri: window.location.origin, - chains: [11155111], // Sepolia chainId - statement: 'Подпишите это сообщение для входа в DApp for Business. Это безопасно и не требует оплаты.', - }), - createMessage: ({ address, ...args }) => formatMessage(args, address), - getNonce: async () => { - try { - const res = await fetch(`${BASE_URL}/nonce`, { - method: 'GET', - credentials: 'include', - headers: { - 'Accept': 'text/plain' - } - }); - if (!res.ok) throw new Error('Failed to get nonce'); - return await res.text(); - } catch (error) { - console.error('Ошибка получения nonce:', error); - throw error; - } +const appKit = createAppKit({ + adapters: [new EthersAdapter()], + projectId: config.ethereum.projectId, + networks: [sepolia], + defaultNetwork: sepolia, + metadata: config.metadata, + features: { + analytics: true }, - getSession: async () => { - try { - const res = await fetch(`${BASE_URL}/session`, { - method: 'GET', - credentials: 'include', - headers: { - 'Accept': 'application/json' - } - }); - if (!res.ok) return null; - return await res.json(); - } catch (error) { - console.error('Ошибка получения сессии:', error); - return null; - } - }, - verifyMessage: async ({ message, signature }) => { - try { - const res = await fetch(`${BASE_URL}/verify`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'Accept': 'application/json' - }, - body: JSON.stringify({ message, signature }), - credentials: 'include' - }); - return res.ok; - } catch (error) { - console.error('Ошибка верификации:', error); - return false; - } - }, - signOut: async () => { - try { - await fetch(`${BASE_URL}/signout`, { - method: 'GET', - credentials: 'include' - }); - } catch (error) { - console.error('Ошибка выхода:', error); - } + themeMode: 'light', // Добавляем светлую тему + themeVariables: { + '--w3m-color-mix': '#00BB7F', + '--w3m-color-mix-strength': 40 } }); -// 3. Create AppKit instance -createAppKit({ - adapters: [new EthersAdapter()], - networks: [sepolia], - projectId, - metadata: { - name: 'DApp for Business', - description: 'Smart Contract Management DApp', - url: window.location.origin, - icons: ['https://avatars.githubusercontent.com/u/37784886'] - }, - defaultNetwork: sepolia, - features: { - analytics: true, - connectMethodsOrder: ['wallet', 'email', 'social'], - autoConnect: false - }, - siweConfig -}); - const app = createApp(App); +app.use(appKit); // Подключаем AppKit как плагин app.mount('#app'); \ No newline at end of file diff --git a/frontend/src/types/index.ts b/frontend/src/types/index.ts new file mode 100644 index 0000000..16505a6 --- /dev/null +++ b/frontend/src/types/index.ts @@ -0,0 +1,5 @@ +export interface ContractState { + owner: string; + isConnected: boolean; + loading: boolean; +} \ No newline at end of file diff --git a/frontend/src/utils/contract.ts b/frontend/src/utils/contract.ts new file mode 100644 index 0000000..6f81d69 --- /dev/null +++ b/frontend/src/utils/contract.ts @@ -0,0 +1,9 @@ +import { ethers } from 'ethers'; + +export const formatAddress = (address: string): string => { + return `${address.slice(0, 6)}...${address.slice(-4)}`; +}; + +export const isValidAddress = (address: string): boolean => { + return ethers.isAddress(address); +}; \ No newline at end of file diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json new file mode 100644 index 0000000..f7b9474 --- /dev/null +++ b/frontend/tsconfig.json @@ -0,0 +1,30 @@ +{ + "compilerOptions": { + "target": "esnext", + "useDefineForClassFields": true, + "module": "esnext", + "moduleResolution": "node", + "strict": true, + "jsx": "preserve", + "sourceMap": true, + "resolveJsonModule": true, + "isolatedModules": true, + "esModuleInterop": true, + "lib": ["esnext", "dom"], + "skipLibCheck": true, + "allowJs": true, + "baseUrl": ".", + "paths": { + "@/*": ["./src/*"] + } + }, + "include": [ + "src/**/*.ts", + "src/**/*.d.ts", + "src/**/*.tsx", + "src/**/*.vue" + ], + "exclude": [ + "node_modules" + ] +} \ No newline at end of file diff --git a/frontend/vite.config.js b/frontend/vite.config.js index 880bcba..728ba89 100644 --- a/frontend/vite.config.js +++ b/frontend/vite.config.js @@ -1,5 +1,6 @@ import { defineConfig } from 'vite'; import vue from '@vitejs/plugin-vue'; +import { fileURLToPath, URL } from 'node:url'; export default defineConfig({ plugins: [ @@ -12,5 +13,10 @@ export default defineConfig({ } } }) - ] + ], + resolve: { + alias: { + '@': fileURLToPath(new URL('./src', import.meta.url)) + } + } }); \ No newline at end of file diff --git a/frontend/yarn.lock b/frontend/yarn.lock index e330285..5e9d52b 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -309,7 +309,7 @@ resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.7.1.tgz#5738f6d765710921e7a751e00c20ae091ed8db0f" integrity sha512-B8XBPsn4vT/KJAGqDzbwztd+6Yte3P4V7iafm24bxgDe/mlRuK6xmWPuCNrKt2vDafZ8MfJLlchDG/vYafQEjQ== -"@reown/appkit-adapter-ethers@1.6.8": +"@reown/appkit-adapter-ethers@^1.6.8": version "1.6.8" resolved "https://registry.yarnpkg.com/@reown/appkit-adapter-ethers/-/appkit-adapter-ethers-1.6.8.tgz#e6c11a267139ace0428794831047c72fa3c854a4" integrity sha512-ShsGfUGgqOzlu3iE0J7blUbvdRkoqqY1/GYcZznIgck9lQekxoT6JkyiOVXytvKg0q9H1QQdBmbXhrIUH9bFLQ== @@ -348,6 +348,19 @@ valtio "1.13.2" viem ">=2.23" +"@reown/appkit-experimental@^1.6.8": + version "1.6.8" + resolved "https://registry.yarnpkg.com/@reown/appkit-experimental/-/appkit-experimental-1.6.8.tgz#3609e674eb4045796356625df04f952a462f717f" + integrity sha512-QGMQqAm348rQJqCUF05JTuCgA7hq9wGonPFLN2/MewBnjj50Cpv8ek9tnKDJ7QrMk1DKAd6bTq0YnFkJ9ChITw== + dependencies: + "@reown/appkit" "1.6.8" + "@reown/appkit-common" "1.6.8" + "@reown/appkit-core" "1.6.8" + "@reown/appkit-ui" "1.6.8" + lit "3.1.0" + valtio "1.13.2" + zod "3.22.4" + "@reown/appkit-polyfills@1.6.8": version "1.6.8" resolved "https://registry.yarnpkg.com/@reown/appkit-polyfills/-/appkit-polyfills-1.6.8.tgz#0b301aa231cb0ad246efbe236d3d26718e88bccf" @@ -404,6 +417,18 @@ valtio "1.13.2" viem ">=2.23.0" +"@reown/appkit-wallet-button@^1.6.8": + version "1.6.8" + resolved "https://registry.yarnpkg.com/@reown/appkit-wallet-button/-/appkit-wallet-button-1.6.8.tgz#6bce54647ec5c812f7feea0ed3db61ec987b210b" + integrity sha512-rpP5HvLuSZwh/I+Ley+fS5Pwd4BKXdxosTT8ICfSIjT0BOG/mWVmmufQ7vEgs3l2R8WU8pf9iTT6ba6tmza3/g== + dependencies: + "@reown/appkit-common" "1.6.8" + "@reown/appkit-core" "1.6.8" + "@reown/appkit-ui" "1.6.8" + "@reown/appkit-utils" "1.6.8" + lit "3.1.0" + valtio "1.13.2" + "@reown/appkit-wallet@1.6.8": version "1.6.8" resolved "https://registry.yarnpkg.com/@reown/appkit-wallet/-/appkit-wallet-1.6.8.tgz#a661a624845a79213757220d4c6b4dc0acc01c47" @@ -414,7 +439,7 @@ "@walletconnect/logger" "2.1.2" zod "3.22.4" -"@reown/appkit@1.6.8": +"@reown/appkit@1.6.8", "@reown/appkit@^1.6.8": version "1.6.8" resolved "https://registry.yarnpkg.com/@reown/appkit/-/appkit-1.6.8.tgz#5c25486b4bf8e8caa20bb82c75d2d679b0efac93" integrity sha512-GA7VZ+TZ7POVjfjWNyeffLoTIjX+iEvmRdKo9TEEvPBZ77996eiQFnhaaQHCNg1Wf9Ba/lptxVJT07gSZknh5A== @@ -550,13 +575,15 @@ "@noble/hashes" "~1.7.1" "@scure/base" "~1.2.4" -"@spruceid/siwe-parser@^3.0.0": - version "3.0.0" - resolved "https://registry.yarnpkg.com/@spruceid/siwe-parser/-/siwe-parser-3.0.0.tgz#8af48683d77aed6dbd1abf541e1b064dc64be10e" - integrity sha512-Y92k63ilw/8jH9Ry4G2e7lQd0jZAvb0d/Q7ssSD0D9mp/Zt2aCXIc3g0ny9yhplpAx1QXHsMz/JJptHK/zDGdw== +"@spruceid/siwe-parser@^2.1.2": + version "2.1.2" + resolved "https://registry.yarnpkg.com/@spruceid/siwe-parser/-/siwe-parser-2.1.2.tgz#3e13e7d3ac0bfdaf109a07342590eb21daee2fc3" + integrity sha512-d/r3S1LwJyMaRAKQ0awmo9whfXeE88Qt00vRj91q5uv5ATtWIQEGJ67Yr5eSZw5zp1/fZCXZYuEckt8lSkereQ== dependencies: "@noble/hashes" "^1.1.2" - apg-js "^4.4.0" + apg-js "^4.3.0" + uri-js "^4.4.1" + valid-url "^1.0.9" "@stablelib/binary@^1.0.1": version "1.0.1" @@ -595,6 +622,13 @@ dependencies: undici-types "~6.19.2" +"@types/node@^22.13.4": + version "22.13.4" + resolved "https://registry.yarnpkg.com/@types/node/-/node-22.13.4.tgz#3fe454d77cd4a2d73c214008b3e331bfaaf5038a" + integrity sha512-ywP2X0DYtX3y08eFVx5fNIw7/uIv8hYUKgXoK8oayJlLnKcRfEYCxWMVE1XagUdVtCJlZT1AU4LXEABW+L1Peg== + dependencies: + undici-types "~6.20.0" + "@types/trusted-types@^2.0.2": version "2.0.7" resolved "https://registry.yarnpkg.com/@types/trusted-types/-/trusted-types-2.0.7.tgz#baccb07a970b91707df3a3e8ba6896c57ead2d11" @@ -605,6 +639,28 @@ resolved "https://registry.yarnpkg.com/@vitejs/plugin-vue/-/plugin-vue-5.2.1.tgz#d1491f678ee3af899f7ae57d9c21dc52a65c7133" integrity sha512-cxh314tzaWwOLqVes2gnnCtvBDcM1UMdn+iFR+UjAn411dPT3tOmqrJjbMd7koZpMAmBM/GqeV4n9ge7JSiJJQ== +"@volar/language-core@1.11.1", "@volar/language-core@~1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@volar/language-core/-/language-core-1.11.1.tgz#ecdf12ea8dc35fb8549e517991abcbf449a5ad4f" + integrity sha512-dOcNn3i9GgZAcJt43wuaEykSluAuOkQgzni1cuxLxTV0nJKanQztp7FxyswdRILaKH+P2XZMPRp2S4MV/pElCw== + dependencies: + "@volar/source-map" "1.11.1" + +"@volar/source-map@1.11.1", "@volar/source-map@~1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@volar/source-map/-/source-map-1.11.1.tgz#535b0328d9e2b7a91dff846cab4058e191f4452f" + integrity sha512-hJnOnwZ4+WT5iupLRnuzbULZ42L7BWWPMmruzwtLhJfpDVoZLjNBxHDi2sY2bgZXCKlpU5XcsMFoYrsQmPhfZg== + dependencies: + muggle-string "^0.3.1" + +"@volar/typescript@~1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@volar/typescript/-/typescript-1.11.1.tgz#ba86c6f326d88e249c7f5cfe4b765be3946fd627" + integrity sha512-iU+t2mas/4lYierSnoFOeRFQUhAEMgsFuQxoxvwn5EdQopw43j+J27a4lt9LMInx1gLJBC6qL14WYGlgymaSMQ== + dependencies: + "@volar/language-core" "1.11.1" + path-browserify "^1.0.1" + "@vue/compiler-core@3.5.13": version "3.5.13" resolved "https://registry.yarnpkg.com/@vue/compiler-core/-/compiler-core-3.5.13.tgz#b0ae6c4347f60c03e849a05d34e5bf747c9bda05" @@ -616,7 +672,7 @@ estree-walker "^2.0.2" source-map-js "^1.2.0" -"@vue/compiler-dom@3.5.13": +"@vue/compiler-dom@3.5.13", "@vue/compiler-dom@^3.3.0": version "3.5.13" resolved "https://registry.yarnpkg.com/@vue/compiler-dom/-/compiler-dom-3.5.13.tgz#bb1b8758dbc542b3658dda973b98a1c9311a8a58" integrity sha512-ZOJ46sMOKUjO3e94wPdCzQ6P1Lx/vhp2RSvfaab88Ajexs0AHeV0uasYhi99WPaogmBlRHNRuly8xV75cNTMDA== @@ -647,6 +703,21 @@ "@vue/compiler-dom" "3.5.13" "@vue/shared" "3.5.13" +"@vue/language-core@1.8.27": + version "1.8.27" + resolved "https://registry.yarnpkg.com/@vue/language-core/-/language-core-1.8.27.tgz#2ca6892cb524e024a44e554e4c55d7a23e72263f" + integrity sha512-L8Kc27VdQserNaCUNiSFdDl9LWT24ly8Hpwf1ECy3aFb9m6bDhBGQYOujDm21N7EW3moKIOKEanQwe1q5BK+mA== + dependencies: + "@volar/language-core" "~1.11.1" + "@volar/source-map" "~1.11.1" + "@vue/compiler-dom" "^3.3.0" + "@vue/shared" "^3.3.0" + computeds "^0.0.1" + minimatch "^9.0.3" + muggle-string "^0.3.1" + path-browserify "^1.0.1" + vue-template-compiler "^2.7.14" + "@vue/reactivity@3.5.13": version "3.5.13" resolved "https://registry.yarnpkg.com/@vue/reactivity/-/reactivity-3.5.13.tgz#b41ff2bb865e093899a22219f5b25f97b6fe155f" @@ -680,7 +751,7 @@ "@vue/compiler-ssr" "3.5.13" "@vue/shared" "3.5.13" -"@vue/shared@3.5.13": +"@vue/shared@3.5.13", "@vue/shared@^3.3.0": version "3.5.13" resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.5.13.tgz#87b309a6379c22b926e696893237826f64339b6f" integrity sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ== @@ -940,7 +1011,7 @@ anymatch@^3.1.3, anymatch@~3.1.2: normalize-path "^3.0.0" picomatch "^2.0.4" -apg-js@^4.4.0: +apg-js@^4.3.0: version "4.4.0" resolved "https://registry.yarnpkg.com/apg-js/-/apg-js-4.4.0.tgz#09dcecab0731fbde233b9f2352fdd2d07e56b2cf" integrity sha512-fefmXFknJmtgtNEXfPwZKYkMFX4Fyeyz+fNF6JWp87biGOPslJbCBVU158zvKRZfHBKnJDy8CMM40oLFGkXT8Q== @@ -950,6 +1021,11 @@ atomic-sleep@^1.0.0: resolved "https://registry.yarnpkg.com/atomic-sleep/-/atomic-sleep-1.0.0.tgz#eb85b77a601fc932cfe432c5acd364a9e2c9075b" integrity sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ== +balanced-match@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== + base-x@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/base-x/-/base-x-5.0.0.tgz#6d835ceae379130e1a4cb846a70ac4746f28ea9b" @@ -980,6 +1056,13 @@ bn.js@^5.2.1: resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.2.1.tgz#0bc527a6a0d18d0aa8d5b0538ce4a77dccfa7b70" integrity sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ== +brace-expansion@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae" + integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA== + dependencies: + balanced-match "^1.0.0" + braces@~3.0.2: version "3.0.3" resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" @@ -1053,6 +1136,11 @@ color-name@~1.1.4: resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== +computeds@^0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/computeds/-/computeds-0.0.1.tgz#215b08a4ba3e08a11ff6eee5d6d8d7166a97ce2e" + integrity sha512-7CEBgcMjVmitjYo5q8JTJVra6X5mQ20uTThdK+0kR7UEaDrAWEQcRiBtWJzga4eRpP6afNwwLsX2SET2JhVB1Q== + cookie-es@^1.2.2: version "1.2.2" resolved "https://registry.yarnpkg.com/cookie-es/-/cookie-es-1.2.2.tgz#18ceef9eb513cac1cb6c14bcbf8bdb2679b34821" @@ -1082,6 +1170,11 @@ dayjs@1.11.10: resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.10.tgz#68acea85317a6e164457d6d6947564029a6a16a0" integrity sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ== +de-indent@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/de-indent/-/de-indent-1.0.2.tgz#b2038e846dc33baa5796128d0804b455b8c1e21d" + integrity sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg== + decamelize@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" @@ -1298,6 +1391,11 @@ hash.js@1.1.7, hash.js@^1.0.0, hash.js@^1.0.3: inherits "^2.0.3" minimalistic-assert "^1.0.1" +he@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" + integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== + hmac-drbg@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1" @@ -1435,6 +1533,18 @@ minimalistic-crypto-utils@^1.0.1: resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a" integrity sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg== +minimatch@^9.0.3: + version "9.0.5" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.5.tgz#d74f9dd6b57d83d8e98cfb82133b03978bc929e5" + integrity sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow== + dependencies: + brace-expansion "^2.0.1" + +muggle-string@^0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/muggle-string/-/muggle-string-0.3.1.tgz#e524312eb1728c63dd0b2ac49e3282e6ed85963a" + integrity sha512-ckmWDJjphvd/FvZawgygcUeQCxzvohjFO5RxTjj4eq8kw359gFF3E1brjfI+viLMxss5JrHTDRHZvu2/tuy0Qg== + multiformats@^9.4.2: version "9.9.0" resolved "https://registry.yarnpkg.com/multiformats/-/multiformats-9.9.0.tgz#c68354e7d21037a8f1f8833c8ccd68618e8f1d37" @@ -1525,6 +1635,11 @@ p-try@^2.0.0: resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== +path-browserify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-1.0.1.tgz#d98454a9c3753d5790860f16f68867b9e46be1fd" + integrity sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g== + path-exists@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" @@ -1599,6 +1714,11 @@ proxy-compare@2.6.0: resolved "https://registry.yarnpkg.com/proxy-compare/-/proxy-compare-2.6.0.tgz#5e8c8b5c3af7e7f17e839bf6cf1435bcc4d315b0" integrity sha512-8xuCeM3l8yqdmbPoYeLbrAXCBWu19XEYc5/F28f5qOaoAIMyfmBUkl5axiK+x9olUvRlcekvnm98AP9RDngOIw== +punycode@^2.1.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" + integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== + qrcode@1.5.3: version "1.5.3" resolved "https://registry.yarnpkg.com/qrcode/-/qrcode-1.5.3.tgz#03afa80912c0dccf12bc93f615a535aad1066170" @@ -1698,18 +1818,25 @@ safe-stable-stringify@^2.1.0: resolved "https://registry.yarnpkg.com/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz#4ca2f8e385f2831c432a719b108a3bf7af42a1dd" integrity sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA== +semver@^7.5.4: + version "7.7.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.1.tgz#abd5098d82b18c6c81f6074ff2647fd3e7220c9f" + integrity sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA== + set-blocking@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" integrity sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw== -siwe@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/siwe/-/siwe-3.0.0.tgz#0508c3fca521c476a07d907a9b5b96a03c27c0f2" - integrity sha512-P2/ry7dHYJA6JJ5+veS//Gn2XDwNb3JMvuD6xiXX8L/PJ1SNVD4a3a8xqEbmANx+7kNQcD8YAh1B9bNKKvRy/g== +siwe@^2.1.4: + version "2.3.2" + resolved "https://registry.yarnpkg.com/siwe/-/siwe-2.3.2.tgz#0794ae25f734f3068de0ab093ddd2f7867bc2d67" + integrity sha512-aSf+6+Latyttbj5nMu6GF3doMfv2UYj83hhwZgUF20ky6fTS83uVhkQABdIVnEuS8y1bBdk7p6ltb9SmlhTTlA== dependencies: - "@spruceid/siwe-parser" "^3.0.0" + "@spruceid/siwe-parser" "^2.1.2" "@stablelib/random" "^1.0.1" + uri-js "^4.4.1" + valid-url "^1.0.9" sonic-boom@^2.2.1: version "2.8.0" @@ -1795,6 +1922,11 @@ tslib@2.7.0: resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.7.0.tgz#d9b40c5c40ab59e8738f297df3087bf1a2690c01" integrity sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA== +typescript@^5.7.3: + version "5.7.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.7.3.tgz#919b44a7dbb8583a9b856d162be24a54bf80073e" + integrity sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw== + ufo@^1.5.4: version "1.5.4" resolved "https://registry.yarnpkg.com/ufo/-/ufo-1.5.4.tgz#16d6949674ca0c9e0fbbae1fa20a71d7b1ded754" @@ -1824,6 +1956,11 @@ undici-types@~6.19.2: resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.19.8.tgz#35111c9d1437ab83a7cdc0abae2f26d88eda0a02" integrity sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw== +undici-types@~6.20.0: + version "6.20.0" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.20.0.tgz#8171bf22c1f588d1554d55bf204bc624af388433" + integrity sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg== + unstorage@^1.9.0: version "1.14.4" resolved "https://registry.yarnpkg.com/unstorage/-/unstorage-1.14.4.tgz#620dd68997a3245fca1e04c0171335817525bc3d" @@ -1838,6 +1975,13 @@ unstorage@^1.9.0: ofetch "^1.4.1" ufo "^1.5.4" +uri-js@^4.4.1: + version "4.4.1" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" + integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== + dependencies: + punycode "^2.1.0" + use-sync-external-store@1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz#7dbefd6ef3fe4e767a0cf5d7287aacfb5846928a" @@ -1848,6 +1992,11 @@ util-deprecate@^1.0.1: resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== +valid-url@^1.0.9: + version "1.0.9" + resolved "https://registry.yarnpkg.com/valid-url/-/valid-url-1.0.9.tgz#1c14479b40f1397a75782f115e4086447433a200" + integrity sha512-QQDsV8OnSf5Uc30CKSwG9lnhMPe6exHtTXLRYX8uMwKENy640pU+2BgBL0LRbDh/eYRahNCS7aewCx0wf3NYVA== + valtio@1.13.2: version "1.13.2" resolved "https://registry.yarnpkg.com/valtio/-/valtio-1.13.2.tgz#e31d452d5da3550935417670aafd34d832dc7241" @@ -1872,9 +2021,9 @@ viem@2.23.0: ws "8.18.0" viem@>=2.23, viem@>=2.23.0, viem@^2.23.2: - version "2.23.2" - resolved "https://registry.yarnpkg.com/viem/-/viem-2.23.2.tgz#db395c8cf5f4fb5572914b962fb8ce5db09f681c" - integrity sha512-NVmW/E0c5crMOtbEAqMF0e3NmvQykFXhLOc/CkLIXOlzHSA6KXVz3CYVmaKqBF8/xtjsjHAGjdJN3Ru1kFJLaA== + version "2.23.3" + resolved "https://registry.yarnpkg.com/viem/-/viem-2.23.3.tgz#3b8af9490f8f453a17e849d774bea1b5c992738c" + integrity sha512-ON/Uybteajqxn3iFyhV/6Ybm+QKhcrsVyTZf/9v2w0CvYQIoyJYCfHSsQR9zpsbOGrR7d2p62w6jzb6fqzzacg== dependencies: "@noble/curves" "1.8.1" "@noble/hashes" "1.7.1" @@ -1896,7 +2045,24 @@ vite@^5.4.10: optionalDependencies: fsevents "~2.3.3" -vue@^3.5.12: +vue-template-compiler@^2.7.14: + version "2.7.16" + resolved "https://registry.yarnpkg.com/vue-template-compiler/-/vue-template-compiler-2.7.16.tgz#c81b2d47753264c77ac03b9966a46637482bb03b" + integrity sha512-AYbUWAJHLGGQM7+cNTELw+KsOG9nl2CnSv467WobS5Cv9uk3wFcnr1Etsz2sEIHEZvw1U+o9mRlEO6QbZvUPGQ== + dependencies: + de-indent "^1.0.2" + he "^1.2.0" + +vue-tsc@^1.8.27: + version "1.8.27" + resolved "https://registry.yarnpkg.com/vue-tsc/-/vue-tsc-1.8.27.tgz#feb2bb1eef9be28017bb9e95e2bbd1ebdd48481c" + integrity sha512-WesKCAZCRAbmmhuGl3+VrdWItEvfoFIPXOvUJkjULi+x+6G/Dy69yO3TBRJDr9eUlmsNAwVmxsNZxvHKzbkKdg== + dependencies: + "@volar/typescript" "~1.11.1" + "@vue/language-core" "1.8.27" + semver "^7.5.4" + +vue@^3.4.15: version "3.5.13" resolved "https://registry.yarnpkg.com/vue/-/vue-3.5.13.tgz#9f760a1a982b09c0c04a867903fc339c9f29ec0a" integrity sha512-wmeiSMxkZCSc+PM2w2VRsOYAZC8GdipNFRTsLSfodVqI9mbejKeXEGr8SckuLnrQPGe3oJN5c3K0vpoU9q/wCQ==