├── .github └── workflows │ └── build.yml ├── .gitignore ├── .prettierrc ├── README.md ├── index.html ├── package.json ├── pnpm-lock.yaml ├── public ├── favicon.ico ├── logo192.png ├── logo512.png ├── manifest.json └── robots.txt ├── screenshots ├── screen-open.png └── screen-txn.png ├── src ├── App.tsx ├── README.md ├── components │ ├── Console │ │ ├── index.tsx │ │ └── styles.css.ts │ └── Group │ │ ├── index.tsx │ │ └── styles.css.ts ├── constants │ └── abi.ts ├── helpers.ts ├── images │ ├── logo.svg │ ├── skyweaver-banner-large.png │ ├── skyweaver-banner.old.png │ └── skyweaver-banner.png ├── index.css ├── index.tsx ├── react-app-env.d.ts └── vite-env.d.ts ├── tsconfig.json └── vite.config.ts /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build dapp 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | name: Build and Push 12 | steps: 13 | - name: git-checkout 14 | uses: actions/checkout@v3 15 | 16 | - name: Setup PNPM 17 | uses: pnpm/action-setup@v2 18 | with: 19 | version: 8 20 | run_install: true 21 | 22 | - name: Build 23 | run: pnpm dist 24 | 25 | - name: Push 26 | uses: s0/git-publish-subdir-action@develop 27 | env: 28 | REPO: self 29 | BRANCH: build 30 | FOLDER: dist 31 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 32 | MESSAGE: 'Build: ({sha}) {msg}' 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | 3 | #dependencies 4 | node_modules/ 5 | 6 | # production 7 | dist/ 8 | 9 | # misc 10 | .DS_Store 11 | .vscode 12 | .idea/ 13 | *.iml 14 | npm-debug.log* 15 | yarn-debug.log* 16 | yarn-error.log* 17 | lerna-debug.log* 18 | 19 | .env.local 20 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 2, 3 | "useTabs": false, 4 | "semi": false, 5 | "singleQuote": true, 6 | "trailingComma": "none", 7 | "arrowParens": "avoid", 8 | "printWidth": 130 9 | } 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Demo Dapp 2 | ========= 3 | 4 | Dapp example on how to use Sequence Wallet. Covers how to connect, sign messages and send transctions. 5 | 6 | Try this dapp at: [https://0xsequence.github.io/demo-dapp](https://0xsequence.github.io/demo-dapp) 7 | 8 | For complete documentation on Sequence, please see: [https://docs.sequence.build](https://docs.sequence.build) 9 | 10 | ## Usage 11 | 12 | 1. pnpm install 13 | 2. pnpm start 14 | 3. Open browser to http://localhost:4000 to access the demo dapp 15 | 4. Open browser inspector to see responses from the remote Sequence Wallet 16 | 17 | ## Development 18 | 19 | See https://github.com/0xsequence/demo-dapp/blob/master/src/App.tsx for the source 20 | usage for a variety of functions. Be sure to open your browser's dev inspector to see output. 21 | Think of these functions as a "cookbook" for how you can perform these functions in your dapps. 22 | 23 | Also note, sequence.js is built on top of ethers.js, and is API-compatible. 24 | 25 | ## Screenshots 26 | 27 | **Opening wallet from dapp:** 28 | 29 | ![Open Sequence Wallet From Dapp](./screenshots/screen-open.png) 30 | 31 | 32 | **Send transaction from dapp:** 33 | 34 | Sequence Wallet is an Ethereum wallet supporting Ethereum mainnet, Polygon and more. Sequence will work 35 | with any blockchain which is EVM compatible and supports Ethereum's node JSON-RPC interface. 36 | 37 | Here you can see in this screenshot the call to "Send DAI" from demo-dapp 38 | (https://github.com/0xsequence/demo-dapp/blob/master/src/routes/HomeRoute.tsx#L420). This function demonstrates 39 | how you can transfer an ERC-20 token like DAI on any Ethereum network. 40 | 41 | Notice how you can pay gas fees for a transaction in either MATIC token or USDC for price of $0.01. 42 | 43 | ![Transfer ERC-20 token on Polygon](./screenshots/screen-txn.png) 44 | 45 | 46 | 47 | ## LICENSE 48 | 49 | Apache 2.0 or MIT (your choice) 50 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | Sequence | Demo Dapp 28 | 29 | 30 | 31 | 32 |
33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "demo-dapp", 3 | "description": "Ethereum Demo Dapp built on Sequence stack", 4 | "version": "0.1.0", 5 | "private": true, 6 | "homepage": "demo-dapp", 7 | "scripts": { 8 | "dev": "BROWSER=none pnpm start", 9 | "start": "vite", 10 | "build": "BUILD_PATH='./dist' tsc && vite build", 11 | "typecheck": "tsc --noEmit", 12 | "serve": "vite preview", 13 | "dist": "pnpm build", 14 | "link-sequence": "pnpm run clear:vite:cache && ../sequence.js/scripts/pnpm-link.sh link", 15 | "unlink-sequence": "pnpm run clear:vite:cache && ../sequence.js/scripts/pnpm-link.sh unlink", 16 | "clear:vite:cache": "rm -rf node_modules/.vite/" 17 | }, 18 | "dependencies": { 19 | "0xsequence": "2.2.3", 20 | "@0xsequence/abi": "2.2.13", 21 | "@0xsequence/design-system": "^1.8.1", 22 | "@0xsequence/ethauth": "^1.0.0", 23 | "@0xsequence/network": "2.2.13", 24 | "@0xsequence/provider": "2.2.13", 25 | "@0xsequence/utils": "2.2.13", 26 | "@types/node": "^20.11.30", 27 | "@types/react": "^18.3.7", 28 | "@types/react-dom": "^18.3.0", 29 | "@vanilla-extract/css": "^1.14.1", 30 | "ethers": "^6.13.4", 31 | "framer-motion": "^9.0.1", 32 | "react": "^18.3.1", 33 | "react-dom": "^18.3.1", 34 | "typescript": "^4.5.5" 35 | }, 36 | "devDependencies": { 37 | "@vanilla-extract/vite-plugin": "^4.0.6", 38 | "@vitejs/plugin-react": "^4.2.1", 39 | "vite": "^5.2.6", 40 | "vite-plugin-svgr": "^4.2.0", 41 | "vite-tsconfig-paths": "^4.3.2" 42 | }, 43 | "eslintConfig": { 44 | "extends": [ 45 | "react-app" 46 | ] 47 | }, 48 | "browserslist": { 49 | "production": [ 50 | ">0.2%", 51 | "not dead", 52 | "not op_mini all" 53 | ], 54 | "development": [ 55 | "last 1 chrome version", 56 | "last 1 firefox version", 57 | "last 1 safari version" 58 | ] 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xsequence/demo-dapp/56dc8e29ee043d2722a5e6c92212f15548d668d4/public/favicon.ico -------------------------------------------------------------------------------- /public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xsequence/demo-dapp/56dc8e29ee043d2722a5e6c92212f15548d668d4/public/logo192.png -------------------------------------------------------------------------------- /public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xsequence/demo-dapp/56dc8e29ee043d2722a5e6c92212f15548d668d4/public/logo512.png -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "Sequence Demo Dapp", 3 | "name": "Ethereum Demo Dapp built on Sequence stack", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /screenshots/screen-open.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xsequence/demo-dapp/56dc8e29ee043d2722a5e6c92212f15548d668d4/screenshots/screen-open.png -------------------------------------------------------------------------------- /screenshots/screen-txn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xsequence/demo-dapp/56dc8e29ee043d2722a5e6c92212f15548d668d4/screenshots/screen-txn.png -------------------------------------------------------------------------------- /src/App.tsx: -------------------------------------------------------------------------------- 1 | import { AnimatePresence } from 'framer-motion' 2 | import React, { useState, useEffect, useMemo, SetStateAction } from 'react' 3 | import { ethers } from 'ethers' 4 | import { sequence } from '0xsequence' 5 | import { walletContracts } from '@0xsequence/abi' 6 | import { 7 | Box, 8 | Image, 9 | Text, 10 | Button, 11 | ExternalLinkIcon, 12 | Divider, 13 | Card, 14 | TransactionIcon, 15 | Select, 16 | TokenImage, 17 | TextInput, 18 | Modal 19 | } from '@0xsequence/design-system' 20 | import { ETHAuth } from '@0xsequence/ethauth' 21 | import { configureLogger } from '@0xsequence/utils' 22 | import { ConnectOptions, OpenWalletIntent, Settings } from '@0xsequence/provider' 23 | import { ChainId, NetworkType } from '@0xsequence/network' 24 | 25 | import { ERC_20_ABI } from './constants/abi' 26 | import { Console } from './components/Console' 27 | import { Group } from './components/Group' 28 | import { getDefaultChainId, toHexString } from './helpers' 29 | import logoUrl from './images/logo.svg' 30 | import skyweaverBannerUrl from './images/skyweaver-banner.png' 31 | import skyweaverBannerLargeUrl from './images/skyweaver-banner-large.png' 32 | 33 | configureLogger({ logLevel: 'DEBUG' }) 34 | 35 | interface Environment { 36 | name: string 37 | walletUrl: string 38 | projectAccessKey: string 39 | } 40 | 41 | const environments: Environment[] = [ 42 | { 43 | name: 'production', 44 | walletUrl: 'https://sequence.app', 45 | projectAccessKey: 'AQAAAAAAAAbvrgpWEC2Aefg5qYStQmwjBpA' 46 | }, 47 | { 48 | name: 'development', 49 | walletUrl: 'https://dev.sequence.app', 50 | //projectAccessKey: 'AQAAAAAAAAVBNfoB30kz7Ph4I_Qs5mkYuDc', 51 | projectAccessKey: 'AQAAAAAAAAVCXiQ9f_57R44MjorZ4SmGdhA' 52 | }, 53 | { 54 | name: 'local', 55 | walletUrl: 'http://localhost:3333', 56 | projectAccessKey: 'AQAAAAAAAAVCXiQ9f_57R44MjorZ4SmGdhA' 57 | }, 58 | { 59 | name: 'custom', 60 | walletUrl: '', 61 | projectAccessKey: '' 62 | } 63 | ] 64 | 65 | const DEFAULT_API_URL = 'https://api.sequence.app' 66 | 67 | // Specify your desired default chain id. NOTE: you can communicate to multiple 68 | // chains at the same time without even having to switch the network, but a default 69 | // chain is required. 70 | const defaultChainId = getDefaultChainId() || ChainId.MAINNET 71 | // const defaultChainId = ChainId.POLYGON 72 | // const defaultChainId = ChainId.GOERLI 73 | // const defaultChainId = ChainId.ARBITRUM 74 | // const defaultChainId = ChainId.AVALANCHE 75 | // etc.. see the full list here: https://docs.sequence.xyz/multi-chain-support 76 | 77 | // For Sequence core dev team -- app developers can ignore 78 | // a custom wallet app url can specified in the query string 79 | const urlParams = new URLSearchParams(window.location.search) 80 | 81 | const env = urlParams.get('env') ?? 'production' 82 | const envConfig = environments.find(x => x.name === env) 83 | const walletAppURL = urlParams.get('walletAppURL') ?? envConfig.walletUrl 84 | const projectAccessKey = urlParams.get('projectAccessKey') ?? envConfig.projectAccessKey 85 | const showProhibitedActions = urlParams.has('showProhibitedActions') 86 | 87 | const isCustom = walletAppURL !== envConfig.walletUrl || projectAccessKey !== envConfig.projectAccessKey 88 | 89 | if (walletAppURL && walletAppURL.length > 0) { 90 | // Wallet can point to a custom wallet app url 91 | // NOTICE: this is not needed, unless testing an alpha version of the wallet 92 | sequence.initWallet(projectAccessKey, { defaultNetwork: defaultChainId, transports: { walletAppURL } }) 93 | } else { 94 | // Init the sequence wallet library at the top-level of your project with 95 | // your designed default chain id 96 | sequence.initWallet(projectAccessKey, { defaultNetwork: defaultChainId, transports: { walletAppURL } }) 97 | } 98 | 99 | // App component 100 | const App = () => { 101 | const [consoleMsg, setConsoleMsg] = useState(null) 102 | const [email, setEmail] = useState(null) 103 | const [consoleLoading, setConsoleLoading] = useState(false) 104 | const [isWalletConnected, setIsWalletConnected] = useState(false) 105 | 106 | const wallet = sequence.getWallet().getProvider() 107 | 108 | const [showChainId, setShowChainId] = useState(wallet.getChainId()) 109 | const [isOpen, toggleModal] = useState(false) 110 | const [warning, setWarning] = useState(false) 111 | 112 | useMemo(() => { 113 | wallet.on('chainChanged', (chainId: string) => { 114 | setShowChainId(Number(BigInt(chainId))) 115 | }) 116 | }, []) 117 | 118 | useEffect(() => { 119 | setIsWalletConnected(wallet.isConnected()) 120 | }, [wallet]) 121 | 122 | useEffect(() => { 123 | consoleWelcomeMessage() 124 | // eslint-disable-next-line 125 | }, [isWalletConnected]) 126 | 127 | useEffect(() => { 128 | // Wallet events 129 | wallet.client.onOpen(() => { 130 | console.log('wallet window opened') 131 | }) 132 | 133 | wallet.client.onClose(() => { 134 | console.log('wallet window closed') 135 | }) 136 | }, [wallet]) 137 | 138 | const defaultConnectOptions: ConnectOptions = { 139 | app: 'Demo Dapp', 140 | askForEmail: true 141 | // keepWalletOpened: true, 142 | } 143 | 144 | // Methods 145 | const connect = async (connectOptions: ConnectOptions = { app: 'Demo dapp' }) => { 146 | if (isWalletConnected) { 147 | resetConsole() 148 | appendConsoleLine('Wallet already connected!') 149 | setConsoleLoading(false) 150 | return 151 | } 152 | 153 | connectOptions = { 154 | ...defaultConnectOptions, 155 | ...connectOptions, 156 | settings: { 157 | ...defaultConnectOptions.settings, 158 | ...connectOptions.settings 159 | } 160 | } 161 | 162 | try { 163 | resetConsole() 164 | appendConsoleLine('Connecting') 165 | const wallet = sequence.getWallet() 166 | 167 | const connectDetails = await wallet.connect(connectOptions) 168 | 169 | // Example of how to verify using ETHAuth via Sequence API 170 | if (connectOptions.authorize && connectDetails.connected) { 171 | let apiUrl = urlParams.get('apiUrl') 172 | 173 | if (!apiUrl || apiUrl.length === 0) { 174 | apiUrl = DEFAULT_API_URL 175 | } 176 | 177 | const api = new sequence.api.SequenceAPIClient(apiUrl) 178 | // or just 179 | // const api = new sequence.api.SequenceAPIClient('https://api.sequence.app') 180 | 181 | const { isValid } = await api.isValidETHAuthProof({ 182 | chainId: connectDetails.chainId, 183 | walletAddress: connectDetails.session.accountAddress, 184 | ethAuthProofString: connectDetails.proof!.proofString 185 | }) 186 | 187 | appendConsoleLine(`isValid (API)?: ${isValid}`) 188 | } 189 | 190 | // Example of how to verify using ETHAuth directl on the client 191 | if (connectOptions.authorize) { 192 | const ethAuth = new ETHAuth() 193 | 194 | if (connectDetails.proof) { 195 | const decodedProof = await ethAuth.decodeProof(connectDetails.proof.proofString, true) 196 | 197 | const isValid = await wallet.utils.isValidTypedDataSignature( 198 | wallet.getAddress(), 199 | connectDetails.proof.typedData, 200 | decodedProof.signature, 201 | Number(BigInt(connectDetails.chainId)) 202 | ) 203 | 204 | appendConsoleLine(`connected using chainId: ${BigInt(connectDetails.chainId).toString()}`) 205 | appendConsoleLine(`isValid (client)?: ${isValid}`) 206 | } 207 | } 208 | 209 | setConsoleLoading(false) 210 | if (connectDetails.connected) { 211 | appendConsoleLine('Wallet connected!') 212 | appendConsoleLine(`shared email: ${connectDetails.email}`) 213 | setIsWalletConnected(true) 214 | } else { 215 | appendConsoleLine('Failed to connect wallet - ' + connectDetails.error) 216 | } 217 | } catch (e) { 218 | console.error(e) 219 | consoleErrorMessage() 220 | } 221 | } 222 | 223 | const disconnect = () => { 224 | const wallet = sequence.getWallet() 225 | wallet.disconnect() 226 | consoleWelcomeMessage() 227 | setIsWalletConnected(false) 228 | } 229 | 230 | const openWallet = () => { 231 | const wallet = sequence.getWallet() 232 | wallet.openWallet() 233 | } 234 | 235 | const openWalletWithSettings = () => { 236 | const wallet = sequence.getWallet() 237 | 238 | const settings: Settings = { 239 | theme: 'light', 240 | includedPaymentProviders: ['moonpay', 'ramp'], 241 | defaultFundingCurrency: 'eth', 242 | defaultPurchaseAmount: 400, 243 | lockFundingCurrencyToDefault: false 244 | } 245 | 246 | const intent: OpenWalletIntent = { 247 | type: 'openWithOptions', 248 | options: { 249 | app: 'Demo Dapp', 250 | settings 251 | } 252 | } 253 | 254 | const path = 'wallet/add-funds' 255 | wallet.openWallet(path, intent) 256 | } 257 | 258 | const closeWallet = () => { 259 | const wallet = sequence.getWallet() 260 | wallet.closeWallet() 261 | } 262 | 263 | const isConnected = async () => { 264 | resetConsole() 265 | const wallet = sequence.getWallet() 266 | appendConsoleLine(`isConnected?: ${wallet.isConnected()}`) 267 | setConsoleLoading(false) 268 | } 269 | 270 | const isOpened = async () => { 271 | resetConsole() 272 | const wallet = sequence.getWallet() 273 | appendConsoleLine(`isOpened?: ${wallet.isOpened()}`) 274 | setConsoleLoading(false) 275 | } 276 | 277 | const getChainID = async () => { 278 | try { 279 | resetConsole() 280 | 281 | const topChainId = wallet.getChainId() 282 | appendConsoleLine(`top chainId: ${topChainId}`) 283 | 284 | const provider = wallet.getProvider() 285 | const providerChainId = provider!.getChainId() 286 | appendConsoleLine(`provider.getChainId(): ${providerChainId}`) 287 | 288 | const signer = wallet.getSigner() 289 | const signerChainId = await signer.getChainId() 290 | appendConsoleLine(`signer.getChainId(): ${signerChainId}`) 291 | 292 | setConsoleLoading(false) 293 | } catch (e) { 294 | console.error(e) 295 | consoleErrorMessage() 296 | } 297 | } 298 | 299 | const getAccounts = async () => { 300 | try { 301 | resetConsole() 302 | 303 | const wallet = sequence.getWallet() 304 | const address = wallet.getAddress() 305 | appendConsoleLine(`getAddress(): ${address}`) 306 | 307 | const provider = wallet.getProvider() 308 | const accountList = provider.listAccounts() 309 | appendConsoleLine(`accounts: ${JSON.stringify(accountList)}`) 310 | 311 | setConsoleLoading(false) 312 | } catch (e) { 313 | console.error(e) 314 | consoleErrorMessage() 315 | } 316 | } 317 | 318 | const getBalance = async () => { 319 | try { 320 | resetConsole() 321 | 322 | const wallet = sequence.getWallet() 323 | 324 | const provider = wallet.getProvider() 325 | const account = wallet.getAddress() 326 | const balanceChk1 = await provider!.getBalance(account) 327 | appendConsoleLine(`balance check 1: ${balanceChk1.toString()}`) 328 | 329 | const signer = wallet.getSigner() 330 | const balanceChk2 = await signer.getBalance() 331 | appendConsoleLine(`balance check 2: ${balanceChk2.toString()}`) 332 | 333 | setConsoleLoading(false) 334 | } catch (e) { 335 | console.error(e) 336 | consoleErrorMessage() 337 | } 338 | } 339 | 340 | const getNetworks = async () => { 341 | try { 342 | resetConsole() 343 | 344 | const wallet = sequence.getWallet() 345 | const networks = await wallet.getNetworks() 346 | 347 | appendConsoleLine(`networks: ${JSON.stringify(networks, null, 2)}`) 348 | setConsoleLoading(false) 349 | } catch (e) { 350 | console.error(e) 351 | consoleErrorMessage() 352 | } 353 | } 354 | 355 | const signMessageString = async () => { 356 | try { 357 | resetConsole() 358 | 359 | const wallet = sequence.getWallet() 360 | 361 | appendConsoleLine('signing message...') 362 | const signer = wallet.getSigner() 363 | 364 | const message = `1915 Robert Frost 365 | The Road Not Taken 366 | 367 | Two roads diverged in a yellow wood, 368 | And sorry I could not travel both 369 | And be one traveler, long I stood 370 | And looked down one as far as I could 371 | To where it bent in the undergrowth 372 | 373 | Then took the other, as just as fair, 374 | And having perhaps the better claim, 375 | Because it was grassy and wanted wear 376 | Though as for that the passing there 377 | Had worn them really about the same, 378 | 379 | And both that morning equally lay 380 | In leaves no step had trodden black. 381 | Oh, I kept the first for another day! 382 | Yet knowing how way leads on to way, 383 | I doubted if I should ever come back. 384 | 385 | I shall be telling this with a sigh 386 | Somewhere ages and ages hence: 387 | Two roads diverged in a wood, and I— 388 | I took the one less traveled by, 389 | And that has made all the difference. 390 | 391 | \u2601 \u2600 \u2602` 392 | 393 | // sign 394 | const sig = await signer.signMessage(message) 395 | appendConsoleLine(`signature: ${sig}`) 396 | 397 | const isValid = await wallet.utils.isValidMessageSignature(wallet.getAddress(), message, sig, await signer.getChainId()) 398 | appendConsoleLine(`isValid?: ${isValid}`) 399 | if (!isValid) throw new Error('sig invalid') 400 | 401 | setConsoleLoading(false) 402 | } catch (e) { 403 | console.error(e) 404 | consoleErrorMessage() 405 | } 406 | } 407 | 408 | const signMessageHex = async () => { 409 | try { 410 | resetConsole() 411 | 412 | const wallet = sequence.getWallet() 413 | 414 | appendConsoleLine('signing message...') 415 | const signer = wallet.getSigner() 416 | 417 | // Message in hex 418 | const message = ethers.hexlify(ethers.toUtf8Bytes('Hello, world!')) 419 | 420 | // sign 421 | const sig = await signer.signMessage(message) 422 | appendConsoleLine(`signature: ${sig}`) 423 | 424 | const isValid = await wallet.utils.isValidMessageSignature(wallet.getAddress(), message, sig, await signer.getChainId()) 425 | appendConsoleLine(`isValid?: ${isValid}`) 426 | if (!isValid) throw new Error('sig invalid') 427 | 428 | setConsoleLoading(false) 429 | } catch (e) { 430 | console.error(e) 431 | consoleErrorMessage() 432 | } 433 | } 434 | 435 | const signMessageBytes = async () => { 436 | try { 437 | resetConsole() 438 | 439 | const wallet = sequence.getWallet() 440 | 441 | appendConsoleLine('signing message...') 442 | const signer = wallet.getSigner() 443 | 444 | // Message in hex 445 | const message = ethers.toUtf8Bytes('Hello, world!') 446 | 447 | // sign 448 | const sig = await signer.signMessage(message) 449 | appendConsoleLine(`signature: ${sig}`) 450 | 451 | const isValid = await wallet.utils.isValidMessageSignature(wallet.getAddress(), message, sig, await signer.getChainId()) 452 | appendConsoleLine(`isValid?: ${isValid}`) 453 | if (!isValid) throw new Error('sig invalid') 454 | 455 | setConsoleLoading(false) 456 | } catch (e) { 457 | console.error(e) 458 | consoleErrorMessage() 459 | } 460 | } 461 | 462 | const signTypedData = async () => { 463 | try { 464 | resetConsole() 465 | const wallet = sequence.getWallet() 466 | 467 | appendConsoleLine('signing typedData...') 468 | 469 | const typedData: sequence.utils.TypedData = { 470 | types: { 471 | Person: [ 472 | { name: 'name', type: 'string' }, 473 | { name: 'wallet', type: 'address' } 474 | ], 475 | Mail: [ 476 | { name: 'from', type: 'Person' }, 477 | { name: 'to', type: 'Person' }, 478 | { name: 'cc', type: 'Person[]' }, 479 | { name: 'contents', type: 'string' }, 480 | { name: 'attachements', type: 'string[]' } 481 | ] 482 | }, 483 | primaryType: 'Mail', 484 | domain: { 485 | name: 'Ether Mail', 486 | version: '1', 487 | chainId: 1, 488 | verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC' 489 | }, 490 | message: { 491 | from: { 492 | name: 'Cow', 493 | wallet: '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826' 494 | }, 495 | to: { 496 | name: 'Bob', 497 | wallet: '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB' 498 | }, 499 | cc: [ 500 | { name: 'Dev Team', wallet: '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB' }, 501 | { name: 'Accounting', wallet: '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB' } 502 | ], 503 | contents: 'Hello, Bob!', 504 | attachements: ['cat.png', 'dog.png'] 505 | } 506 | } 507 | 508 | const signer = wallet.getSigner() 509 | 510 | const sig = await signer.signTypedData(typedData.domain, typedData.types, typedData.message) 511 | appendConsoleLine(`signature: ${sig}`) 512 | 513 | // validate 514 | const isValid = await wallet.utils.isValidTypedDataSignature(wallet.getAddress(), typedData, sig, await signer.getChainId()) 515 | appendConsoleLine(`isValid?: ${isValid}`) 516 | 517 | setConsoleLoading(false) 518 | } catch (e) { 519 | console.error(e) 520 | consoleErrorMessage() 521 | } 522 | } 523 | 524 | const estimateUnwrapGas = async () => { 525 | try { 526 | resetConsole() 527 | 528 | const wallet = sequence.getWallet() 529 | 530 | const wmaticContractAddress = '0x0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270' 531 | const wmaticInterface = new ethers.Interface(['function withdraw(uint256 amount)']) 532 | 533 | const tx: sequence.transactions.Transaction = { 534 | to: wmaticContractAddress, 535 | data: wmaticInterface.encodeFunctionData('withdraw', ['1000000000000000000']) 536 | } 537 | 538 | const provider = wallet.getProvider() 539 | const estimate = await provider.estimateGas(tx) 540 | 541 | appendConsoleLine(`estimated gas needed for wmatic withdrawal : ${estimate.toString()}`) 542 | 543 | setConsoleLoading(false) 544 | } catch (e) { 545 | console.error(e) 546 | consoleErrorMessage() 547 | } 548 | } 549 | 550 | const sendETH = async (signer?: sequence.provider.SequenceSigner) => { 551 | try { 552 | resetConsole() 553 | const wallet = sequence.getWallet() 554 | 555 | signer = signer || wallet.getSigner() 556 | 557 | appendConsoleLine(`Transfer txn on ${signer.getChainId()} chainId`) 558 | 559 | // NOTE: on mainnet, the balance will be of ETH value 560 | // and on matic, the balance will be of MATIC value 561 | 562 | // Sending the funds to the wallet itself 563 | // so we don't lose any funds ;-) 564 | // (of course, you can send anywhere) 565 | const toAddress = await signer.getAddress() 566 | 567 | const tx1: sequence.transactions.Transaction = { 568 | delegateCall: false, 569 | revertOnError: false, 570 | gasLimit: '0x55555', 571 | to: toAddress, 572 | value: ethers.parseEther('1.234'), 573 | data: '0x' 574 | } 575 | 576 | const tx2: sequence.transactions.Transaction = { 577 | delegateCall: false, 578 | revertOnError: false, 579 | gasLimit: '0x55555', 580 | to: toAddress, 581 | value: ethers.parseEther('0.4242'), 582 | data: '0x' 583 | } 584 | 585 | const provider = signer.provider 586 | 587 | const balance1 = await provider.getBalance(toAddress) 588 | appendConsoleLine(`balance of ${toAddress}, before: ${balance1}`) 589 | 590 | const txnResp = await signer.sendTransaction([tx1, tx2]) 591 | appendConsoleLine(`txnResponse: ${JSON.stringify(txnResp)}`) 592 | 593 | const balance2 = await provider.getBalance(toAddress) 594 | appendConsoleLine(`balance of ${toAddress}, after: ${balance2}`) 595 | 596 | setConsoleLoading(false) 597 | } catch (e) { 598 | console.error(e) 599 | consoleErrorMessage() 600 | } 601 | } 602 | 603 | const sendSepoliaUSDC = async (signer?: sequence.provider.SequenceSigner) => { 604 | try { 605 | resetConsole() 606 | 607 | const wallet = sequence.getWallet() 608 | 609 | signer = signer || wallet.getSigner() // select DefaultChain signer by default 610 | 611 | // Sending the funds to the wallet itself 612 | // so we don't lose any funds ;-) 613 | // (of course, you can send anywhere) 614 | const toAddress = await signer.getAddress() 615 | 616 | const amount = ethers.parseUnits('1', 1) 617 | 618 | // (USDC address on Sepolia) 619 | const usdcAddress = '0x07865c6e87b9f70255377e024ace6630c1eaa37f' 620 | 621 | const tx: sequence.transactions.Transaction = { 622 | delegateCall: false, 623 | revertOnError: false, 624 | gasLimit: '0x55555', 625 | to: usdcAddress, 626 | value: 0, 627 | data: new ethers.Interface(ERC_20_ABI).encodeFunctionData('transfer', [toAddress, toHexString(amount)]) 628 | } 629 | 630 | const txnResp = await signer.sendTransaction([tx], { chainId: ChainId.SEPOLIA }) 631 | appendConsoleLine(`txnResponse: ${JSON.stringify(txnResp)}`) 632 | 633 | setConsoleLoading(false) 634 | } catch (e) { 635 | console.error(e) 636 | consoleErrorMessage() 637 | } 638 | } 639 | 640 | const sendDAI = async (signer?: sequence.provider.SequenceSigner) => { 641 | try { 642 | resetConsole() 643 | 644 | const wallet = sequence.getWallet() 645 | 646 | signer = signer || wallet.getSigner() // select DefaultChain signer by default 647 | 648 | // Sending the funds to the wallet itself 649 | // so we don't lose any funds ;-) 650 | // (of course, you can send anywhere) 651 | const toAddress = await signer.getAddress() 652 | 653 | const amount = ethers.parseUnits('0.05', 18) 654 | const daiContractAddress = '0x8f3Cf7ad23Cd3CaDbD9735AFf958023239c6A063' // (DAI address on Polygon) 655 | 656 | const tx: sequence.transactions.Transaction = { 657 | delegateCall: false, 658 | revertOnError: false, 659 | gasLimit: '0x55555', 660 | to: daiContractAddress, 661 | value: 0, 662 | data: new ethers.Interface(ERC_20_ABI).encodeFunctionData('transfer', [toAddress, toHexString(amount)]) 663 | } 664 | 665 | const txnResp = await signer.sendTransaction([tx]) 666 | appendConsoleLine(`txnResponse: ${JSON.stringify(txnResp)}`) 667 | 668 | setConsoleLoading(false) 669 | } catch (e) { 670 | console.error(e) 671 | consoleErrorMessage() 672 | } 673 | } 674 | 675 | const sendETHSidechain = async () => { 676 | try { 677 | const wallet = sequence.getWallet() 678 | 679 | // Send either to Arbitrum or Optimism 680 | // just pick one that is not the current chainId 681 | const pick = wallet.getChainId() === ChainId.ARBITRUM ? ChainId.OPTIMISM : ChainId.ARBITRUM 682 | sendETH(wallet.getSigner(pick)) 683 | } catch (e) { 684 | console.error(e) 685 | consoleErrorMessage() 686 | } 687 | } 688 | 689 | const send1155Tokens = async () => { 690 | try { 691 | resetConsole() 692 | appendConsoleLine('TODO') 693 | setConsoleLoading(false) 694 | } catch (e) { 695 | console.error(e) 696 | consoleErrorMessage() 697 | } 698 | } 699 | 700 | const contractExample = async (signer?: sequence.provider.SequenceSigner) => { 701 | try { 702 | resetConsole() 703 | 704 | const wallet = sequence.getWallet() 705 | 706 | signer = signer || wallet.getSigner() 707 | 708 | const abi = [ 709 | 'function balanceOf(address owner) view returns (uint256)', 710 | 'function decimals() view returns (uint8)', 711 | 'function symbol() view returns (string)', 712 | 'function transfer(address to, uint amount) returns (bool)', 713 | 'event Transfer(address indexed from, address indexed to, uint amount)' 714 | ] 715 | 716 | // USD Coin (PoS) on Polygon 717 | const address = '0x2791bca1f2de4661ed88a30c99a7a9449aa84174' 718 | 719 | const usdc = new ethers.Contract(address, abi) 720 | 721 | const usdSymbol = await usdc.symbol() 722 | appendConsoleLine(`Token symbol: ${usdSymbol}`) 723 | 724 | const balance = await usdc.balanceOf(await signer.getAddress()) 725 | appendConsoleLine(`Token Balance: ${balance.toString()}`) 726 | 727 | setConsoleLoading(false) 728 | } catch (e) { 729 | console.error(e) 730 | consoleErrorMessage() 731 | } 732 | } 733 | 734 | const fetchTokenBalances = async () => { 735 | try { 736 | resetConsole() 737 | 738 | const wallet = sequence.getWallet() 739 | 740 | const signer = wallet.getSigner() 741 | const accountAddress = await signer.getAddress() 742 | const networks = await wallet.getNetworks() 743 | const network = networks.find(network => network.chainId === ChainId.POLYGON) 744 | 745 | if (!network) { 746 | throw new Error(`Could not find Polygon network in networks list`) 747 | } 748 | 749 | const indexer = new sequence.indexer.SequenceIndexer(network.indexerUrl) 750 | 751 | const tokenBalances = await indexer.getTokenBalances({ 752 | accountAddress: accountAddress, 753 | includeMetadata: true 754 | }) 755 | 756 | appendConsoleLine(`tokens in your account: ${JSON.stringify(tokenBalances)}`) 757 | 758 | // NOTE: you can put any NFT/collectible address in the `contractAddress` field and it will return all of the balances + metadata. 759 | // We use the Skyweaver production contract address here for demo purposes, but try another one :) 760 | const skyweaverCollectibles = await indexer.getTokenBalances({ 761 | accountAddress: accountAddress, 762 | includeMetadata: true, 763 | contractAddress: '0x631998e91476DA5B870D741192fc5Cbc55F5a52E' 764 | }) 765 | appendConsoleLine(`skyweaver collectibles in your account: ${JSON.stringify(skyweaverCollectibles)}`) 766 | 767 | setConsoleLoading(false) 768 | } catch (e) { 769 | console.error(e) 770 | consoleErrorMessage() 771 | } 772 | } 773 | 774 | const updateImplementation = async (signer?: sequence.provider.SequenceSigner) => { 775 | try { 776 | resetConsole() 777 | 778 | const wallet = sequence.getWallet() 779 | 780 | signer = signer || wallet.getSigner() // select DefaultChain signer by default 781 | 782 | const transaction: sequence.transactions.Transaction = { 783 | to: wallet.getAddress(), 784 | data: new ethers.Interface(walletContracts.mainModule.abi).encodeFunctionData('updateImplementation', [ 785 | ethers.ZeroAddress 786 | ]) 787 | } 788 | 789 | const response = await signer.sendTransaction(transaction) 790 | appendConsoleLine(`response: ${JSON.stringify(response)}`) 791 | setConsoleLoading(false) 792 | } catch (e) { 793 | console.error(e) 794 | consoleErrorMessage() 795 | } 796 | } 797 | 798 | const updateImageHash = async (signer?: sequence.provider.SequenceSigner) => { 799 | try { 800 | resetConsole() 801 | 802 | const wallet = sequence.getWallet() 803 | 804 | signer = signer || wallet.getSigner() // select DefaultChain signer by default 805 | 806 | const transaction: sequence.transactions.Transaction = { 807 | to: wallet.getAddress(), 808 | data: new ethers.Interface(walletContracts.mainModuleUpgradable.abi).encodeFunctionData('updateImageHash', [ 809 | ethers.ZeroHash 810 | ]) 811 | } 812 | 813 | const response = await signer.sendTransaction(transaction) 814 | appendConsoleLine(`response: ${JSON.stringify(response)}`) 815 | setConsoleLoading(false) 816 | } catch (e) { 817 | console.error(e) 818 | consoleErrorMessage() 819 | } 820 | } 821 | 822 | const delegateCall = async (signer?: sequence.provider.SequenceSigner) => { 823 | try { 824 | resetConsole() 825 | 826 | const wallet = sequence.getWallet() 827 | 828 | signer = signer || wallet.getSigner() // select DefaultChain signer by default 829 | 830 | const transaction: sequence.transactions.Transaction = { 831 | to: wallet.getAddress(), 832 | delegateCall: true 833 | } 834 | 835 | const response = await signer.sendTransaction(transaction) 836 | appendConsoleLine(`response: ${JSON.stringify(response)}`) 837 | setConsoleLoading(false) 838 | } catch (e) { 839 | console.error(e) 840 | consoleErrorMessage() 841 | } 842 | } 843 | 844 | const addHook = async (signer?: sequence.provider.SequenceSigner) => { 845 | try { 846 | resetConsole() 847 | 848 | const wallet = sequence.getWallet() 849 | 850 | signer = signer || wallet.getSigner() // select DefaultChain signer by default 851 | 852 | const transaction: sequence.transactions.Transaction = { 853 | to: wallet.getAddress(), 854 | data: new ethers.Interface(['function addHook(bytes4 _signature, address _implementation)']).encodeFunctionData( 855 | 'addHook', 856 | ['0x01234567', ethers.ZeroAddress] 857 | ) 858 | } 859 | 860 | const response = await signer.sendTransaction(transaction) 861 | appendConsoleLine(`response: ${JSON.stringify(response)}`) 862 | setConsoleLoading(false) 863 | } catch (e) { 864 | console.error(e) 865 | consoleErrorMessage() 866 | } 867 | } 868 | 869 | const setExtraImageHash = async (signer?: sequence.provider.SequenceSigner) => { 870 | try { 871 | resetConsole() 872 | 873 | const wallet = sequence.getWallet() 874 | 875 | signer = signer || wallet.getSigner() // select DefaultChain signer by default 876 | 877 | const transaction: sequence.transactions.Transaction = { 878 | to: wallet.getAddress(), 879 | data: new ethers.Interface(['function setExtraImageHash(bytes32 _imageHash, uint256 _expiration)']).encodeFunctionData( 880 | 'setExtraImageHash', 881 | [ethers.ZeroHash, ethers.MaxUint256] 882 | ) 883 | } 884 | 885 | const response = await signer.sendTransaction(transaction) 886 | appendConsoleLine(`response: ${JSON.stringify(response)}`) 887 | setConsoleLoading(false) 888 | } catch (e) { 889 | console.error(e) 890 | consoleErrorMessage() 891 | } 892 | } 893 | 894 | const appendConsoleLine = (message: string, clear = false) => { 895 | console.log(message) 896 | 897 | if (clear) { 898 | return setConsoleMsg(message) 899 | } 900 | 901 | return setConsoleMsg(prevState => { 902 | return `${prevState}\n\n${message}` 903 | }) 904 | } 905 | 906 | const resetConsole = () => { 907 | setConsoleLoading(true) 908 | } 909 | 910 | const consoleWelcomeMessage = () => { 911 | setConsoleLoading(false) 912 | 913 | if (isWalletConnected) { 914 | setConsoleMsg('Status: Wallet is connected :)') 915 | } else { 916 | setConsoleMsg('Status: Wallet not connected. Please connect wallet first.') 917 | } 918 | } 919 | 920 | const consoleErrorMessage = () => { 921 | setConsoleLoading(false) 922 | setConsoleMsg('An error occurred') 923 | } 924 | 925 | // networks list, filtered and sorted 926 | const omitNetworks = [ 927 | ChainId.RINKEBY, 928 | ChainId.HARDHAT, 929 | ChainId.HARDHAT_2, 930 | ChainId.KOVAN, 931 | ChainId.ROPSTEN, 932 | ChainId.HOMEVERSE_TESTNET, 933 | ChainId.BASE_GOERLI 934 | ] 935 | 936 | const mainnets = Object.values(sequence.network.networks) 937 | .filter(network => network.type === NetworkType.MAINNET) 938 | .sort((a, b) => a.chainId - b.chainId) 939 | const testnets = Object.values(sequence.network.networks) 940 | .filter(network => network.type === NetworkType.TESTNET) 941 | .sort((a, b) => a.chainId - b.chainId) 942 | const networks = [...mainnets, ...testnets].filter(network => !network.deprecated && !omitNetworks.includes(network.chainId)) 943 | 944 | useEffect(() => { 945 | if (email && !isOpen) { 946 | console.log(email) 947 | connect({ 948 | app: 'Demo Dapp', 949 | authorize: true, 950 | settings: { 951 | // Specify signInWithEmail with an email address to allow user automatically sign in with the email option. 952 | signInWithEmail: email, 953 | theme: 'dark', 954 | bannerUrl: `${window.location.origin}${skyweaverBannerUrl}` 955 | } 956 | }) 957 | setEmail(null) 958 | } 959 | }, [email, isOpen]) 960 | 961 | const sanitizeEmail = (email: string) => { 962 | // Trim unnecessary spaces 963 | email = email.trim() 964 | 965 | // Check if the email matches the pattern of a typical email 966 | const emailRegex = /^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,6}$/ 967 | if (emailRegex.test(email)) { 968 | return true 969 | } 970 | 971 | return false 972 | } 973 | 974 | return ( 975 | 976 | 977 | 978 | logo 979 | 980 | 981 | 982 | 983 | 984 | Demo Dapp 985 | 986 | 987 | 988 | 989 | 990 | A dapp example on how to use the Sequence Wallet. This covers how to connect, sign messages and send transctions. 991 | 992 | 993 | 994 | 995 | 996 | 997 | Please open your browser dev inspector to view output of functions below. 998 | 999 | 1000 | 1001 | 1002 | 1003 | {!isCustom && ( 1004 | 1005 | wallet.setDefaultChainId(Number(value))} 1092 | value={String(showChainId)} 1093 | options={[ 1094 | ...Object.values(networks).map(network => ({ 1095 | label: ( 1096 | 1097 | 1098 | {network.title!} 1099 | 1100 | ), 1101 | value: String(network.chainId) 1102 | })) 1103 | ]} 1104 | /> 1105 | 1106 | 1107 | 1108 |