eip155 example (#115)

* eip155 example

* temp fix for duplicate accs
This commit is contained in:
0xAsimetriq 2023-02-15 17:11:42 +02:00 committed by GitHub
parent 8927cddaf9
commit 2379c795ce
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
77 changed files with 8350 additions and 0 deletions

36
.gitignore vendored Normal file
View File

@ -0,0 +1,36 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
.DS_Store
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# local env files
.env.local
.env.development.local
.env.test.local
.env.production.local
# vercel
.vercel

View File

@ -0,0 +1,3 @@
NEXT_PUBLIC_PROJECT_ID=...
NEXT_PUBLIC_RELAY_URL=wss://relay.walletconnect.com

View File

@ -0,0 +1,4 @@
{
"extends": "next/core-web-vitals",
"ignorePatterns": ["next.config.js"]
}

36
wallets/react-wallet-eip155/.gitignore vendored Normal file
View File

@ -0,0 +1,36 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
.DS_Store
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# local env files
.env.local
.env.development.local
.env.test.local
.env.production.local
# vercel
.vercel

View File

@ -0,0 +1,9 @@
{
"arrowParens": "avoid",
"parser": "typescript",
"printWidth": 100,
"singleQuote": true,
"tabWidth": 2,
"trailingComma": "none",
"semi": false
}

View File

@ -0,0 +1,50 @@
# Wallet Example (React, Typescript, Ethers, NextJS, Cosmos)
This example aims to demonstrate basic and advanced use cases enabled by WalletConnect's Sign SDK.
**The wallet implements Sign v1 and v2 side-by-side**, to serve as a reference for wallet developers
aiming to support both major versions for backwards compatibility. **All files containing code for the legacy
v1 implementation are prefixed with `Legacy...`.**
Please only use this for reference and development purposes, otherwise you are at risk of losing your funds.
# Useful links
🔗 Live wallet app - https://react-wallet.walletconnect.com <br />
🔗 Live dapp - https://react-app.walletconnect.com <br />
📚 WalletConnect docs - https://docs.walletconnect.com/2.0
## Getting started
Example is built atop of [NextJS](https://nextjs.org/) in order to abstract complexity of setting up bundlers, routing etc. So there are few steps you need to follow in order to set everything up
1. Go to [WalletConnect Cloud](https://cloud.walletconnect.com/sign-in) and obtain a project id
2. Add your project details in [WalletConnectUtil.ts](https://github.com/WalletConnect/web-examples/blob/main/wallets/react-wallet-v2/src/utils/WalletConnectUtil.ts) file
3. Install dependencies `yarn install` or `npm install`
4. Setup your environment variables
```bash
cp .env.local.example .env.local
```
Your `.env.local` now contains the following environment variables:
- `NEXT_PUBLIC_PROJECT_ID` (placeholder) - You can generate your own ProjectId at https://cloud.walletconnect.com
- `NEXT_PUBLIC_RELAY_URL` (already set)
5. Run `yarn dev` or `npm run dev` to start local development
## Navigating through example
1. Initial setup and initializations happen in [_app.ts](https://github.com/WalletConnect/web-examples/blob/main/wallets/react-wallet-v2/src/pages/_app.tsx) file
2. WalletConnect client, ethers and cosmos wallets are initialized in [useInitialization.ts ](https://github.com/WalletConnect/web-examples/blob/main/wallets/react-wallet-v2/src/hooks/useInitialization.ts) hook
3. Subscription and handling of WalletConnect events happens in [useWalletConnectEventsManager.ts](https://github.com/WalletConnect/web-examples/blob/main/wallets/react-wallet-v2/src/hooks/useWalletConnectEventsManager.ts) hook, that opens related [Modal views](https://github.com/WalletConnect/web-examples/tree/main/wallets/react-wallet-v2/src/views) and passes them all necessary data
4. [Modal views](https://github.com/WalletConnect/web-examples/tree/main/wallets/react-wallet-v2/src/views) are responsible for data display and handling approval or rejection actions
5. Upon approval or rejection, modals pass the request data to [RequestHandlerUtil.ts](https://github.com/WalletConnect/web-examples/blob/main/wallets/react-wallet-v2/src/utils/RequestHandlerUtil.ts) that performs all necessary work based on the request method and returns formated json rpc result data that can be then used for WalletConnect client responses
## Preview of wallet and dapp examples in action
https://user-images.githubusercontent.com/3154053/156764521-3492c232-7a93-47ba-88bd-2cee3f8366d4.mp4

View File

@ -0,0 +1,5 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
// NOTE: This file should not be edited
// see https://nextjs.org/docs/basic-features/typescript for more information.

View File

@ -0,0 +1,11 @@
module.exports = {
reactStrictMode: true,
webpack(config) {
config.resolve.fallback = {
...config.resolve.fallback,
fs: false
}
return config
}
}

View File

@ -0,0 +1,38 @@
{
"name": "react-wallet-eip155",
"private": true,
"scripts": {
"dev": "next dev -p 3001",
"build": "next build",
"start": "next start",
"lint": "next lint",
"prettier:write": "prettier --write '**/*.{js,ts,jsx,tsx}'"
},
"dependencies": {
"@json-rpc-tools/utils": "1.7.6",
"@nextui-org/react": "1.0.0-beta.12",
"@walletconnect/client": "1.8.0",
"@walletconnect/legacy-types": "2.0.0",
"@walletconnect/web3wallet": "1.2.0",
"@walletconnect/core": "2.4.3",
"@walletconnect/utils": "2.4.3",
"ethers": "5.7.2",
"framer-motion": "9.0.2",
"next": "13.1.6",
"react": "18.2.0",
"react-code-blocks": "0.0.9-0",
"react-dom": "18.2.0",
"react-qr-reader-es6": "2.2.1-2",
"valtio": "1.10.2"
},
"devDependencies": {
"@types/node": "18.13.0",
"@types/react": "18.0.28",
"@walletconnect/types": "2.4.3",
"eslint": "8.34.0",
"eslint-config-next": "13.1.6",
"eslint-config-prettier": "8.6.0",
"prettier": "2.8.4",
"typescript": "4.9.5"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

View File

@ -0,0 +1,24 @@
<svg width="512" height="512" viewBox="0 0 512 512" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M160 368H448M160 144H448H160ZM160 256H448H160Z" stroke="url(#paint0_linear_46_13)" stroke-width="48" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M80 160C88.8366 160 96 152.837 96 144C96 135.163 88.8366 128 80 128C71.1634 128 64 135.163 64 144C64 152.837 71.1634 160 80 160Z" stroke="url(#paint1_linear_46_13)" stroke-width="32" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M80 272C88.8366 272 96 264.837 96 256C96 247.163 88.8366 240 80 240C71.1634 240 64 247.163 64 256C64 264.837 71.1634 272 80 272Z" stroke="url(#paint2_linear_46_13)" stroke-width="32" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M80 384C88.8366 384 96 376.837 96 368C96 359.163 88.8366 352 80 352C71.1634 352 64 359.163 64 368C64 376.837 71.1634 384 80 384Z" stroke="url(#paint3_linear_46_13)" stroke-width="32" stroke-linecap="round" stroke-linejoin="round"/>
<defs>
<linearGradient id="paint0_linear_46_13" x1="160" y1="144.018" x2="380.191" y2="421.745" gradientUnits="userSpaceOnUse">
<stop stop-color="#A562D5"/>
<stop offset="1" stop-color="#306FEB"/>
</linearGradient>
<linearGradient id="paint1_linear_46_13" x1="64" y1="128.003" x2="96.3014" y2="159.69" gradientUnits="userSpaceOnUse">
<stop stop-color="#A562D5"/>
<stop offset="1" stop-color="#306FEB"/>
</linearGradient>
<linearGradient id="paint2_linear_46_13" x1="64" y1="240.003" x2="96.3014" y2="271.69" gradientUnits="userSpaceOnUse">
<stop stop-color="#A562D5"/>
<stop offset="1" stop-color="#306FEB"/>
</linearGradient>
<linearGradient id="paint3_linear_46_13" x1="64" y1="352.003" x2="96.3014" y2="383.69" gradientUnits="userSpaceOnUse">
<stop stop-color="#A562D5"/>
<stop offset="1" stop-color="#306FEB"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -0,0 +1,3 @@
<svg width="512" height="512" viewBox="0 0 512 512" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M112 184L256 328L400 184" stroke="white" stroke-width="48" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 224 B

View File

@ -0,0 +1,3 @@
<svg width="512" height="512" viewBox="0 0 512 512" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M184 112L328 256L184 400" stroke="white" stroke-width="48" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 224 B

View File

@ -0,0 +1,3 @@
<svg width="512" height="512" viewBox="0 0 512 512" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M416 128L192 384L96 288" stroke="white" stroke-width="32" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 223 B

View File

@ -0,0 +1,4 @@
<svg width="512" height="512" viewBox="0 0 512 512" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M336 64H368C380.73 64 392.939 69.0571 401.941 78.0589C410.943 87.0606 416 99.2696 416 112V432C416 444.73 410.943 456.939 401.941 465.941C392.939 474.943 380.73 480 368 480H144C131.27 480 119.061 474.943 110.059 465.941C101.057 456.939 96 444.73 96 432V112C96 99.2696 101.057 87.0606 110.059 78.0589C119.061 69.0571 131.27 64 144 64H176" stroke="white" stroke-width="32" stroke-linejoin="round"/>
<path d="M309.87 32H202.13C187.699 32 176 43.6988 176 58.13V69.87C176 84.3012 187.699 96 202.13 96H309.87C324.301 96 336 84.3012 336 69.87V58.13C336 43.6988 324.301 32 309.87 32Z" stroke="white" stroke-width="32" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 751 B

View File

@ -0,0 +1,6 @@
<svg width="512" height="512" viewBox="0 0 512 512" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M112 112L132 432C132.95 450.49 146.4 464 164 464H348C365.67 464 378.87 450.49 380 432L400 112" stroke="#F21361" stroke-width="32" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M80 112H432Z" fill="#F21361"/>
<path d="M80 112H432" stroke="#F21361" stroke-width="32" stroke-miterlimit="10" stroke-linecap="round"/>
<path d="M328 176L320 400M192 112V72.0001C191.991 68.8458 192.605 65.7208 193.808 62.8048C195.011 59.8888 196.778 57.2394 199.009 55.0089C201.239 52.7785 203.889 51.011 206.805 49.8082C209.721 48.6053 212.846 47.9909 216 48.0001H296C299.154 47.9909 302.279 48.6053 305.195 49.8082C308.111 51.011 310.761 52.7785 312.991 55.0089C315.222 57.2394 316.989 59.8888 318.192 62.8048C319.395 65.7208 320.009 68.8458 320 72.0001V112H192ZM256 176V400V176ZM184 176L192 400L184 176Z" stroke="#F21361" stroke-width="32" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 996 B

View File

@ -0,0 +1,9 @@
<svg width="512" height="512" viewBox="0 0 512 512" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M208 352H144C118.539 352 94.1212 341.886 76.1178 323.882C58.1143 305.879 48 281.461 48 256C48 230.539 58.1143 206.121 76.1178 188.118C94.1212 170.114 118.539 160 144 160H208M304 160H368C393.461 160 417.879 170.114 435.882 188.118C453.886 206.121 464 230.539 464 256C464 281.461 453.886 305.879 435.882 323.882C417.879 341.886 393.461 352 368 352H304M163.29 256H350.71" stroke="url(#paint0_linear_112_7)" stroke-width="36" stroke-linecap="round" stroke-linejoin="round"/>
<defs>
<linearGradient id="paint0_linear_112_7" x1="48.0001" y1="160.015" x2="197.341" y2="477.442" gradientUnits="userSpaceOnUse">
<stop stop-color="#A562D5"/>
<stop offset="1" stop-color="#306FEB"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 814 B

View File

@ -0,0 +1,13 @@
<svg width="512" height="512" viewBox="0 0 512 512" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M408 336H344C339.582 336 336 339.582 336 344V408C336 412.418 339.582 416 344 416H408C412.418 416 416 412.418 416 408V344C416 339.582 412.418 336 408 336Z" fill="#8B8B8B"/>
<path d="M328 272H280C275.582 272 272 275.582 272 280V328C272 332.418 275.582 336 280 336H328C332.418 336 336 332.418 336 328V280C336 275.582 332.418 272 328 272Z" fill="#8B8B8B"/>
<path d="M472 416H424C419.582 416 416 419.582 416 424V472C416 476.418 419.582 480 424 480H472C476.418 480 480 476.418 480 472V424C480 419.582 476.418 416 472 416Z" fill="#8B8B8B"/>
<path d="M472 272H440C435.582 272 432 275.582 432 280V312C432 316.418 435.582 320 440 320H472C476.418 320 480 316.418 480 312V280C480 275.582 476.418 272 472 272Z" fill="#8B8B8B"/>
<path d="M312 432H280C275.582 432 272 435.582 272 440V472C272 476.418 275.582 480 280 480H312C316.418 480 320 476.418 320 472V440C320 435.582 316.418 432 312 432Z" fill="#8B8B8B"/>
<path d="M408 96H344C339.582 96 336 99.5817 336 104V168C336 172.418 339.582 176 344 176H408C412.418 176 416 172.418 416 168V104C416 99.5817 412.418 96 408 96Z" fill="#8B8B8B"/>
<path d="M448 48H304C295.163 48 288 55.1634 288 64V208C288 216.837 295.163 224 304 224H448C456.837 224 464 216.837 464 208V64C464 55.1634 456.837 48 448 48Z" stroke="#8B8B8B" stroke-width="32" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M168 96H104C99.5817 96 96 99.5817 96 104V168C96 172.418 99.5817 176 104 176H168C172.418 176 176 172.418 176 168V104C176 99.5817 172.418 96 168 96Z" fill="#8B8B8B"/>
<path d="M208 48H64C55.1634 48 48 55.1634 48 64V208C48 216.837 55.1634 224 64 224H208C216.837 224 224 216.837 224 208V64C224 55.1634 216.837 48 208 48Z" stroke="#8B8B8B" stroke-width="32" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M168 336H104C99.5817 336 96 339.582 96 344V408C96 412.418 99.5817 416 104 416H168C172.418 416 176 412.418 176 408V344C176 339.582 172.418 336 168 336Z" fill="#8B8B8B"/>
<path d="M208 288H64C55.1634 288 48 295.163 48 304V448C48 456.837 55.1634 464 64 464H208C216.837 464 224 456.837 224 448V304C224 295.163 216.837 288 208 288Z" stroke="#8B8B8B" stroke-width="32" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@ -0,0 +1,19 @@
<svg width="512" height="512" viewBox="0 0 512 512" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M320 120L368 168L320 216" stroke="url(#paint0_linear_101_17)" stroke-width="32" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M352 168H144C122.802 168.063 102.491 176.512 87.5014 191.501C72.5122 206.491 64.0633 226.802 64 248V264M192 392L144 344L192 296" stroke="url(#paint1_linear_101_17)" stroke-width="32" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M160 344H368C389.198 343.937 409.509 335.488 424.499 320.499C439.488 305.509 447.937 285.198 448 264V248" stroke="url(#paint2_linear_101_17)" stroke-width="32" stroke-linecap="round" stroke-linejoin="round"/>
<defs>
<linearGradient id="paint0_linear_101_17" x1="320" y1="120.008" x2="396.642" y2="157.601" gradientUnits="userSpaceOnUse">
<stop stop-color="#A562D5"/>
<stop offset="1" stop-color="#306FEB"/>
</linearGradient>
<linearGradient id="paint1_linear_101_17" x1="64.0001" y1="168.018" x2="284.191" y2="445.745" gradientUnits="userSpaceOnUse">
<stop stop-color="#A562D5"/>
<stop offset="1" stop-color="#306FEB"/>
</linearGradient>
<linearGradient id="paint2_linear_101_17" x1="160" y1="248.008" x2="219.048" y2="421.788" gradientUnits="userSpaceOnUse">
<stop stop-color="#A562D5"/>
<stop offset="1" stop-color="#306FEB"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 9.8 KiB

View File

@ -0,0 +1,86 @@
* {
box-sizing: border-box;
-ms-overflow-style: none;
scrollbar-width: none;
}
::-webkit-scrollbar {
display: none;
}
.routeTransition {
display: flex;
flex: 1;
flex-direction: column;
overflow: hidden;
}
.container {
width: 100%;
height: calc(100% - 220px);
display: flex;
flex: 1;
flex-direction: column;
justify-content: center;
align-items: center;
}
.qrVideoMask {
width: 100%;
border-radius: 15px;
overflow: hidden !important;
position: relative;
}
.qrPlaceholder {
border: 2px rgba(139, 139, 139, 0.4) dashed;
width: 100%;
border-radius: 15px;
padding: 50px;
}
.qrIcon {
opacity: 0.3;
}
.codeBlock code {
flex: 1;
}
.codeBlock span {
background-color: transparent !important;
overflow: scroll;
}
.navLink {
transition: ease-in-out .2s opacity;
}
.navLink:hover {
opacity: 0.6;
}
select {
background-color: rgba(139, 139, 139, 0.2);
background-image: url(/icons/arrow-down-icon.svg);
background-size: 15px 15px;
background-position: right 10px center;
background-repeat: no-repeat;
padding: 5px 30px 6px 10px;
border-radius: 10px;
cursor: pointer;
border: none;
appearance: none;
transition: .2s ease-in-out background-color;
font-family: var(--nextui-fonts-sans);
font-weight: var(--nextui-fontWeights-light);
border: 1px solid rgba(139, 139, 139, 0.25);
}
select:hover {
background-color: rgba(139, 139, 139, 0.35);
}
i {
margin-top: -5px !important;
}

View File

@ -0,0 +1,4 @@
<svg width="283" height="64" viewBox="0 0 283 64" fill="none"
xmlns="http://www.w3.org/2000/svg">
<path d="M141.04 16c-11.04 0-19 7.2-19 18s8.96 18 20 18c6.67 0 12.55-2.64 16.19-7.09l-7.65-4.42c-2.02 2.21-5.09 3.5-8.54 3.5-4.79 0-8.86-2.5-10.37-6.5h28.02c.22-1.12.35-2.28.35-3.5 0-10.79-7.96-17.99-19-17.99zm-9.46 14.5c1.25-3.99 4.67-6.5 9.45-6.5 4.79 0 8.21 2.51 9.45 6.5h-18.9zM248.72 16c-11.04 0-19 7.2-19 18s8.96 18 20 18c6.67 0 12.55-2.64 16.19-7.09l-7.65-4.42c-2.02 2.21-5.09 3.5-8.54 3.5-4.79 0-8.86-2.5-10.37-6.5h28.02c.22-1.12.35-2.28.35-3.5 0-10.79-7.96-17.99-19-17.99zm-9.45 14.5c1.25-3.99 4.67-6.5 9.45-6.5 4.79 0 8.21 2.51 9.45 6.5h-18.9zM200.24 34c0 6 3.92 10 10 10 4.12 0 7.21-1.87 8.8-4.92l7.68 4.43c-3.18 5.3-9.14 8.49-16.48 8.49-11.05 0-19-7.2-19-18s7.96-18 19-18c7.34 0 13.29 3.19 16.48 8.49l-7.68 4.43c-1.59-3.05-4.68-4.92-8.8-4.92-6.07 0-10 4-10 10zm82.48-29v46h-9V5h9zM36.95 0L73.9 64H0L36.95 0zm92.38 5l-27.71 48L73.91 5H84.3l17.32 30 17.32-30h10.39zm58.91 12v9.69c-1-.29-2.06-.49-3.2-.49-5.81 0-10 4-10 10V51h-9V17h9v9.2c0-5.08 5.91-9.2 13.2-9.2z" fill="#000"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -0,0 +1,3 @@
<svg width="388" height="238" viewBox="0 0 388 238" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M79.4993 46.539C142.716 -15.355 245.209 -15.355 308.426 46.539L316.034 53.988C319.195 57.0827 319.195 62.1002 316.034 65.1949L290.008 90.6766C288.427 92.2239 285.865 92.2239 284.285 90.6766L273.815 80.4258C229.714 37.247 158.211 37.247 114.11 80.4258L102.898 91.4035C101.317 92.9509 98.7551 92.9509 97.1747 91.4035L71.1486 65.9219C67.9878 62.8272 67.9878 57.8096 71.1486 54.715L79.4993 46.539ZM362.25 99.2378L385.413 121.917C388.574 125.011 388.574 130.029 385.413 133.123L280.969 235.385C277.808 238.48 272.683 238.48 269.522 235.385C269.522 235.385 269.522 235.385 269.522 235.385L195.394 162.807C194.604 162.033 193.322 162.033 192.532 162.807C192.532 162.807 192.532 162.807 192.532 162.807L118.405 235.385C115.244 238.48 110.12 238.48 106.959 235.385C106.959 235.385 106.959 235.385 106.959 235.385L2.51129 133.122C-0.649517 130.027 -0.649517 125.01 2.51129 121.915L25.6746 99.2365C28.8354 96.1418 33.9601 96.1418 37.1209 99.2365L111.25 171.816C112.041 172.589 113.322 172.589 114.112 171.816C114.112 171.816 114.112 171.815 114.112 171.815L188.238 99.2365C191.399 96.1417 196.523 96.1416 199.684 99.2362C199.684 99.2362 199.684 99.2363 199.684 99.2363L273.814 171.815C274.604 172.589 275.885 172.589 276.675 171.815L350.804 99.2378C353.964 96.1431 359.089 96.1431 362.25 99.2378Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -0,0 +1,51 @@
import ChainCard from '@/components/ChainCard'
import { truncate } from '@/utils/HelperUtil'
import { Avatar, Button, Text, Tooltip } from '@nextui-org/react'
import Image from 'next/image'
import { useState } from 'react'
interface Props {
name: string
logo: string
rgb: string
address: string
}
export default function AccountCard({ name, logo, rgb, address }: Props) {
const [copied, setCopied] = useState(false)
function onCopy() {
navigator?.clipboard?.writeText(address)
setCopied(true)
setTimeout(() => setCopied(false), 1500)
}
return (
<ChainCard rgb={rgb} flexDirection="row" alignItems="center">
<Avatar src={logo} />
<div style={{ flex: 1 }}>
<Text h5 css={{ marginLeft: '$9', marginBottom: 0 }}>
{name}
</Text>
<Text weight="light" size={13} css={{ marginLeft: '$9' }}>
{truncate(address, 19)}
</Text>
</div>
<Tooltip content={copied ? 'Copied!' : 'Copy'} placement="left">
<Button
size="sm"
css={{ minWidth: 'auto', backgroundColor: 'rgba(255, 255, 255, 0.15)' }}
onPress={onCopy}
>
<Image
src={copied ? '/icons/checkmark-icon.svg' : '/icons/copy-icon.svg'}
width={15}
height={15}
alt="copy icon"
/>
</Button>
</Tooltip>
</ChainCard>
)
}

View File

@ -0,0 +1,20 @@
import SettingsStore from '@/store/SettingsStore'
import { eip155Addresses } from '@/utils/EIP155WalletUtil'
import { useSnapshot } from 'valtio'
export default function AccountPicker() {
const { account } = useSnapshot(SettingsStore.state)
function onSelect(value: string) {
const account = Number(value)
SettingsStore.setAccount(account)
SettingsStore.setEIP155Address(eip155Addresses[account])
}
return (
<select value={account} onChange={e => onSelect(e.currentTarget.value)} aria-label="addresses">
<option value={0}>Account 1</option>
<option value={1}>Account 2</option>
</select>
)
}

View File

@ -0,0 +1,35 @@
import { truncate } from '@/utils/HelperUtil'
import { Card, Checkbox, Row, Text } from '@nextui-org/react'
/**
* Types
*/
interface IProps {
address: string
index: number
selected: boolean
onSelect: () => void
}
/**
* Component
*/
export default function AccountSelectCard({ address, selected, index, onSelect }: IProps) {
return (
<Card
onPress={onSelect}
isPressable
key={address}
css={{
marginTop: '$5',
backgroundColor: selected ? 'rgba(23, 200, 100, 0.2)' : '$accents2',
padding: '10px 20px'
}}
>
<Row justify="space-between" align="center">
<Checkbox size="lg" color="success" isSelected={selected} onChange={onSelect} />
<Text>{`${truncate(address, 14)} - Account ${index + 1}`} </Text>
</Row>
</Card>
)
}

View File

@ -0,0 +1,38 @@
import { Card } from '@nextui-org/react'
import { ReactNode } from 'react'
interface Props {
children: ReactNode | ReactNode[]
rgb: string
flexDirection: 'row' | 'col'
alignItems: 'center' | 'flex-start'
}
export default function ChainCard({ rgb, children, flexDirection, alignItems }: Props) {
return (
<Card
css={{
borderColor: `rgba(${rgb}, 0.4)`,
boxShadow: `0 0 10px 0 rgba(${rgb}, 0.15)`,
backgroundColor: `rgba(${rgb}, 0.25)`,
marginBottom: '$6'
}}
variant="bordered"
>
<Card.Body
css={{
flexDirection,
alignItems,
justifyContent: 'space-between',
overflow: 'hidden',
paddingTop: 10,
paddingBottom: 10,
paddingLeft: 20,
paddingRight: 20
}}
>
{children}
</Card.Body>
</Card>
)
}

View File

@ -0,0 +1,87 @@
import Navigation from '@/components/Navigation'
import RouteTransition from '@/components/RouteTransition'
import { Card, Container, Loading } from '@nextui-org/react'
import { Fragment, ReactNode } from 'react'
/**
* Types
*/
interface Props {
initialized: boolean
children: ReactNode | ReactNode[]
}
/**
* Container
*/
export default function Layout({ children, initialized }: Props) {
return (
<Container
display="flex"
justify="center"
alignItems="center"
css={{
width: '100vw',
height: '100vh',
padding: 0
}}
>
<Card
variant="bordered"
css={{
height: '100%',
width: '100%',
justifyContent: initialized ? 'normal' : 'center',
alignItems: initialized ? 'normal' : 'center',
borderRadius: 0,
'@xs': {
borderRadius: '$lg',
height: '95vh',
maxWidth: '450px'
}
}}
>
{initialized ? (
<Fragment>
<RouteTransition>
<Card.Body
css={{
display: 'block',
padding: 10,
paddingTop: 10,
'@xs': {
padding: 40,
paddingTop: 30
}
}}
>
{children}
</Card.Body>
</RouteTransition>
<Card.Footer
css={{
height: '85px',
minHeight: '85px',
position: 'sticky',
justifyContent: 'flex-end',
alignItems: 'flex-end',
boxShadow: '0 -30px 20px #16181A',
backgroundColor: '#16181A',
zIndex: 200,
inset: 0,
paddingLeft: 40,
paddingRight: 40,
paddingBottom: 30
}}
>
<Navigation />
</Card.Footer>
</Fragment>
) : (
<Loading />
)}
</Card>
</Container>
)
}

View File

@ -0,0 +1,30 @@
import ModalStore from '@/store/ModalStore'
import LegacySessionProposalModal from '@/views/LegacySessionProposalModal'
import LegacySessionSendTransactionModal from '@/views/LegacySessionSendTransactionModal'
import LegacySessionSignModal from '@/views/LegacySessionSignModal'
import LegacySessionSignTypedDataModal from '@/views/LegacySessionSignTypedDataModal'
import SessionProposalModal from '@/views/SessionProposalModal'
import SessionSendTransactionModal from '@/views/SessionSendTransactionModal'
import SessionRequestModal from '@/views/SessionSignModal'
import SessionSignTypedDataModal from '@/views/SessionSignTypedDataModal'
import SessionUnsuportedMethodModal from '@/views/SessionUnsuportedMethodModal'
import { Modal as NextModal } from '@nextui-org/react'
import { useSnapshot } from 'valtio'
export default function Modal() {
const { open, view } = useSnapshot(ModalStore.state)
return (
<NextModal blur open={open} style={{ border: '1px solid rgba(139, 139, 139, 0.4)' }}>
{view === 'SessionProposalModal' && <SessionProposalModal />}
{view === 'SessionSignModal' && <SessionRequestModal />}
{view === 'SessionSignTypedDataModal' && <SessionSignTypedDataModal />}
{view === 'SessionSendTransactionModal' && <SessionSendTransactionModal />}
{view === 'SessionUnsuportedMethodModal' && <SessionUnsuportedMethodModal />}
{view === 'LegacySessionProposalModal' && <LegacySessionProposalModal />}
{view === 'LegacySessionSignModal' && <LegacySessionSignModal />}
{view === 'LegacySessionSignTypedDataModal' && <LegacySessionSignTypedDataModal />}
{view === 'LegacySessionSendTransactionModal' && <LegacySessionSendTransactionModal />}
</NextModal>
)
}

View File

@ -0,0 +1,39 @@
import { Avatar, Row } from '@nextui-org/react'
import Image from 'next/image'
import Link from 'next/link'
export default function Navigation() {
return (
<Row justify="space-between" align="center">
<Row css={{ width: '50%' }} justify="space-between">
<Link href="/" className="navLink">
<Image alt="accounts icon" src="/icons/accounts-icon.svg" width={27} height={27} />
</Link>
<Link href="/sessions" className="navLink">
<Image alt="sessions icon" src="/icons/sessions-icon.svg" width={27} height={27} />
</Link>
<Link href="/settings" className="navLink">
<Image alt="settings icon" src="/icons/settings-icon.svg" width={27} height={27} />
</Link>
</Row>
<Link href="/walletconnect" className="navLink">
<Avatar
size="lg"
css={{ cursor: 'pointer' }}
color="gradient"
icon={
<Image
alt="wallet connect icon"
src="/wallet-connect-logo.svg"
width={30}
height={30}
/>
}
/>
</Link>
</Row>
)
}

View File

@ -0,0 +1,36 @@
import { Col, Divider, Row, Text } from '@nextui-org/react'
import { Fragment, ReactNode } from 'react'
/**
* Types
*/
interface Props {
children?: ReactNode | ReactNode[]
title: string
}
/**
* Component
*/
export default function PageHeader({ title, children }: Props) {
return (
<Fragment>
<Row css={{ marginBottom: '$5', width: '100%' }} justify="space-between" align="center">
<Col>
<Text
h3
weight="bold"
css={{
textGradient: '90deg, $secondary, $primary 30%'
}}
>
{title}
</Text>
</Col>
{children ? <Col css={{ flex: 1 }}>{children}</Col> : null}
</Row>
<Divider css={{ marginBottom: '$10' }} />
</Fragment>
)
}

View File

@ -0,0 +1,53 @@
import { truncate } from '@/utils/HelperUtil'
import { Avatar, Button, Card, Link, Text, Tooltip } from '@nextui-org/react'
import Image from 'next/image'
/**
* Types
*/
interface IProps {
logo?: string
name?: string
url?: string
onDelete: () => Promise<void>
}
/**
* Component
*/
export default function PairingCard({ logo, name, url, onDelete }: IProps) {
return (
<Card
variant="bordered"
css={{
position: 'relative',
marginBottom: '$6',
minHeight: '70px'
}}
>
<Card.Body
css={{
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
overflow: 'hidden'
}}
>
<Avatar src={logo} />
<div style={{ flex: 1 }}>
<Text h5 css={{ marginLeft: '$9' }}>
{name}
</Text>
<Link href={url} css={{ marginLeft: '$9' }}>
{truncate(url?.split('https://')[1] ?? 'Unknown', 23)}
</Link>
</div>
<Tooltip content="Delete" placement="left">
<Button size="sm" color="error" flat onPress={onDelete} css={{ minWidth: 'auto' }}>
<Image src={'/icons/delete-icon.svg'} width={15} height={15} alt="delete icon" />
</Button>
</Tooltip>
</Card.Body>
</Card>
)
}

View File

@ -0,0 +1,28 @@
import { Avatar, Col, Link, Row, Text } from '@nextui-org/react'
import { SignClientTypes } from '@walletconnect/types'
/**
* Types
*/
interface IProps {
metadata: SignClientTypes.Metadata
}
/**
* Components
*/
export default function ProjectInfoCard({ metadata }: IProps) {
const { icons, name, url } = metadata
return (
<Row align="center">
<Col span={3}>
<Avatar src={icons[0]} />
</Col>
<Col span={14}>
<Text h5>{name}</Text>
<Link href={url}>{url}</Link>
</Col>
</Row>
)
}

View File

@ -0,0 +1,48 @@
import AccountSelectCard from '@/components/AccountSelectCard'
import { Col, Row, Text } from '@nextui-org/react'
/**
* Types
*/
interface IProps {
chain: string
addresses: string[]
selectedAddresses: string[] | undefined
onSelect: (chain: string, address: string) => void
}
/**
* Component
*/
export default function ProposalSelectSection({
addresses,
selectedAddresses,
chain,
onSelect
}: IProps) {
return (
<Row>
<Col>
<Text h4 css={{ marginTop: '$5' }}>{`Choose ${chain} accounts`}</Text>
{/* TODO(ilja) re-implement when duplicate optional namespaces are fixed */}
{/* {addresses.map((address, index) => (
<AccountSelectCard
key={address}
address={address}
index={index}
onSelect={() => onSelect(chain, address)}
selected={selectedAddresses?.includes(address) ?? false}
/>
))} */}
<AccountSelectCard
key={addresses[0]}
address={addresses[0]}
index={1}
onSelect={() => onSelect(chain, addresses[0])}
selected={selectedAddresses?.includes(addresses[0]) ?? false}
/>
</Col>
</Row>
)
}

View File

@ -0,0 +1,77 @@
import { Button, Loading } from '@nextui-org/react'
import dynamic from 'next/dynamic'
import Image from 'next/image'
import { Fragment, useState } from 'react'
/**
* You can use normal import if you are not within next / ssr environment
* @info https://nextjs.org/docs/advanced-features/dynamic-import
*/
const ReactQrReader = dynamic(() => import('react-qr-reader-es6'), { ssr: false })
/**
* Types
*/
interface IProps {
onConnect: (uri: string) => Promise<void>
}
/**
* Component
*/
export default function QrReader({ onConnect }: IProps) {
const [show, setShow] = useState(false)
const [loading, setLoading] = useState(false)
function onError() {
setShow(false)
}
async function onScan(data: string | null) {
if (data) {
await onConnect(data)
setShow(false)
}
}
function onShowScanner() {
setLoading(true)
setShow(true)
}
return (
<div className="container">
{show ? (
<Fragment>
{loading && <Loading css={{ position: 'absolute' }} />}
<div className="qrVideoMask">
<ReactQrReader
onLoad={() => setLoading(false)}
showViewFinder={false}
onError={onError}
onScan={onScan}
style={{ width: '100%' }}
/>
</div>
</Fragment>
) : (
<div className="container qrPlaceholder">
<Image
src="/icons/qr-icon.svg"
width={100}
height={100}
alt="qr code icon"
className="qrIcon"
/>
<Button
color="gradient"
css={{ marginTop: '$10', width: '100%' }}
onPress={onShowScanner}
>
Scan QR code
</Button>
</div>
)}
</div>
)
}

View File

@ -0,0 +1,29 @@
import { REGIONALIZED_RELAYER_ENDPOINTS } from '@/data/RelayerRegions'
import { useState } from 'react'
import { core } from '../utils/WalletConnectUtil'
export default function AccountPicker() {
const [region, setRegion] = useState('')
async function onSelect(newRegion: string) {
await core.relayer.transportClose()
await core.relayer.transportOpen(newRegion)
setRegion(newRegion)
}
return (
<select
value={region}
onChange={e => onSelect(e.currentTarget.value)}
aria-label="relayerRegions"
>
{REGIONALIZED_RELAYER_ENDPOINTS.map((endpoint, index) => {
return (
<option key={index} value={endpoint.value}>
{endpoint.label}
</option>
)
})}
</select>
)
}

View File

@ -0,0 +1,28 @@
import { Col, Row, Text } from '@nextui-org/react'
import { CodeBlock, codepen } from 'react-code-blocks'
/**
* Types
*/
interface IProps {
data: Record<string, unknown>
}
/**
* Component
*/
export default function RequestDataCard({ data }: IProps) {
return (
<Row>
<Col>
<Text h5>Data</Text>
<CodeBlock
showLineNumbers={false}
text={JSON.stringify(data, null, 2)}
theme={codepen}
language="json"
/>
</Col>
</Row>
)
}

View File

@ -0,0 +1,38 @@
import { EIP155_CHAINS, TEIP155Chain } from '@/data/EIP155Data'
import { Col, Divider, Row, Text } from '@nextui-org/react'
import { Fragment } from 'react'
/**
* Types
*/
interface IProps {
chains: string[]
protocol: string
}
/**
* Component
*/
export default function RequesDetailsCard({ chains, protocol }: IProps) {
return (
<Fragment>
<Row>
<Col>
<Text h5>Blockchain(s)</Text>
<Text color="$gray400">
{chains.map(chain => EIP155_CHAINS[chain as TEIP155Chain]?.name ?? chain).join(', ')}
</Text>
</Col>
</Row>
<Divider y={2} />
<Row>
<Col>
<Text h5>Relay Protocol</Text>
<Text color="$gray400">{protocol}</Text>
</Col>
</Row>
</Fragment>
)
}

View File

@ -0,0 +1,22 @@
import { Col, Row, Text } from '@nextui-org/react'
/**
* Types
*/
interface IProps {
methods: string[]
}
/**
* Component
*/
export default function RequestMethodCard({ methods }: IProps) {
return (
<Row>
<Col>
<Text h5>Methods</Text>
<Text color="$gray800">{methods.map(method => method).join(', ')}</Text>
</Col>
</Row>
)
}

View File

@ -0,0 +1,27 @@
import { Container, Modal, Text } from '@nextui-org/react'
import { Fragment, ReactNode } from 'react'
/**
* Types
*/
interface IProps {
title: string
children: ReactNode | ReactNode[]
}
/**
* Component
*/
export default function RequestModalContainer({ children, title }: IProps) {
return (
<Fragment>
<Modal.Header>
<Text h3>{title}</Text>
</Modal.Header>
<Modal.Body>
<Container css={{ padding: 0 }}>{children}</Container>
</Modal.Body>
</Fragment>
)
}

View File

@ -0,0 +1,32 @@
import { AnimatePresence, motion } from 'framer-motion'
import { useRouter } from 'next/router'
import { ReactNode } from 'react'
/**
* Types
*/
interface IProps {
children: ReactNode | ReactNode[]
}
/**
* Components
*/
export default function RouteTransition({ children }: IProps) {
const { pathname } = useRouter()
return (
<AnimatePresence mode="wait">
<motion.div
className="routeTransition"
key={pathname}
initial={{ opacity: 0, translateY: 7 }}
animate={{ opacity: 1, translateY: 0 }}
exit={{ opacity: 0, translateY: 7 }}
transition={{ duration: 0.18 }}
>
{children}
</motion.div>
</AnimatePresence>
)
}

View File

@ -0,0 +1,53 @@
import { truncate } from '@/utils/HelperUtil'
import { Avatar, Card, Link, Text } from '@nextui-org/react'
import Image from 'next/image'
import NextLink from 'next/link'
/**
* Types
*/
interface IProps {
topic?: string
logo?: string
name?: string
url?: string
}
/**
* Component
*/
export default function SessionCard({ logo, name, url, topic }: IProps) {
return (
<NextLink href={topic ? `/session?topic=${topic}` : '#'} passHref>
<Card
variant="bordered"
css={{
position: 'relative',
marginBottom: '$6',
minHeight: '70px'
}}
>
<Card.Body
css={{
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
overflow: 'hidden'
}}
>
<Avatar src={logo} />
<div style={{ flex: 1 }}>
<Text h5 css={{ marginLeft: '$9' }}>
{name}
</Text>
<Link href={url} css={{ marginLeft: '$9' }}>
{truncate(url?.split('https://')[1] ?? 'Unknown', 23)}
</Link>
</div>
<Image src={'/icons/arrow-right-icon.svg'} width={20} height={20} alt="session icon" />
</Card.Body>
</Card>
</NextLink>
)
}

View File

@ -0,0 +1,69 @@
import ChainCard from '@/components/ChainCard'
import { EIP155_MAINNET_CHAINS, EIP155_TEST_CHAINS } from '@/data/EIP155Data'
import { formatChainName } from '@/utils/HelperUtil'
import { Col, Row, Text } from '@nextui-org/react'
import { SessionTypes } from '@walletconnect/types'
import { Fragment } from 'react'
/**
* Utilities
*/
const CHAIN_METADATA = {
...EIP155_MAINNET_CHAINS,
...EIP155_TEST_CHAINS
}
/**
* Types
*/
interface IProps {
namespace: SessionTypes.Namespace
}
/**
* Component
*/
export default function SessionChainCard({ namespace }: IProps) {
const chains: string[] = []
// WIP
namespace.accounts.forEach(account => {
const [type, chain] = account.split(':')
const chainId = `${type}:${chain}`
chains.push(chainId)
})
return (
<Fragment>
{chains.map(chainId => {
// @ts-expect-error
const rgb = CHAIN_METADATA[chainId]?.rgb
return (
<ChainCard key={chainId} rgb={rgb ?? ''} flexDirection="col" alignItems="flex-start">
<Text h5 css={{ marginBottom: '$5' }}>
{formatChainName(chainId)}
</Text>
<Row>
<Col>
<Text h6>Methods</Text>
<Text color="$gray800">
{namespace.methods.length ? namespace.methods.join(', ') : '-'}
</Text>
</Col>
</Row>
<Row css={{ marginTop: '$5' }}>
<Col>
<Text h6>Events</Text>
<Text color="$gray800">
{namespace.events.length ? namespace.events.join(', ') : '-'}
</Text>
</Col>
</Row>
</ChainCard>
)
})}
</Fragment>
)
}

View File

@ -0,0 +1,59 @@
import ChainCard from '@/components/ChainCard'
import { EIP155_MAINNET_CHAINS, EIP155_TEST_CHAINS } from '@/data/EIP155Data'
import { formatChainName } from '@/utils/HelperUtil'
import { Col, Row, Text } from '@nextui-org/react'
import { ProposalTypes } from '@walletconnect/types'
import { Fragment } from 'react'
/**
* Utilities
*/
const CHAIN_METADATA = {
...EIP155_MAINNET_CHAINS,
...EIP155_TEST_CHAINS
}
/**
* Types
*/
interface IProps {
requiredNamespace: ProposalTypes.RequiredNamespace
}
/**
* Component
*/
export default function SessionProposalChainCard({ requiredNamespace }: IProps) {
return (
<Fragment>
{requiredNamespace?.chains?.map(chainId => {
// @ts-expect-error
const rgb = CHAIN_METADATA[chainId]?.rgb
return (
<ChainCard key={chainId} rgb={rgb ?? ''} flexDirection="col" alignItems="flex-start">
<Text h5 css={{ marginBottom: '$5' }}>
{formatChainName(chainId)}
</Text>
<Row>
<Col>
<Text h6>Methods</Text>
<Text color="$gray800">
{requiredNamespace.methods.length ? requiredNamespace.methods.join(', ') : '-'}
</Text>
</Col>
</Row>
<Row css={{ marginTop: '$5' }}>
<Col>
<Text h6>Events</Text>
<Text color="$gray800">
{requiredNamespace.events.length ? requiredNamespace.events.join(', ') : '-'}
</Text>
</Col>
</Row>
</ChainCard>
)
})}
</Fragment>
)
}

View File

@ -0,0 +1,90 @@
/**
* @desc Refference list of eip155 chains
* @url https://chainlist.org
*/
/**
* Types
*/
export type TEIP155Chain = keyof typeof EIP155_CHAINS
/**
* Chains
*/
export const EIP155_MAINNET_CHAINS = {
'eip155:1': {
chainId: 1,
name: 'Ethereum',
logo: '/chain-logos/eip155-1.png',
rgb: '99, 125, 234',
rpc: 'https://cloudflare-eth.com/'
},
'eip155:43114': {
chainId: 43114,
name: 'Avalanche C-Chain',
logo: '/chain-logos/eip155-43113.png',
rgb: '232, 65, 66',
rpc: 'https://api.avax.network/ext/bc/C/rpc'
},
'eip155:137': {
chainId: 137,
name: 'Polygon',
logo: '/chain-logos/eip155-137.png',
rgb: '130, 71, 229',
rpc: 'https://polygon-rpc.com/'
},
'eip155:10': {
chainId: 10,
name: 'Optimism',
logo: '/chain-logos/eip155-10.png',
rgb: '235, 0, 25',
rpc: 'https://mainnet.optimism.io'
}
}
export const EIP155_TEST_CHAINS = {
'eip155:5': {
chainId: 5,
name: 'Ethereum Goerli',
logo: '/chain-logos/eip155-1.png',
rgb: '99, 125, 234',
rpc: 'https://goerli.infura.io/v3/9aa3d95b3bc440fa88ea12eaa4456161'
},
'eip155:43113': {
chainId: 43113,
name: 'Avalanche Fuji',
logo: '/chain-logos/eip155-43113.png',
rgb: '232, 65, 66',
rpc: 'https://api.avax-test.network/ext/bc/C/rpc'
},
'eip155:80001': {
chainId: 80001,
name: 'Polygon Mumbai',
logo: '/chain-logos/eip155-137.png',
rgb: '130, 71, 229',
rpc: 'https://matic-mumbai.chainstacklabs.com'
},
'eip155:420': {
chainId: 420,
name: 'Optimism Goerli',
logo: '/chain-logos/eip155-10.png',
rgb: '235, 0, 25',
rpc: 'https://goerli.optimism.io'
}
}
export const EIP155_CHAINS = { ...EIP155_MAINNET_CHAINS, ...EIP155_TEST_CHAINS }
/**
* Methods
*/
export const EIP155_SIGNING_METHODS = {
PERSONAL_SIGN: 'personal_sign',
ETH_SIGN: 'eth_sign',
ETH_SIGN_TRANSACTION: 'eth_signTransaction',
ETH_SIGN_TYPED_DATA: 'eth_signTypedData',
ETH_SIGN_TYPED_DATA_V3: 'eth_signTypedData_v3',
ETH_SIGN_TYPED_DATA_V4: 'eth_signTypedData_v4',
ETH_SEND_RAW_TRANSACTION: 'eth_sendRawTransaction',
ETH_SEND_TRANSACTION: 'eth_sendTransaction'
}

View File

@ -0,0 +1,30 @@
/**
* Types
*/
type RelayerType = {
value: string
label: string
}
/**
* Relayer Regions
*/
export const REGIONALIZED_RELAYER_ENDPOINTS: RelayerType[] = [
{
value: 'wss://relay.walletconnect.com',
label: 'Default'
},
{
value: 'wss://us-east-1.relay.walletconnect.com/',
label: 'US'
},
{
value: 'wss://eu-central-1.relay.walletconnect.com/',
label: 'EU'
},
{
value: 'wss://ap-southeast-1.relay.walletconnect.com/',
label: 'Asia Pacific'
}
]

View File

@ -0,0 +1,10 @@
import SettingsStore from '@/store/SettingsStore'
import { createOrRestoreEIP155Wallet } from '@/utils/EIP155WalletUtil'
import { useEffect } from 'react'
export default function useAccounts() {
useEffect(() => {
const { eip155Addresses } = createOrRestoreEIP155Wallet()
SettingsStore.setEIP155Address(eip155Addresses[0])
}, [])
}

View File

@ -0,0 +1,71 @@
import { EIP155_SIGNING_METHODS } from '@/data/EIP155Data'
import ModalStore from '@/store/ModalStore'
import SettingsStore from '@/store/SettingsStore'
import { web3wallet } from '@/utils/WalletConnectUtil'
import { SignClientTypes } from '@walletconnect/types'
import { useCallback, useEffect } from 'react'
import { useSnapshot } from 'valtio'
export default function useWalletConnectEventsManager() {
const { web3WalletReady } = useSnapshot(SettingsStore.state)
/******************************************************************************
* 1. Open session proposal modal for confirmation / rejection
*****************************************************************************/
const onSessionProposal = useCallback(
(proposal: SignClientTypes.EventArguments['session_proposal']) => {
ModalStore.open('SessionProposalModal', { proposal })
},
[]
)
/******************************************************************************
* 3. Open request handling modal based on method that was used
*****************************************************************************/
const onSessionRequest = useCallback(
async (requestEvent: SignClientTypes.EventArguments['session_request']) => {
console.log('session_request', requestEvent)
const { topic, params } = requestEvent
const { request } = params
const requestSession = web3wallet.getActiveSessions()[topic]
switch (request.method) {
case EIP155_SIGNING_METHODS.ETH_SIGN:
case EIP155_SIGNING_METHODS.PERSONAL_SIGN:
return ModalStore.open('SessionSignModal', { requestEvent, requestSession })
case EIP155_SIGNING_METHODS.ETH_SIGN_TYPED_DATA:
case EIP155_SIGNING_METHODS.ETH_SIGN_TYPED_DATA_V3:
case EIP155_SIGNING_METHODS.ETH_SIGN_TYPED_DATA_V4:
return ModalStore.open('SessionSignTypedDataModal', { requestEvent, requestSession })
case EIP155_SIGNING_METHODS.ETH_SEND_TRANSACTION:
case EIP155_SIGNING_METHODS.ETH_SIGN_TRANSACTION:
return ModalStore.open('SessionSendTransactionModal', { requestEvent, requestSession })
default:
return ModalStore.open('SessionUnsuportedMethodModal', { requestEvent, requestSession })
}
},
[]
)
/******************************************************************************
* Set up WalletConnect event listeners
*****************************************************************************/
useEffect(() => {
if (web3WalletReady) {
web3wallet.on('session_proposal', onSessionProposal)
web3wallet.on('session_request', onSessionRequest)
web3wallet.on('session_delete', data => console.log('delete', data))
}
return () => {
if (web3WalletReady) {
web3wallet.off('session_proposal', onSessionProposal)
web3wallet.off('session_request', onSessionRequest)
web3wallet.off('session_delete', data => console.log('delete', data))
}
}
}, [web3WalletReady, onSessionProposal, onSessionRequest])
}

View File

@ -0,0 +1,49 @@
import { providers, Wallet } from 'ethers'
/**
* Types
*/
interface IInitArgs {
mnemonic?: string
}
/**
* Library
*/
export default class EIP155Lib {
wallet: Wallet
constructor(wallet: Wallet) {
this.wallet = wallet
}
static init({ mnemonic }: IInitArgs) {
const wallet = mnemonic ? Wallet.fromMnemonic(mnemonic) : Wallet.createRandom()
return new EIP155Lib(wallet)
}
getMnemonic() {
return this.wallet.mnemonic.phrase
}
getAddress() {
return this.wallet.address
}
signMessage(message: string) {
return this.wallet.signMessage(message)
}
_signTypedData(domain: any, types: any, data: any) {
return this.wallet._signTypedData(domain, types, data)
}
connect(provider: providers.JsonRpcProvider) {
return this.wallet.connect(provider)
}
signTransaction(transaction: providers.TransactionRequest) {
return this.wallet.signTransaction(transaction)
}
}

View File

@ -0,0 +1,37 @@
import Layout from '@/components/Layout'
import Modal from '@/components/Modal'
import useWalletConnectEventsManager from '@/hooks/useWalletConnectEventsManager'
import SettingsStore from '@/store/SettingsStore'
import { createLegacySignClient } from '@/utils/LegacyWalletConnectUtil'
import { createTheme, NextUIProvider } from '@nextui-org/react'
import { AppProps } from 'next/app'
import { useSnapshot } from 'valtio'
import '../../public/main.css'
import useAccounts from '../hooks/useAccounts'
import { createWeb3Wallet } from '../utils/WalletConnectUtil'
// Step 1 - Initialize web3wallet with default region
createWeb3Wallet()
export default function App({ Component, pageProps }: AppProps) {
const { web3WalletReady } = useSnapshot(SettingsStore.state)
// Step 2 - Initialize wallets
useAccounts()
// Step 3 - Once initialized, set up wallet connect event manager
useWalletConnectEventsManager()
// Backwards compatibility only - create a legacy v1 SignClient instance.
createLegacySignClient()
return (
<NextUIProvider theme={createTheme({ type: 'dark' })}>
<Layout initialized={web3WalletReady}>
<Component {...pageProps} />
</Layout>
<Modal />
</NextUIProvider>
)
}

View File

@ -0,0 +1,37 @@
import AccountCard from '@/components/AccountCard'
import AccountPicker from '@/components/AccountPicker'
import PageHeader from '@/components/PageHeader'
import { EIP155_MAINNET_CHAINS, EIP155_TEST_CHAINS } from '@/data/EIP155Data'
import SettingsStore from '@/store/SettingsStore'
import { Text } from '@nextui-org/react'
import { Fragment } from 'react'
import { useSnapshot } from 'valtio'
export default function HomePage() {
const { testNets, eip155Address } = useSnapshot(SettingsStore.state)
return (
<Fragment>
<PageHeader title="Accounts">
<AccountPicker />
</PageHeader>
<Text h4 css={{ marginBottom: '$5' }}>
Mainnets
</Text>
{Object.values(EIP155_MAINNET_CHAINS).map(({ name, logo, rgb }) => (
<AccountCard key={name} name={name} logo={logo} rgb={rgb} address={eip155Address} />
))}
{testNets ? (
<Fragment>
<Text h4 css={{ marginBottom: '$5' }}>
Testnets
</Text>
{Object.values(EIP155_TEST_CHAINS).map(({ name, logo, rgb }) => (
<AccountCard key={name} name={name} logo={logo} rgb={rgb} address={eip155Address} />
))}
</Fragment>
) : null}
</Fragment>
)
}

View File

@ -0,0 +1,125 @@
import PageHeader from '@/components/PageHeader'
import ProjectInfoCard from '@/components/ProjectInfoCard'
import SessionChainCard from '@/components/SessionChainCard'
import { web3wallet } from '@/utils/WalletConnectUtil'
import { Button, Divider, Loading, Row, Text } from '@nextui-org/react'
import { getSdkError } from '@walletconnect/utils'
import { useRouter } from 'next/router'
import { Fragment, useEffect, useState } from 'react'
/**
* Component
*/
export default function SessionPage() {
const [topic, setTopic] = useState('')
const [updated, setUpdated] = useState(new Date())
const { query, replace } = useRouter()
const [loading, setLoading] = useState(false)
useEffect(() => {
if (query?.topic) {
setTopic(query.topic as string)
}
}, [query])
const session = web3wallet.getActiveSessions()[topic]
if (!session) {
return null
}
// Get necessary data from session
const expiryDate = new Date(session.expiry * 1000)
const { namespaces } = session
// Handle deletion of a session
async function onDeleteSession() {
setLoading(true)
await web3wallet.disconnectSession({ topic, reason: getSdkError('USER_DISCONNECTED') })
replace('/sessions')
setLoading(false)
}
async function onSessionEmit() {
setLoading(true)
await web3wallet.emitSessionEvent({
topic,
event: { name: 'chainChanged', data: 'Hello World' },
chainId: 'eip155:1'
})
setLoading(false)
}
const newNs = {
eip155: {
accounts: [
'eip155:1:0x70012948c348CBF00806A3C79E3c5DAdFaAa347B',
'eip155:137:0x70012948c348CBF00806A3C79E3c5DAdFaAa347B'
],
methods: [
'eth_sendTransaction',
'eth_signTransaction',
'eth_sign',
'personal_sign',
'eth_signTypedData'
],
events: ['chainChanged', 'accountsChanged']
}
}
async function onSessionUpdate() {
setLoading(true)
await web3wallet.updateSession({ topic, namespaces: newNs })
setUpdated(new Date())
setLoading(false)
}
return (
<Fragment>
<PageHeader title="Session Details" />
<ProjectInfoCard metadata={session.peer.metadata} />
<Divider y={2} />
{Object.keys(namespaces).map(chain => {
return (
<Fragment key={chain}>
<Text h4 css={{ marginBottom: '$5' }}>{`Review ${chain} permissions`}</Text>
<SessionChainCard namespace={namespaces[chain]} />
{/* {renderAccountSelection(chain)} */}
<Divider y={2} />
</Fragment>
)
})}
<Row justify="space-between">
<Text h5>Expiry</Text>
<Text css={{ color: '$gray400' }}>{expiryDate.toDateString()}</Text>
</Row>
<Row justify="space-between">
<Text h5>Last Updated</Text>
<Text css={{ color: '$gray400' }}>{updated.toDateString()}</Text>
</Row>
<Row css={{ marginTop: '$10' }}>
<Button flat css={{ width: '100%' }} color="error" onPress={onDeleteSession}>
{loading ? <Loading size="sm" color="error" /> : 'Delete'}
</Button>
</Row>
<Row css={{ marginTop: '$10' }}>
<Button flat css={{ width: '100%' }} color="secondary" onPress={onSessionEmit}>
{loading ? <Loading size="sm" color="secondary" /> : 'Emit'}
</Button>
</Row>
<Row css={{ marginTop: '$10' }}>
<Button flat css={{ width: '100%' }} color="warning" onPress={onSessionUpdate}>
{loading ? <Loading size="sm" color="warning" /> : 'Update'}
</Button>
</Row>
</Fragment>
)
}

View File

@ -0,0 +1,41 @@
import PageHeader from '@/components/PageHeader'
import SessionCard from '@/components/SessionCard'
import { legacySignClient } from '@/utils/LegacyWalletConnectUtil'
import { web3wallet } from '@/utils/WalletConnectUtil'
import { Text } from '@nextui-org/react'
import { Fragment } from 'react'
export default function SessionsPage() {
const sessions = web3wallet.getActiveSessions()
const sessionTopics = Object.keys(sessions)
const legacySession = legacySignClient?.session
if (!legacySession && !sessionTopics.length) {
return (
<Fragment>
<PageHeader title="Sessions" />
<Text css={{ opacity: '0.5', textAlign: 'center', marginTop: '$20' }}>No sessions</Text>
</Fragment>
)
}
return (
<Fragment>
<PageHeader title="Sessions" />
{legacySession ? (
<SessionCard
name={legacySession.peerMeta?.name + ' (v1/legacy)'}
url={legacySession.peerMeta?.url}
logo={legacySession.peerMeta?.icons[0]}
/>
) : null}
{sessionTopics.length
? sessionTopics.map(topic => {
const { name, icons, url } = sessions[topic].peer.metadata
return <SessionCard key={topic} topic={topic} name={name} logo={icons[0]} url={url} />
})
: null}
</Fragment>
)
}

View File

@ -0,0 +1,71 @@
import PageHeader from '@/components/PageHeader'
import RelayRegionPicker from '@/components/RelayRegionPicker'
import SettingsStore from '@/store/SettingsStore'
import { eip155Wallets } from '@/utils/EIP155WalletUtil'
import { Card, Divider, Row, Switch, Text } from '@nextui-org/react'
import { Fragment } from 'react'
import { useSnapshot } from 'valtio'
import packageJSON from '../../package.json'
export default function SettingsPage() {
const { testNets, eip155Address } = useSnapshot(SettingsStore.state)
return (
<Fragment>
<PageHeader title="Settings" />
<Text h4 css={{ marginBottom: '$5' }}>
Packages
</Text>
<Row justify="space-between" align="center">
<Text color="$gray400">@walletconnect/web3wallet</Text>
<Text color="$gray400">{packageJSON.dependencies['@walletconnect/web3wallet']}</Text>
</Row>
<Row justify="space-between" align="center">
<Text color="$gray400">@walletconnect/core</Text>
<Text color="$gray400">{packageJSON.dependencies['@walletconnect/core']}</Text>
</Row>
<Row justify="space-between" align="center">
<Text color="$gray400">@walletconnect/utils</Text>
<Text color="$gray400">{packageJSON.dependencies['@walletconnect/utils']}</Text>
</Row>
<Row justify="space-between" align="center">
<Text color="$gray400">@walletconnect/types</Text>
<Text color="$gray400">{packageJSON.devDependencies['@walletconnect/types']}</Text>
</Row>
<Divider y={2} />
<Text h4 css={{ marginBottom: '$5' }}>
Testnets
</Text>
<Row justify="space-between" align="center">
<Switch checked={testNets} onChange={SettingsStore.toggleTestNets} />
<Text>{testNets ? 'Enabled' : 'Disabled'}</Text>
</Row>
<Divider y={2} />
<Row justify="space-between" align="center">
<Text h4 css={{ marginBottom: '$5' }}>
Relayer Region
</Text>
<RelayRegionPicker />
</Row>
<Divider y={2} />
<Text css={{ color: '$yellow500', marginBottom: '$5', textAlign: 'left', padding: 0 }}>
Warning: mnemonics and secret keys are provided for development purposes only and should not
be used elsewhere!
</Text>
<Text h4 css={{ marginTop: '$5', marginBottom: '$5' }}>
EIP155 Mnemonic
</Text>
<Card variant="bordered" css={{ minHeight: '100px', padding: 10 }}>
<Text css={{ fontFamily: '$mono' }}>{eip155Wallets[eip155Address].getMnemonic()}</Text>
</Card>
</Fragment>
)
}

View File

@ -0,0 +1,63 @@
import PageHeader from '@/components/PageHeader'
import QrReader from '@/components/QrReader'
import { createLegacySignClient } from '@/utils/LegacyWalletConnectUtil'
import { web3wallet } from '@/utils/WalletConnectUtil'
import { Button, Input, Loading, Text } from '@nextui-org/react'
import { parseUri } from '@walletconnect/utils'
import { Fragment, useState } from 'react'
export default function WalletConnectPage() {
const [uri, setUri] = useState('')
const [loading, setLoading] = useState(false)
async function onConnect(uri: string) {
try {
setLoading(true)
const { version } = parseUri(uri)
// Route the provided URI to the v1 SignClient if URI version indicates it, else use v2.
if (version === 1) {
createLegacySignClient({ uri })
} else {
await web3wallet.pair({ uri })
}
} catch (err: unknown) {
alert(err)
} finally {
setUri('')
setLoading(false)
}
}
return (
<Fragment>
<PageHeader title="WalletConnect" />
<QrReader onConnect={onConnect} />
<Text size={13} css={{ textAlign: 'center', marginTop: '$10', marginBottom: '$10' }}>
or use walletconnect uri
</Text>
<Input
css={{ width: '100%' }}
bordered
aria-label="wc url connect input"
placeholder="e.g. wc:a281567bb3e4..."
onChange={e => setUri(e.target.value)}
value={uri}
contentRight={
<Button
size="xs"
disabled={!uri}
css={{ marginLeft: -60 }}
onPress={() => onConnect(uri)}
color="gradient"
>
{loading ? <Loading size="sm" /> : 'Connect'}
</Button>
}
/>
</Fragment>
)
}

View File

@ -0,0 +1,59 @@
import { IClientMeta, IWalletConnectSession } from '@walletconnect/legacy-types'
import { SessionTypes, SignClientTypes } from '@walletconnect/types'
import { proxy } from 'valtio'
/**
* Types
*/
interface ModalData {
proposal?: SignClientTypes.EventArguments['session_proposal']
requestEvent?: SignClientTypes.EventArguments['session_request']
requestSession?: SessionTypes.Struct
legacyProposal?: {
id: number
params: [{ chainId: number; peerId: string; peerMeta: IClientMeta }]
}
legacyCallRequestEvent?: { id: number; method: string; params: any[] }
legacyRequestSession?: IWalletConnectSession
}
interface State {
open: boolean
view?:
| 'SessionProposalModal'
| 'SessionSignModal'
| 'SessionSignTypedDataModal'
| 'SessionSendTransactionModal'
| 'SessionUnsuportedMethodModal'
| 'LegacySessionProposalModal'
| 'LegacySessionSignModal'
| 'LegacySessionSignTypedDataModal'
| 'LegacySessionSendTransactionModal'
data?: ModalData
}
/**
* State
*/
const state = proxy<State>({
open: false
})
/**
* Store / Actions
*/
const ModalStore = {
state,
open(view: State['view'], data: State['data']) {
state.view = view
state.data = data
state.open = true
},
close() {
state.open = false
}
}
export default ModalStore

View File

@ -0,0 +1,51 @@
import { proxy } from 'valtio'
/**
* Types
*/
interface State {
testNets: boolean
account: number
eip155Address: string
web3WalletReady: boolean
}
/**
* State
*/
const state = proxy<State>({
testNets: typeof localStorage !== 'undefined' ? Boolean(localStorage.getItem('TEST_NETS')) : true,
account: 0,
eip155Address: '',
web3WalletReady: false
})
/**
* Store / Actions
*/
const SettingsStore = {
state,
setAccount(value: number) {
state.account = value
},
setEIP155Address(eip155Address: string) {
state.eip155Address = eip155Address
},
toggleTestNets() {
state.testNets = !state.testNets
if (state.testNets) {
localStorage.setItem('TEST_NETS', 'YES')
} else {
localStorage.removeItem('TEST_NETS')
}
},
setWeb3WalletReady(value: boolean) {
state.web3WalletReady = value
}
}
export default SettingsStore

View File

@ -0,0 +1,57 @@
import { EIP155_CHAINS, EIP155_SIGNING_METHODS, TEIP155Chain } from '@/data/EIP155Data'
import { eip155Addresses, eip155Wallets } from '@/utils/EIP155WalletUtil'
import {
getSignParamsMessage,
getSignTypedDataParamsData,
getWalletAddressFromParams
} from '@/utils/HelperUtil'
import { formatJsonRpcError, formatJsonRpcResult } from '@json-rpc-tools/utils'
import { SignClientTypes } from '@walletconnect/types'
import { getSdkError } from '@walletconnect/utils'
import { providers } from 'ethers'
export async function approveEIP155Request(
requestEvent: SignClientTypes.EventArguments['session_request']
) {
const { params, id } = requestEvent
const { chainId, request } = params
const wallet = eip155Wallets[getWalletAddressFromParams(eip155Addresses, params)]
switch (request.method) {
case EIP155_SIGNING_METHODS.PERSONAL_SIGN:
case EIP155_SIGNING_METHODS.ETH_SIGN:
const message = getSignParamsMessage(request.params)
const signedMessage = await wallet.signMessage(message)
return formatJsonRpcResult(id, signedMessage)
case EIP155_SIGNING_METHODS.ETH_SIGN_TYPED_DATA:
case EIP155_SIGNING_METHODS.ETH_SIGN_TYPED_DATA_V3:
case EIP155_SIGNING_METHODS.ETH_SIGN_TYPED_DATA_V4:
const { domain, types, message: data } = getSignTypedDataParamsData(request.params)
// https://github.com/ethers-io/ethers.js/issues/687#issuecomment-714069471
delete types.EIP712Domain
const signedData = await wallet._signTypedData(domain, types, data)
return formatJsonRpcResult(id, signedData)
case EIP155_SIGNING_METHODS.ETH_SEND_TRANSACTION:
const provider = new providers.JsonRpcProvider(EIP155_CHAINS[chainId as TEIP155Chain].rpc)
const sendTransaction = request.params[0]
const connectedWallet = wallet.connect(provider)
const { hash } = await connectedWallet.sendTransaction(sendTransaction)
return formatJsonRpcResult(id, hash)
case EIP155_SIGNING_METHODS.ETH_SIGN_TRANSACTION:
const signTransaction = request.params[0]
const signature = await wallet.signTransaction(signTransaction)
return formatJsonRpcResult(id, signature)
default:
throw new Error(getSdkError('INVALID_METHOD').message)
}
}
export function rejectEIP155Request(request: SignClientTypes.EventArguments['session_request']) {
const { id } = request
return formatJsonRpcError(id, getSdkError('USER_REJECTED_METHODS').message)
}

View File

@ -0,0 +1,43 @@
import EIP155Lib from '@/lib/EIP155Lib'
export let wallet1: EIP155Lib
export let wallet2: EIP155Lib
export let eip155Wallets: Record<string, EIP155Lib>
export let eip155Addresses: string[]
let address1: string
let address2: string
/**
* Utilities
*/
export function createOrRestoreEIP155Wallet() {
const mnemonic1 = localStorage.getItem('EIP155_MNEMONIC_1')
const mnemonic2 = localStorage.getItem('EIP155_MNEMONIC_2')
if (mnemonic1 && mnemonic2) {
wallet1 = EIP155Lib.init({ mnemonic: mnemonic1 })
wallet2 = EIP155Lib.init({ mnemonic: mnemonic2 })
} else {
wallet1 = EIP155Lib.init({})
wallet2 = EIP155Lib.init({})
// Don't store mnemonic in local storage in a production project!
localStorage.setItem('EIP155_MNEMONIC_1', wallet1.getMnemonic())
localStorage.setItem('EIP155_MNEMONIC_2', wallet2.getMnemonic())
}
address1 = wallet1.getAddress()
address2 = wallet2.getAddress()
eip155Wallets = {
[address1]: wallet1,
[address2]: wallet2
}
eip155Addresses = Object.keys(eip155Wallets)
return {
eip155Wallets,
eip155Addresses
}
}

View File

@ -0,0 +1,86 @@
import { EIP155_CHAINS, TEIP155Chain } from '@/data/EIP155Data'
import { utils } from 'ethers'
/**
* Truncates string (in the middle) via given lenght value
*/
export function truncate(value: string, length: number) {
if (value?.length && value.length <= length) {
return value
}
const separator = '...'
const stringLength = length - separator.length
const frontLength = Math.ceil(stringLength / 2)
const backLength = Math.floor(stringLength / 2)
return value.substring(0, frontLength) + separator + value.substring(value.length - backLength)
}
/**
* Converts hex to utf8 string if it is valid bytes
*/
export function convertHexToUtf8(value: string) {
if (utils.isHexString(value)) {
return utils.toUtf8String(value)
}
return value
}
/**
* Gets message from various signing request methods by filtering out
* a value that is not an address (thus is a message).
* If it is a hex string, it gets converted to utf8 string
*/
export function getSignParamsMessage(params: string[]) {
const message = params.filter(p => !utils.isAddress(p))[0]
return convertHexToUtf8(message)
}
/**
* Gets data from various signTypedData request methods by filtering out
* a value that is not an address (thus is data).
* If data is a string convert it to object
*/
export function getSignTypedDataParamsData(params: string[]) {
const data = params.filter(p => !utils.isAddress(p))[0]
if (typeof data === 'string') {
return JSON.parse(data)
}
return data
}
/**
* Get our address from params checking if params string contains one
* of our wallet addresses
*/
export function getWalletAddressFromParams(addresses: string[], params: any) {
const paramsString = JSON.stringify(params)
let address = ''
addresses.forEach(addr => {
if (paramsString.toLowerCase().includes(addr.toLowerCase())) {
address = addr
}
})
return address
}
/**
* Check if chain is part of EIP155 standard
*/
export function isEIP155Chain(chain: string) {
return chain.includes('eip155')
}
/**
* Formats chainId to its name
*/
export function formatChainName(chainId: string) {
return EIP155_CHAINS[chainId as TEIP155Chain]?.name ?? chainId
}

View File

@ -0,0 +1,96 @@
import { EIP155_SIGNING_METHODS } from '@/data/EIP155Data'
import ModalStore from '@/store/ModalStore'
import LegacySignClient from '@walletconnect/client'
import { IWalletConnectSession } from '@walletconnect/legacy-types'
export let legacySignClient: LegacySignClient
export function createLegacySignClient({ uri }: { uri?: string } = {}) {
// If URI is passed always create a new session,
// otherwise fall back to cached session if client isn't already instantiated.
if (uri) {
deleteCachedLegacySession()
legacySignClient = new LegacySignClient({ uri })
} else if (!legacySignClient && getCachedLegacySession()) {
const session = getCachedLegacySession()
legacySignClient = new LegacySignClient({ session })
} else {
return
}
legacySignClient.on('session_request', (error, payload) => {
if (error) {
throw new Error(`legacySignClient > session_request failed: ${error}`)
}
ModalStore.open('LegacySessionProposalModal', { legacyProposal: payload })
})
legacySignClient.on('connect', () => {
console.log('legacySignClient > connect')
})
legacySignClient.on('error', error => {
throw new Error(`legacySignClient > on error: ${error}`)
})
legacySignClient.on('call_request', (error, payload) => {
if (error) {
throw new Error(`legacySignClient > call_request failed: ${error}`)
}
onCallRequest(payload)
})
legacySignClient.on('disconnect', async () => {
deleteCachedLegacySession()
})
}
const onCallRequest = async (payload: { id: number; method: string; params: any[] }) => {
switch (payload.method) {
case EIP155_SIGNING_METHODS.ETH_SIGN:
case EIP155_SIGNING_METHODS.PERSONAL_SIGN:
return ModalStore.open('LegacySessionSignModal', {
legacyCallRequestEvent: payload,
legacyRequestSession: legacySignClient.session
})
case EIP155_SIGNING_METHODS.ETH_SIGN_TYPED_DATA:
case EIP155_SIGNING_METHODS.ETH_SIGN_TYPED_DATA_V3:
case EIP155_SIGNING_METHODS.ETH_SIGN_TYPED_DATA_V4:
return ModalStore.open('LegacySessionSignTypedDataModal', {
legacyCallRequestEvent: payload,
legacyRequestSession: legacySignClient.session
})
case EIP155_SIGNING_METHODS.ETH_SEND_TRANSACTION:
case EIP155_SIGNING_METHODS.ETH_SIGN_TRANSACTION:
return ModalStore.open('LegacySessionSendTransactionModal', {
legacyCallRequestEvent: payload,
legacyRequestSession: legacySignClient.session
})
default:
alert(`${payload.method} is not supported for WalletConnect v1`)
}
}
function getCachedLegacySession(): IWalletConnectSession | undefined {
if (typeof window === 'undefined') return
const local = window.localStorage ? window.localStorage.getItem('walletconnect') : null
let session = null
if (local) {
try {
session = JSON.parse(local)
} catch (error) {
throw error
}
}
return session
}
function deleteCachedLegacySession(): void {
if (typeof window === 'undefined') return
window.localStorage.removeItem('walletconnect')
}

View File

@ -0,0 +1,27 @@
import SettingsStore from '@/store/SettingsStore'
import { Core } from '@walletconnect/core'
import { Web3Wallet } from '@walletconnect/web3wallet'
export let web3wallet: InstanceType<typeof Web3Wallet>
export let core: InstanceType<typeof Core>
export async function createWeb3Wallet() {
if (!SettingsStore.state.web3WalletReady && typeof window !== 'undefined') {
core = new Core({
projectId: process.env.NEXT_PUBLIC_PROJECT_ID,
logger: 'debug'
})
web3wallet = await Web3Wallet.init({
core,
metadata: {
name: 'React Wallet',
description: 'React Wallet for WalletConnect',
url: 'https://walletconnect.com/',
icons: ['https://avatars.githubusercontent.com/u/37784886']
}
})
SettingsStore.setWeb3WalletReady(true)
}
}

View File

@ -0,0 +1,105 @@
import ProjectInfoCard from '@/components/ProjectInfoCard'
import ProposalSelectSection from '@/components/ProposalSelectSection'
import RequestModalContainer from '@/components/RequestModalContainer'
import ModalStore from '@/store/ModalStore'
import { eip155Addresses } from '@/utils/EIP155WalletUtil'
import { isEIP155Chain } from '@/utils/HelperUtil'
import { legacySignClient } from '@/utils/LegacyWalletConnectUtil'
import { Button, Divider, Modal, Text } from '@nextui-org/react'
import { getSdkError } from '@walletconnect/utils'
import { Fragment, useState } from 'react'
export default function LegacySessionProposalModal() {
const [selectedAccounts, setSelectedAccounts] = useState<Record<string, string[]>>({})
const hasSelected = Object.keys(selectedAccounts).length
// Get proposal data and wallet address from store
const proposal = ModalStore.state.data?.legacyProposal
// Ensure proposal is defined
if (!proposal) {
return <Text>Missing proposal data</Text>
}
// Get required proposal data
const { id, params } = proposal
const [{ chainId, peerMeta }] = params
// Add / remove address from EIP155 selection
function onSelectAccount(chain: string, account: string) {
if (selectedAccounts[chain]?.includes(account)) {
const newSelectedAccounts = selectedAccounts[chain]?.filter(a => a !== account)
setSelectedAccounts(prev => ({
...prev,
[chain]: newSelectedAccounts
}))
} else {
const prevChainAddresses = selectedAccounts[chain] ?? []
setSelectedAccounts(prev => ({
...prev,
[chain]: [...prevChainAddresses, account]
}))
}
}
// Hanlde approve action, construct session namespace
async function onApprove() {
if (proposal) {
legacySignClient.approveSession({
accounts: selectedAccounts['eip155'],
chainId: chainId ?? 1
})
}
ModalStore.close()
}
// Handle reject action
function onReject() {
if (proposal) {
legacySignClient.rejectSession(getSdkError('USER_REJECTED_METHODS'))
}
ModalStore.close()
}
// Render account selection checkboxes based on chain
function renderAccountSelection(chain: string) {
if (isEIP155Chain(chain)) {
return (
<ProposalSelectSection
addresses={eip155Addresses}
selectedAddresses={selectedAccounts[chain]}
onSelect={onSelectAccount}
chain={chain}
/>
)
}
}
return (
<Fragment>
<RequestModalContainer title="Session Proposal">
<ProjectInfoCard metadata={peerMeta} />
<Divider y={2} />
{renderAccountSelection('eip155')}
<Divider y={2} />
</RequestModalContainer>
<Modal.Footer>
<Button auto flat color="error" onPress={onReject}>
Reject
</Button>
<Button
auto
flat
color="success"
onPress={onApprove}
disabled={!hasSelected}
css={{ opacity: hasSelected ? 1 : 0.4 }}
>
Approve
</Button>
</Modal.Footer>
</Fragment>
)
}

View File

@ -0,0 +1,96 @@
import ProjectInfoCard from '@/components/ProjectInfoCard'
import RequestDataCard from '@/components/RequestDataCard'
import RequesDetailsCard from '@/components/RequestDetalilsCard'
import RequestMethodCard from '@/components/RequestMethodCard'
import RequestModalContainer from '@/components/RequestModalContainer'
import ModalStore from '@/store/ModalStore'
import { approveEIP155Request, rejectEIP155Request } from '@/utils/EIP155RequestHandlerUtil'
import { legacySignClient } from '@/utils/LegacyWalletConnectUtil'
import { Button, Divider, Loading, Modal, Text } from '@nextui-org/react'
import { Fragment, useState } from 'react'
export default function LegacySessionSendTransactionModal() {
const [loading, setLoading] = useState(false)
// Get request and wallet data from store
const requestEvent = ModalStore.state.data?.legacyCallRequestEvent
const requestSession = ModalStore.state.data?.legacyRequestSession
// Ensure request and wallet are defined
if (!requestEvent || !requestSession) {
return <Text>Missing request data</Text>
}
// Get required proposal data
const { id, method, params } = requestEvent
const transaction = params[0]
// // Remove unneeded key coming from v1 sample dapp that throws Ethers.
if (transaction['gas']) delete transaction['gas']
// Handle approve action
async function onApprove() {
if (requestEvent) {
const { result } = await approveEIP155Request({
id,
topic: '',
params: { request: { method, params }, chainId: '1' }
})
legacySignClient.approveRequest({
id,
result
})
ModalStore.close()
}
}
// Handle reject action
async function onReject() {
if (requestEvent) {
const { error } = rejectEIP155Request({
id,
topic: '',
params: { request: { method, params }, chainId: '1' }
})
legacySignClient.rejectRequest({
id,
error
})
ModalStore.close()
}
}
return (
<Fragment>
<RequestModalContainer title="Send / Sign Transaction">
<ProjectInfoCard metadata={requestSession.peerMeta!} />
<Divider y={2} />
<RequestDataCard data={transaction} />
<Divider y={2} />
<RequesDetailsCard
chains={['eip155:' + legacySignClient.chainId]}
protocol={legacySignClient.protocol}
/>
<Divider y={2} />
<RequestMethodCard methods={[method]} />
</RequestModalContainer>
<Modal.Footer>
<Button auto flat color="error" onPress={onReject} disabled={loading}>
Reject
</Button>
<Button auto flat color="success" onPress={onApprove} disabled={loading}>
{loading ? <Loading size="sm" color="success" /> : 'Approve'}
</Button>
</Modal.Footer>
</Fragment>
)
}

View File

@ -0,0 +1,97 @@
import ProjectInfoCard from '@/components/ProjectInfoCard'
import RequesDetailsCard from '@/components/RequestDetalilsCard'
import RequestMethodCard from '@/components/RequestMethodCard'
import RequestModalContainer from '@/components/RequestModalContainer'
import ModalStore from '@/store/ModalStore'
import { approveEIP155Request, rejectEIP155Request } from '@/utils/EIP155RequestHandlerUtil'
import { getSignParamsMessage } from '@/utils/HelperUtil'
import { legacySignClient } from '@/utils/LegacyWalletConnectUtil'
import { Button, Col, Divider, Modal, Row, Text } from '@nextui-org/react'
import { Fragment } from 'react'
export default function LegacySessionSignModal() {
// Get request and wallet data from store
const requestEvent = ModalStore.state.data?.legacyCallRequestEvent
const requestSession = ModalStore.state.data?.legacyRequestSession
// Ensure request and wallet are defined
if (!requestEvent || !requestSession) {
return <Text>Missing request data</Text>
}
// Get required request data
const { id, method, params } = requestEvent
// Get message, convert it to UTF8 string if it is valid hex
const message = getSignParamsMessage(params)
// Handle approve action (logic varies based on request method)
async function onApprove() {
if (requestEvent) {
const { result } = await approveEIP155Request({
id,
topic: '',
params: { request: { method, params }, chainId: '1' }
})
legacySignClient.approveRequest({
id,
result
})
ModalStore.close()
}
}
// Handle reject action
async function onReject() {
if (requestEvent) {
const { error } = rejectEIP155Request({
id,
topic: '',
params: { request: { method, params }, chainId: '1' }
})
legacySignClient.rejectRequest({
id,
error
})
ModalStore.close()
}
}
return (
<Fragment>
<RequestModalContainer title="Sign Message">
<ProjectInfoCard metadata={requestSession.peerMeta!} />
<Divider y={2} />
<RequesDetailsCard
chains={['eip155:' + legacySignClient.chainId]}
protocol={legacySignClient.protocol}
/>
<Divider y={2} />
<Row>
<Col>
<Text h5>Message</Text>
<Text color="$gray400">{message}</Text>
</Col>
</Row>
<Divider y={2} />
<RequestMethodCard methods={[method]} />
</RequestModalContainer>
<Modal.Footer>
<Button auto flat color="error" onPress={onReject}>
Reject
</Button>
<Button auto flat color="success" onPress={onApprove}>
Approve
</Button>
</Modal.Footer>
</Fragment>
)
}

View File

@ -0,0 +1,93 @@
import ProjectInfoCard from '@/components/ProjectInfoCard'
import RequestDataCard from '@/components/RequestDataCard'
import RequesDetailsCard from '@/components/RequestDetalilsCard'
import RequestMethodCard from '@/components/RequestMethodCard'
import RequestModalContainer from '@/components/RequestModalContainer'
import ModalStore from '@/store/ModalStore'
import { approveEIP155Request, rejectEIP155Request } from '@/utils/EIP155RequestHandlerUtil'
import { getSignTypedDataParamsData } from '@/utils/HelperUtil'
import { legacySignClient } from '@/utils/LegacyWalletConnectUtil'
import { Button, Divider, Modal, Text } from '@nextui-org/react'
import { Fragment } from 'react'
export default function LegacySessionSignTypedDataModal() {
// Get request and wallet data from store
const requestEvent = ModalStore.state.data?.legacyCallRequestEvent
const requestSession = ModalStore.state.data?.legacyRequestSession
// Ensure request and wallet are defined
if (!requestEvent || !requestSession) {
return <Text>Missing request data</Text>
}
// Get required request data
const { id, method, params } = requestEvent
// Get data
const data = getSignTypedDataParamsData(params)
// Handle approve action (logic varies based on request method)
async function onApprove() {
if (requestEvent) {
const { result } = await approveEIP155Request({
id,
topic: '',
params: { request: { method, params }, chainId: '1' }
})
legacySignClient.approveRequest({
id,
result
})
ModalStore.close()
}
}
// Handle reject action
async function onReject() {
if (requestEvent) {
const { error } = rejectEIP155Request({
id,
topic: '',
params: { request: { method, params }, chainId: '1' }
})
legacySignClient.rejectRequest({
id,
error
})
ModalStore.close()
}
}
return (
<Fragment>
<RequestModalContainer title="Sign Typed Data">
<ProjectInfoCard metadata={requestSession.peerMeta!} />
<Divider y={2} />
<RequesDetailsCard
chains={['eip155:' + legacySignClient.chainId]}
protocol={legacySignClient.protocol}
/>
<Divider y={2} />
<RequestDataCard data={data} />
<Divider y={2} />
<RequestMethodCard methods={[method]} />
</RequestModalContainer>
<Modal.Footer>
<Button auto flat color="error" onPress={onReject}>
Reject
</Button>
<Button auto flat color="success" onPress={onApprove}>
Approve
</Button>
</Modal.Footer>
</Fragment>
)
}

View File

@ -0,0 +1,180 @@
import ProjectInfoCard from '@/components/ProjectInfoCard'
import ProposalSelectSection from '@/components/ProposalSelectSection'
import RequestModalContainer from '@/components/RequestModalContainer'
import SessionProposalChainCard from '@/components/SessionProposalChainCard'
import ModalStore from '@/store/ModalStore'
import { eip155Addresses } from '@/utils/EIP155WalletUtil'
import { isEIP155Chain } from '@/utils/HelperUtil'
import { web3wallet } from '@/utils/WalletConnectUtil'
import { Button, Divider, Modal, Text } from '@nextui-org/react'
import { SessionTypes } from '@walletconnect/types'
import { getSdkError } from '@walletconnect/utils'
import { Fragment, useEffect, useState } from 'react'
export default function SessionProposalModal() {
const [selectedAccounts, setSelectedAccounts] = useState<Record<string, string[]>>({})
const hasSelected = Object.keys(selectedAccounts).length
// Get proposal data and wallet address from store
const proposal = ModalStore.state.data?.proposal
useEffect(() => {
console.log('selectedAccounts', selectedAccounts)
}, [selectedAccounts])
// Ensure proposal is defined
if (!proposal) {
return <Text>Missing proposal data</Text>
}
// Get required proposal data
const { id, params } = proposal
const { proposer, requiredNamespaces, optionalNamespaces, sessionProperties, relays } = params
console.log('proposal', params, requiredNamespaces, optionalNamespaces, sessionProperties)
const requiredNamespaceKeys = requiredNamespaces ? Object.keys(requiredNamespaces) : []
const optionalNamespaceKeys = optionalNamespaces ? Object.keys(optionalNamespaces) : []
// Add / remove address from EIP155 selection
function onSelectAccount(chain: string, account: string) {
if (selectedAccounts[chain]?.includes(account)) {
const newSelectedAccounts = selectedAccounts[chain]?.filter(a => a !== account)
setSelectedAccounts(prev => ({
...prev,
[chain]: newSelectedAccounts
}))
} else {
const prevChainAddresses = selectedAccounts[chain] ?? []
setSelectedAccounts(prev => ({
...prev,
[chain]: [...prevChainAddresses, account]
}))
}
}
// Hanlde approve action, construct session namespace
async function onApprove() {
if (proposal) {
let namespaces: SessionTypes.Namespaces = {}
const selectedOptionalNamespaces = []
for (const [chain, account] of Object.entries(selectedAccounts)) {
if (chain.includes('optional')) {
selectedOptionalNamespaces.push(chain.split(':')[1])
}
}
console.log('selectedOptionalNamespaces', selectedOptionalNamespaces)
requiredNamespaceKeys.concat(selectedOptionalNamespaces).forEach(key => {
const accounts: string[] = []
if (requiredNamespaces[key].chains) {
requiredNamespaces[key].chains?.map(chain => {
selectedAccounts[`required:${key}`].map(acc => accounts.push(`${chain}:${acc}`))
})
namespaces[key] = {
accounts,
methods: requiredNamespaces[key].methods,
events: requiredNamespaces[key].events,
chains: requiredNamespaces[key].chains
}
}
if (optionalNamespaces[key] && selectedAccounts[`optional:${key}`]) {
optionalNamespaces[key].chains?.map(chain => {
selectedAccounts[`optional:${key}`].map(acc => accounts.push(`${chain}:${acc}`))
})
namespaces[key] = {
...namespaces[key],
accounts,
methods: optionalNamespaces[key].methods,
events: optionalNamespaces[key].events,
chains: namespaces[key].chains?.concat(optionalNamespaces[key].chains || [])
}
}
})
console.log('namespaces', namespaces)
await web3wallet.approveSession({
id,
relayProtocol: relays[0].protocol,
namespaces
})
}
ModalStore.close()
}
// Hanlde reject action
async function onReject() {
if (proposal) {
await web3wallet.rejectSession({
id,
reason: getSdkError('USER_REJECTED_METHODS')
})
}
ModalStore.close()
}
// Render account selection checkboxes based on chain
function renderAccountSelection(chain: string) {
if (isEIP155Chain(chain)) {
return (
<ProposalSelectSection
addresses={eip155Addresses}
selectedAddresses={selectedAccounts[chain]}
onSelect={onSelectAccount}
chain={chain}
/>
)
}
}
return (
<Fragment>
<RequestModalContainer title="Session Proposal">
<ProjectInfoCard metadata={proposer.metadata} />
<Divider y={2} />
{requiredNamespaceKeys.length ? <Text h4>Required Namespaces</Text> : null}
{requiredNamespaceKeys.map(chain => {
return (
<Fragment key={chain}>
<Text css={{ marginBottom: '$5' }}>{`Review ${chain} permissions`}</Text>
<SessionProposalChainCard requiredNamespace={requiredNamespaces[chain]} />
{renderAccountSelection(`required:${chain}`)}
<Divider y={2} />
</Fragment>
)
})}
{optionalNamespaceKeys ? <Text h4>Optional Namespaces</Text> : null}
{optionalNamespaceKeys.length &&
optionalNamespaceKeys.map(chain => {
return (
<Fragment key={chain}>
<Text css={{ marginBottom: '$5' }}>{`Review ${chain} permissions`}</Text>
<SessionProposalChainCard requiredNamespace={optionalNamespaces[chain]} />
{renderAccountSelection(`optional:${chain}`)}
<Divider y={2} />
</Fragment>
)
})}
</RequestModalContainer>
<Modal.Footer>
<Button auto flat color="error" onPress={onReject}>
Reject
</Button>
<Button
auto
flat
color="success"
onPress={onApprove}
disabled={!hasSelected}
css={{ opacity: hasSelected ? 1 : 0.4 }}
>
Approve
</Button>
</Modal.Footer>
</Fragment>
)
}

View File

@ -0,0 +1,83 @@
import ProjectInfoCard from '@/components/ProjectInfoCard'
import RequestDataCard from '@/components/RequestDataCard'
import RequesDetailsCard from '@/components/RequestDetalilsCard'
import RequestMethodCard from '@/components/RequestMethodCard'
import RequestModalContainer from '@/components/RequestModalContainer'
import ModalStore from '@/store/ModalStore'
import { approveEIP155Request, rejectEIP155Request } from '@/utils/EIP155RequestHandlerUtil'
import { web3wallet } from '@/utils/WalletConnectUtil'
import { Button, Divider, Loading, Modal, Text } from '@nextui-org/react'
import { Fragment, useState } from 'react'
export default function SessionSendTransactionModal() {
const [loading, setLoading] = useState(false)
// Get request and wallet data from store
const requestEvent = ModalStore.state.data?.requestEvent
const requestSession = ModalStore.state.data?.requestSession
// Ensure request and wallet are defined
if (!requestEvent || !requestSession) {
return <Text>Missing request data</Text>
}
// Get required proposal data
const { topic, params } = requestEvent
const { request, chainId } = params
const transaction = request.params[0]
// Handle approve action
async function onApprove() {
if (requestEvent) {
setLoading(true)
const response = await approveEIP155Request(requestEvent)
await web3wallet.respondSessionRequest({
topic,
response
})
ModalStore.close()
}
}
// Handle reject action
async function onReject() {
if (requestEvent) {
const response = rejectEIP155Request(requestEvent)
await web3wallet.respondSessionRequest({
topic,
response
})
ModalStore.close()
}
}
return (
<Fragment>
<RequestModalContainer title="Send / Sign Transaction">
<ProjectInfoCard metadata={requestSession.peer.metadata} />
<Divider y={2} />
<RequestDataCard data={transaction} />
<Divider y={2} />
<RequesDetailsCard chains={[chainId ?? '']} protocol={requestSession.relay.protocol} />
<Divider y={2} />
<RequestMethodCard methods={[request.method]} />
</RequestModalContainer>
<Modal.Footer>
<Button auto flat color="error" onPress={onReject} disabled={loading}>
Reject
</Button>
<Button auto flat color="success" onPress={onApprove} disabled={loading}>
{loading ? <Loading size="sm" color="success" /> : 'Approve'}
</Button>
</Modal.Footer>
</Fragment>
)
}

View File

@ -0,0 +1,86 @@
import ProjectInfoCard from '@/components/ProjectInfoCard'
import RequesDetailsCard from '@/components/RequestDetalilsCard'
import RequestMethodCard from '@/components/RequestMethodCard'
import RequestModalContainer from '@/components/RequestModalContainer'
import ModalStore from '@/store/ModalStore'
import { approveEIP155Request, rejectEIP155Request } from '@/utils/EIP155RequestHandlerUtil'
import { getSignParamsMessage } from '@/utils/HelperUtil'
import { web3wallet } from '@/utils/WalletConnectUtil'
import { Button, Col, Divider, Modal, Row, Text } from '@nextui-org/react'
import { Fragment } from 'react'
export default function SessionSignModal() {
// Get request and wallet data from store
const requestEvent = ModalStore.state.data?.requestEvent
const requestSession = ModalStore.state.data?.requestSession
// Ensure request and wallet are defined
if (!requestEvent || !requestSession) {
return <Text>Missing request data</Text>
}
// Get required request data
const { topic, params } = requestEvent
const { request, chainId } = params
// Get message, convert it to UTF8 string if it is valid hex
const message = getSignParamsMessage(request.params)
// Handle approve action (logic varies based on request method)
async function onApprove() {
if (requestEvent) {
const response = await approveEIP155Request(requestEvent)
await web3wallet.respondSessionRequest({
topic,
response
})
ModalStore.close()
}
}
// Handle reject action
async function onReject() {
if (requestEvent) {
const response = rejectEIP155Request(requestEvent)
await web3wallet.respondSessionRequest({
topic,
response
})
ModalStore.close()
}
}
return (
<Fragment>
<RequestModalContainer title="Sign Message">
<ProjectInfoCard metadata={requestSession.peer.metadata} />
<Divider y={2} />
<RequesDetailsCard chains={[chainId ?? '']} protocol={requestSession.relay.protocol} />
<Divider y={2} />
<Row>
<Col>
<Text h5>Message</Text>
<Text color="$gray400">{message}</Text>
</Col>
</Row>
<Divider y={2} />
<RequestMethodCard methods={[request.method]} />
</RequestModalContainer>
<Modal.Footer>
<Button auto flat color="error" onPress={onReject}>
Reject
</Button>
<Button auto flat color="success" onPress={onApprove}>
Approve
</Button>
</Modal.Footer>
</Fragment>
)
}

View File

@ -0,0 +1,82 @@
import ProjectInfoCard from '@/components/ProjectInfoCard'
import RequestDataCard from '@/components/RequestDataCard'
import RequesDetailsCard from '@/components/RequestDetalilsCard'
import RequestMethodCard from '@/components/RequestMethodCard'
import RequestModalContainer from '@/components/RequestModalContainer'
import ModalStore from '@/store/ModalStore'
import { approveEIP155Request, rejectEIP155Request } from '@/utils/EIP155RequestHandlerUtil'
import { getSignTypedDataParamsData } from '@/utils/HelperUtil'
import { web3wallet } from '@/utils/WalletConnectUtil'
import { Button, Divider, Modal, Text } from '@nextui-org/react'
import { Fragment } from 'react'
export default function SessionSignTypedDataModal() {
// Get request and wallet data from store
const requestEvent = ModalStore.state.data?.requestEvent
const requestSession = ModalStore.state.data?.requestSession
// Ensure request and wallet are defined
if (!requestEvent || !requestSession) {
return <Text>Missing request data</Text>
}
// Get required request data
const { topic, params } = requestEvent
const { request, chainId } = params
// Get data
const data = getSignTypedDataParamsData(request.params)
// Handle approve action (logic varies based on request method)
async function onApprove() {
if (requestEvent) {
const response = await approveEIP155Request(requestEvent)
await web3wallet.respondSessionRequest({
topic,
response
})
ModalStore.close()
}
}
// Handle reject action
async function onReject() {
if (requestEvent) {
const response = rejectEIP155Request(requestEvent)
await web3wallet.respondSessionRequest({
topic,
response
})
ModalStore.close()
}
}
return (
<Fragment>
<RequestModalContainer title="Sign Typed Data">
<ProjectInfoCard metadata={requestSession.peer.metadata} />
<Divider y={2} />
<RequesDetailsCard chains={[chainId ?? '']} protocol={requestSession.relay.protocol} />
<Divider y={2} />
<RequestDataCard data={data} />
<Divider y={2} />
<RequestMethodCard methods={[request.method]} />
</RequestModalContainer>
<Modal.Footer>
<Button auto flat color="error" onPress={onReject}>
Reject
</Button>
<Button auto flat color="success" onPress={onApprove}>
Approve
</Button>
</Modal.Footer>
</Fragment>
)
}

View File

@ -0,0 +1,44 @@
import ProjectInfoCard from '@/components/ProjectInfoCard'
import RequesDetailsCard from '@/components/RequestDetalilsCard'
import RequestMethodCard from '@/components/RequestMethodCard'
import RequestModalContainer from '@/components/RequestModalContainer'
import ModalStore from '@/store/ModalStore'
import { Button, Divider, Modal, Text } from '@nextui-org/react'
import { Fragment } from 'react'
export default function SessionUnsuportedMethodModal() {
// Get request and wallet data from store
const requestEvent = ModalStore.state.data?.requestEvent
const requestSession = ModalStore.state.data?.requestSession
// Ensure request and wallet are defined
if (!requestEvent || !requestSession) {
return <Text>Missing request data</Text>
}
// Get required request data
const { topic, params } = requestEvent
const { chainId, request } = params
return (
<Fragment>
<RequestModalContainer title="Unsuported Method">
<ProjectInfoCard metadata={requestSession.peer.metadata} />
<Divider y={2} />
<RequesDetailsCard chains={[chainId ?? '']} protocol={requestSession.relay.protocol} />
<Divider y={2} />
<RequestMethodCard methods={[request.method]} />
</RequestModalContainer>
<Modal.Footer>
<Button auto flat color="error" onPress={ModalStore.close}>
Close
</Button>
</Modal.Footer>
</Fragment>
)
}

View File

@ -0,0 +1,34 @@
{
"compilerOptions": {
"target": "es5",
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"incremental": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
}
},
"include": [
"next-env.d.ts",
"**/*.ts",
"**/*.tsx"
],
"exclude": [
"node_modules"
]
}

File diff suppressed because it is too large Load Diff