├── .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 | 
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 | 
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 |
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 |
1035 | )}
1036 |
1037 |
1038 |
1039 | Wallet URL
1040 |
1041 |
1042 |
1053 |
1054 | {walletAppURL}
1055 |
1056 |
1057 |
1058 |
1059 |
1060 |
1061 |
1062 |
1063 |
1064 | Project Access Key
1065 |
1066 |
1067 |
1078 |
1079 | {projectAccessKey}
1080 |
1081 |
1082 |
1083 |
1084 |
1085 |
1086 |
1087 |
1106 |
1107 |
1108 |
1164 |
1165 |
1166 | openWallet()} label="Open Wallet" />
1167 | openWalletWithSettings()}
1172 | label="Open Wallet with Settings"
1173 | />
1174 | closeWallet()} label="Close Wallet" />
1175 | isConnected()} label="Is Connected?" />
1176 | isOpened()} label="Is Opened?" />
1177 |
1178 |
1179 |
1180 | getChainID()} label="ChainID" />
1181 | getNetworks()} label="Networks" />
1182 | getAccounts()} label="Get Accounts" />
1183 | getBalance()} label="Get Balance" />
1184 |
1185 |
1186 |
1187 | signMessageString()}
1192 | label="Sign Message"
1193 | />
1194 | signMessageHex()}
1199 | label="Sign Message (Hex)"
1200 | />
1201 | signMessageBytes()}
1206 | label="Sign Message (Bytes)"
1207 | />
1208 | signTypedData()}
1213 | label="Sign TypedData"
1214 | />
1215 |
1216 |
1217 |
1218 | estimateUnwrapGas()}
1223 | label="Estimate Unwrap Gas"
1224 | />
1225 |
1226 |
1227 |
1228 | sendETH()} label="Send funds" />
1229 | sendETHSidechain()} label="Send on L2" />
1230 | sendDAI()} label="Send DAI" />
1231 | send1155Tokens()}
1237 | label="Send ERC-1155 Tokens"
1238 | />
1239 | sendSepoliaUSDC()}
1244 | label="Send USDC on Sepolia"
1245 | />
1246 |
1247 |
1248 |
1249 | contractExample()}
1254 | label="Read Symbol and Balance"
1255 | />
1256 | fetchTokenBalances()}
1261 | label="Fetch Token Balances"
1262 | />
1263 |
1264 |
1265 | {showProhibitedActions && (
1266 |
1267 | updateImplementation()}
1272 | label="Update Implementation"
1273 | />
1274 | updateImageHash()}
1279 | label="Update Image Hash"
1280 | />
1281 | delegateCall()}
1286 | label="Delegate Call"
1287 | />
1288 | addHook()} label="Add Hook" />
1289 | setExtraImageHash()}
1294 | label="Set Extra Image Hash"
1295 | />
1296 |
1297 | )}
1298 |
1299 |
1300 | {isOpen && (
1301 | toggleModal(false)} size={'sm'}>
1302 |
1303 |
1304 |
1305 |
1306 | Auto-email login, please specify the email address
1307 |
1308 |
1309 |
1310 | } }) => {
1312 | setEmail(ev.target.value)
1313 | }}
1314 | >
1315 |
1316 | {warning ? (
1317 |
1318 |
1319 | please input an email with correct format
1320 |
1321 |
1322 | ) : null}
1323 |
1324 | {
1328 | if (sanitizeEmail(email)) {
1329 | setWarning(false)
1330 | toggleModal(false)
1331 | } else {
1332 | setWarning(true)
1333 | }
1334 | }}
1335 | data-id="login"
1336 | />
1337 |
1338 |
1339 |
1340 |
1341 | )}
1342 |
1343 |
1344 |
1345 | )
1346 | }
1347 |
1348 | export default React.memo(App)
1349 |
--------------------------------------------------------------------------------
/src/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 dev
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/routes/HomeRoute.tsx for the source
20 | usage for a variety of functions. be sure t 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 | 
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 | 
44 |
45 |
46 |
47 | ## LICENSE
48 |
49 | Apache 2.0 or MIT (your choice)
50 |
--------------------------------------------------------------------------------
/src/components/Console/index.tsx:
--------------------------------------------------------------------------------
1 | import { Box, Collapsible } from '@0xsequence/design-system'
2 |
3 | import * as styles from './styles.css'
4 |
5 | export interface ConsoleProps {
6 | message: string | null
7 | loading: boolean
8 | }
9 |
10 | export const Console = ({ message, loading }: ConsoleProps) => {
11 | const getLoadingDots = () => {
12 | if (message) {
13 | return '\n...'
14 | }
15 | return '...'
16 | }
17 |
18 | return (
19 |
20 |
21 | {message}
22 | {loading && getLoadingDots()}
23 | {
24 |
31 | _
32 |
33 | }
34 |
35 |
36 | )
37 | }
38 |
--------------------------------------------------------------------------------
/src/components/Console/styles.css.ts:
--------------------------------------------------------------------------------
1 | import { style, keyframes } from '@vanilla-extract/css'
2 |
3 | export const blink = keyframes({
4 | '0%': { visibility: 'hidden' },
5 | '50%': { visibility: 'hidden' },
6 | '100%': {visibility: 'visible' }
7 | })
8 |
9 | export const cursor = style({
10 | animation: `${blink} 2s infinite`,
11 | })
--------------------------------------------------------------------------------
/src/components/Group/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Box, Text } from '@0xsequence/design-system'
3 |
4 | import * as styles from './styles.css'
5 |
6 | interface GroupProps {
7 | label?: JSX.Element | string
8 | children: React.ReactNode
9 | style?: any
10 | className?: string
11 | }
12 |
13 | export const Group = (props: GroupProps) => {
14 | const { label, children, style, className } = props
15 |
16 | return (
17 |
18 | {label && (
19 |
20 | {label}
21 |
22 | )}
23 |
24 | {React.Children.map(children, (child, i) => (
25 | {child}
26 | ))}
27 |
28 |
29 | )
30 | }
31 |
32 | interface GroupTitleProps {
33 | children?: React.ReactNode
34 | }
35 |
36 | export const GroupTitle = (props: GroupTitleProps) => {
37 | return (
38 |
39 | {props.children}
40 |
41 | )
42 | }
43 |
--------------------------------------------------------------------------------
/src/components/Group/styles.css.ts:
--------------------------------------------------------------------------------
1 | import { vars, responsiveStyle } from '@0xsequence/design-system'
2 | import { style } from '@vanilla-extract/css'
3 |
4 | export const groupItems = style({
5 | display: 'grid',
6 | gridColumnGap: vars.space[2],
7 | gridRowGap: vars.space[2],
8 | gridTemplateColumns: 'repeat(1, minmax(0, 1fr))',
9 | '@media': responsiveStyle({
10 | lg: {
11 | gridTemplateColumns: 'repeat(2, minmax(0, 1fr))'
12 | },
13 | xl: {
14 | gridTemplateColumns: 'repeat(3, minmax(0, 1fr))'
15 | }
16 | })
17 | })
18 |
--------------------------------------------------------------------------------
/src/constants/abi.ts:
--------------------------------------------------------------------------------
1 | export const ERC_1155_ABI = [
2 | {
3 | inputs: [
4 | {
5 | internalType: 'address',
6 | name: '_from',
7 | type: 'address'
8 | },
9 | {
10 | internalType: 'address',
11 | name: '_to',
12 | type: 'address'
13 | },
14 | {
15 | internalType: 'uint256[]',
16 | name: '_ids',
17 | type: 'uint256[]'
18 | },
19 | {
20 | internalType: 'uint256[]',
21 | name: '_amounts',
22 | type: 'uint256[]'
23 | },
24 | {
25 | internalType: 'bytes',
26 | name: '_data',
27 | type: 'bytes'
28 | }
29 | ],
30 | name: 'safeBatchTransferFrom',
31 | outputs: [],
32 | stateMutability: 'nonpayable',
33 | type: 'function'
34 | }
35 | ]
36 |
37 | export const ERC_20_ABI = [
38 | {
39 | constant: false,
40 | inputs: [
41 | {
42 | internalType: 'address',
43 | name: 'recipient',
44 | type: 'address'
45 | },
46 | {
47 | internalType: 'uint256',
48 | name: 'amount',
49 | type: 'uint256'
50 | }
51 | ],
52 | name: 'transfer',
53 | outputs: [
54 | {
55 | internalType: 'bool',
56 | name: '',
57 | type: 'bool'
58 | }
59 | ],
60 | payable: false,
61 | stateMutability: 'nonpayable',
62 | type: 'function'
63 | }
64 | ]
65 |
--------------------------------------------------------------------------------
/src/helpers.ts:
--------------------------------------------------------------------------------
1 | export const getDefaultChainId = () => {
2 | const chainId = window.localStorage.getItem('defaultChainId')
3 | if (chainId === null || chainId === undefined) {
4 | return null
5 | } else {
6 | return Number(chainId)
7 | }
8 | }
9 |
10 | export const saveDefaultChainId = (chainId: number) => {
11 | console.log('huh?', chainId)
12 | window.localStorage.setItem('defaultChainId', `${chainId}`)
13 | }
14 |
15 | export const toHexString = (value: bigint) => {
16 | return '0x' + value.toString(16)
17 | }
18 |
--------------------------------------------------------------------------------
/src/images/logo.svg:
--------------------------------------------------------------------------------
1 |
62 |
--------------------------------------------------------------------------------
/src/images/skyweaver-banner-large.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xsequence/demo-dapp/56dc8e29ee043d2722a5e6c92212f15548d668d4/src/images/skyweaver-banner-large.png
--------------------------------------------------------------------------------
/src/images/skyweaver-banner.old.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xsequence/demo-dapp/56dc8e29ee043d2722a5e6c92212f15548d668d4/src/images/skyweaver-banner.old.png
--------------------------------------------------------------------------------
/src/images/skyweaver-banner.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xsequence/demo-dapp/56dc8e29ee043d2722a5e6c92212f15548d668d4/src/images/skyweaver-banner.png
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | *,
2 | *::before,
3 | *::after {
4 | box-sizing: border-box;
5 | }
6 |
7 | body {
8 | background: var(--seq-colors-background-primary);
9 | }
10 |
--------------------------------------------------------------------------------
/src/index.tsx:
--------------------------------------------------------------------------------
1 | import './index.css'
2 | import '@0xsequence/design-system/styles.css'
3 |
4 | import { ThemeProvider } from '@0xsequence/design-system'
5 | import React from 'react'
6 | import { createRoot } from 'react-dom/client'
7 | import App from './App'
8 |
9 | const root = createRoot(document.getElementById('root'))
10 |
11 | root.render(
12 |
13 |
14 |
15 |
16 |
17 | )
18 |
--------------------------------------------------------------------------------
/src/react-app-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/src/vite-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ESNext",
4 | "lib": [
5 | "dom",
6 | "dom.iterable",
7 | "esnext"
8 | ],
9 | "allowJs": true,
10 | "skipLibCheck": true,
11 | "esModuleInterop": true,
12 | "allowSyntheticDefaultImports": true,
13 | "strict": false,
14 | "forceConsistentCasingInFileNames": true,
15 | "noFallthroughCasesInSwitch": true,
16 | "module": "esnext",
17 | "moduleResolution": "node",
18 | "resolveJsonModule": true,
19 | "isolatedModules": true,
20 | "noEmit": true,
21 | "jsx": "react-jsx",
22 | "types": ["vite/client", "vite-plugin-svgr/client"],
23 | },
24 | "include": [
25 | "src"
26 | ]
27 | }
28 |
--------------------------------------------------------------------------------
/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite'
2 | import dns from 'dns'
3 | import react from '@vitejs/plugin-react'
4 | import viteTsconfigPaths from 'vite-tsconfig-paths'
5 | import svgrPlugin from 'vite-plugin-svgr'
6 | import { vanillaExtractPlugin } from '@vanilla-extract/vite-plugin'
7 |
8 | dns.setDefaultResultOrder('verbatim')
9 |
10 | // https://vitejs.dev/config/
11 | export default defineConfig({
12 | plugins: [react(), viteTsconfigPaths(), svgrPlugin(), vanillaExtractPlugin()],
13 | server: {
14 | port: 4000,
15 | fs: {
16 | // Allow serving files from one level up to the project root
17 | allow: ['..']
18 | }
19 | },
20 | base: '/demo-dapp'
21 | })
22 |
--------------------------------------------------------------------------------