89 | >
90 | )
91 | }
92 |
93 | export default AuthKitDemo
94 |
--------------------------------------------------------------------------------
/src/constants/chains.ts:
--------------------------------------------------------------------------------
1 | import Chain from 'src/models/chain'
2 |
3 | export const gnosisChain: Chain = {
4 | id: '0x64',
5 | token: 'xDai',
6 | shortName: 'gno',
7 | label: 'Gnosis Chain',
8 | rpcUrl: 'https://rpc.gnosischain.com',
9 | blockExplorerUrl: 'https://gnosisscan.io',
10 | color: '#3e6957',
11 | transactionServiceUrl: 'https://safe-transaction-gnosis-chain.safe.global',
12 | isStripePaymentsEnabled: false,
13 | isMoneriumPaymentsEnabled: false,
14 | supportedErc20Tokens: [
15 | '0xDDAfbb505ad214D7b80b1f830fcCc89B60fb7A83' // USDC
16 | ]
17 | }
18 |
19 | export const goerliChain: Chain = {
20 | id: '0x5',
21 | token: 'gETH',
22 | label: 'Görli',
23 | shortName: 'gor',
24 | rpcUrl: 'https://goerli.infura.io/v3/9aa3d95b3bc440fa88ea12eaa4456161',
25 | blockExplorerUrl: 'https://goerli.etherscan.io',
26 | color: '#fbc02d',
27 | transactionServiceUrl: 'https://safe-transaction-goerli.safe.global',
28 | isStripePaymentsEnabled: false,
29 | isMoneriumPaymentsEnabled: true,
30 | supportedErc20Tokens: [
31 | '0xB4FBF271143F4FBf7B91A5ded31805e42b2208d6' // WETH
32 | ]
33 | }
34 |
35 | export const mainnetChain: Chain = {
36 | id: '0x1',
37 | token: 'ETH',
38 | label: 'Ethereum',
39 | shortName: 'eth',
40 | rpcUrl: 'https://cloudflare-eth.com',
41 | blockExplorerUrl: 'https://etherscan.io',
42 | color: '#DDDDDD',
43 | transactionServiceUrl: 'https://safe-transaction-mainnet.safe.global',
44 | isStripePaymentsEnabled: false,
45 | isMoneriumPaymentsEnabled: false,
46 | supportedErc20Tokens: [
47 | '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48' // USDC
48 | ]
49 | }
50 |
51 | export const polygonChain: Chain = {
52 | id: '0x89',
53 | token: 'matic',
54 | shortName: 'matic',
55 | label: 'Polygon',
56 | rpcUrl: 'https://polygon-rpc.com',
57 | blockExplorerUrl: 'https://polygonscan.com',
58 | color: '#8248E5',
59 | transactionServiceUrl: 'https://safe-transaction-polygon.safe.global',
60 | isStripePaymentsEnabled: false,
61 | isMoneriumPaymentsEnabled: false,
62 | supportedErc20Tokens: [
63 | '0x2791bca1f2de4661ed88a30c99a7a9449aa84174' // USDC
64 | ]
65 | }
66 |
67 | export const mumbaiChain: Chain = {
68 | id: '0x13881',
69 | token: 'matic',
70 | shortName: 'matic',
71 | label: 'Mumbai',
72 | rpcUrl: 'https://rpc-mumbai.maticvigil.com/',
73 | blockExplorerUrl: 'https://mumbai.polygonscan.com',
74 | color: '#8248E5',
75 | isStripePaymentsEnabled: true,
76 | isMoneriumPaymentsEnabled: false,
77 | faucetUrl: 'https://mumbaifaucet.com/',
78 | supportedErc20Tokens: [
79 | '0x9c3C9283D3e44854697Cd22D3Faa240Cfb032889' // WMATIC
80 | ]
81 | }
82 |
83 | export const sepoliaChain: Chain = {
84 | id: '0xaa36a7',
85 | token: 'SepoliaETH',
86 | shortName: 'eth',
87 | label: 'Sepolia',
88 | rpcUrl: 'https://ethereum-sepolia.publicnode.com/',
89 | blockExplorerUrl: 'https://sepolia.polygonscan.com',
90 | color: '#AA36A7',
91 | isStripePaymentsEnabled: false,
92 | isMoneriumPaymentsEnabled: false,
93 | faucetUrl: 'https://sepoliafaucet.com/',
94 | supportedErc20Tokens: ['0x8267cF9254734C6Eb452a7bb9AAF97B392258b21']
95 | }
96 |
97 | const chains: Chain[] = [
98 | gnosisChain,
99 | goerliChain,
100 | mainnetChain,
101 | mumbaiChain,
102 | polygonChain,
103 | sepoliaChain
104 | ]
105 |
106 | export const initialChain = mumbaiChain
107 |
108 | export default chains
109 |
--------------------------------------------------------------------------------
/src/App.tsx:
--------------------------------------------------------------------------------
1 | import { useCallback, useEffect, useState } from 'react'
2 | import Box from '@mui/material/Box'
3 | import Button from '@mui/material/Button'
4 | import CssBaseline from '@mui/material/CssBaseline'
5 | import Stack from '@mui/material/Stack'
6 | import Typography from '@mui/material/Typography'
7 | import '@safe-global/safe-react-components/dist/fonts.css'
8 |
9 | import Header from 'src/components/header/Header'
10 | import Providers from 'src/components/providers/Providers'
11 | import AuthKitDemo from 'src/pages/AuthKitDemo'
12 | import Intro from 'src/pages/Intro'
13 | import OnRampKitDemo from 'src/pages/OnRampKitDemo'
14 | import RelayKitDemo from 'src/pages/RelayKitDemo'
15 | import NavMenu from './components/nav-menu/NavMenu'
16 | import SafeCoreInfo from './components/safe-core-info/SafeCoreInfo'
17 | import { useAccountAbstraction } from './store/accountAbstractionContext'
18 | import isMoneriumRedirect from './utils/isMoneriumRedirect'
19 |
20 | function App() {
21 | const { setChainId } = useAccountAbstraction()
22 | const [activeStep, setActiveStep] = useState(0)
23 |
24 | useEffect(() => {
25 | if (isMoneriumRedirect()) {
26 | setActiveStep(2)
27 | }
28 | }, [setChainId])
29 |
30 | const nextStep = useCallback(() => {
31 | setActiveStep((activeStep) => activeStep + 1)
32 | }, [])
33 |
34 | const previousStep = useCallback(() => {
35 | setActiveStep((activeStep) => activeStep - 1)
36 | }, [])
37 |
38 | const setStep = useCallback((newStep: number) => {
39 | setActiveStep(newStep)
40 | }, [])
41 |
42 | const isFirstStep = activeStep === 0
43 | const isLastStep = activeStep === steps.length - 1
44 |
45 | const showSafeCoreVideo = isFirstStep
46 |
47 | const ActiveStepComponent = steps[activeStep].component
48 | const nextLabel = steps[activeStep].nextLabel
49 |
50 | return (
51 |
183 | >
184 | )
185 | }
186 |
187 | export default RelayKitDemo
188 |
--------------------------------------------------------------------------------
/src/components/safe-info/SafeInfo.tsx:
--------------------------------------------------------------------------------
1 | import { useCallback, useState } from 'react'
2 | import { ethers } from 'ethers'
3 | import styled from '@emotion/styled'
4 | import { Skeleton, Theme } from '@mui/material'
5 | import FormControl from '@mui/material/FormControl'
6 | import MenuItem from '@mui/material/MenuItem'
7 | import Select, { SelectChangeEvent } from '@mui/material/Select'
8 | import Stack from '@mui/material/Stack'
9 | import Tooltip from '@mui/material/Tooltip'
10 | import Typography from '@mui/material/Typography'
11 |
12 | import AddressLabel from 'src/components/address-label/AddressLabel'
13 | import AmountLabel from 'src/components/amount-label/AmountLabel'
14 | import getSafeInfo from 'src/api/getSafeInfo'
15 | import useApi from 'src/hooks/useApi'
16 | import safeLogoLight from 'src/assets/safe-info-logo-light.svg'
17 | import safeLogoDark from 'src/assets/safe-info-logo-dark.svg'
18 | import usePolling from 'src/hooks/usePolling'
19 | import { useAccountAbstraction } from 'src/store/accountAbstractionContext'
20 | import { useTheme } from 'src/store/themeContext'
21 | import isContractAddress from 'src/utils/isContractAddress'
22 |
23 | type TokenBalanceProps = {
24 | value: string
25 | tokenSymbol: string
26 | }
27 |
28 | function TokenBalance({ value, tokenSymbol }: TokenBalanceProps) {
29 | return (
30 |
213 | >
214 | )
215 | }
216 |
217 | export default OnRampKitDemo
218 |
--------------------------------------------------------------------------------
/src/assets/safe-logo.svg:
--------------------------------------------------------------------------------
1 |
20 |
--------------------------------------------------------------------------------
/src/constants/erc20ABI.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "anonymous": false,
4 | "inputs": [
5 | {
6 | "indexed": true,
7 | "internalType": "address",
8 | "name": "owner",
9 | "type": "address"
10 | },
11 | {
12 | "indexed": true,
13 | "internalType": "address",
14 | "name": "spender",
15 | "type": "address"
16 | },
17 | {
18 | "indexed": false,
19 | "internalType": "uint256",
20 | "name": "value",
21 | "type": "uint256"
22 | }
23 | ],
24 | "name": "Approval",
25 | "type": "event"
26 | },
27 | {
28 | "anonymous": false,
29 | "inputs": [
30 | {
31 | "indexed": true,
32 | "internalType": "address",
33 | "name": "_account",
34 | "type": "address"
35 | }
36 | ],
37 | "name": "Blacklisted",
38 | "type": "event"
39 | },
40 | {
41 | "anonymous": false,
42 | "inputs": [
43 | {
44 | "indexed": true,
45 | "internalType": "address",
46 | "name": "newBlacklister",
47 | "type": "address"
48 | }
49 | ],
50 | "name": "BlacklisterChanged",
51 | "type": "event"
52 | },
53 | {
54 | "anonymous": false,
55 | "inputs": [
56 | {
57 | "indexed": true,
58 | "internalType": "address",
59 | "name": "burner",
60 | "type": "address"
61 | },
62 | {
63 | "indexed": false,
64 | "internalType": "uint256",
65 | "name": "amount",
66 | "type": "uint256"
67 | }
68 | ],
69 | "name": "Burn",
70 | "type": "event"
71 | },
72 | {
73 | "anonymous": false,
74 | "inputs": [
75 | {
76 | "indexed": true,
77 | "internalType": "address",
78 | "name": "newMasterMinter",
79 | "type": "address"
80 | }
81 | ],
82 | "name": "MasterMinterChanged",
83 | "type": "event"
84 | },
85 | {
86 | "anonymous": false,
87 | "inputs": [
88 | {
89 | "indexed": true,
90 | "internalType": "address",
91 | "name": "minter",
92 | "type": "address"
93 | },
94 | {
95 | "indexed": true,
96 | "internalType": "address",
97 | "name": "to",
98 | "type": "address"
99 | },
100 | {
101 | "indexed": false,
102 | "internalType": "uint256",
103 | "name": "amount",
104 | "type": "uint256"
105 | }
106 | ],
107 | "name": "Mint",
108 | "type": "event"
109 | },
110 | {
111 | "anonymous": false,
112 | "inputs": [
113 | {
114 | "indexed": true,
115 | "internalType": "address",
116 | "name": "minter",
117 | "type": "address"
118 | },
119 | {
120 | "indexed": false,
121 | "internalType": "uint256",
122 | "name": "minterAllowedAmount",
123 | "type": "uint256"
124 | }
125 | ],
126 | "name": "MinterConfigured",
127 | "type": "event"
128 | },
129 | {
130 | "anonymous": false,
131 | "inputs": [
132 | {
133 | "indexed": true,
134 | "internalType": "address",
135 | "name": "oldMinter",
136 | "type": "address"
137 | }
138 | ],
139 | "name": "MinterRemoved",
140 | "type": "event"
141 | },
142 | {
143 | "anonymous": false,
144 | "inputs": [
145 | {
146 | "indexed": false,
147 | "internalType": "address",
148 | "name": "previousOwner",
149 | "type": "address"
150 | },
151 | {
152 | "indexed": false,
153 | "internalType": "address",
154 | "name": "newOwner",
155 | "type": "address"
156 | }
157 | ],
158 | "name": "OwnershipTransferred",
159 | "type": "event"
160 | },
161 | { "anonymous": false, "inputs": [], "name": "Pause", "type": "event" },
162 | {
163 | "anonymous": false,
164 | "inputs": [
165 | {
166 | "indexed": true,
167 | "internalType": "address",
168 | "name": "newAddress",
169 | "type": "address"
170 | }
171 | ],
172 | "name": "PauserChanged",
173 | "type": "event"
174 | },
175 | {
176 | "anonymous": false,
177 | "inputs": [
178 | {
179 | "indexed": true,
180 | "internalType": "address",
181 | "name": "from",
182 | "type": "address"
183 | },
184 | {
185 | "indexed": true,
186 | "internalType": "address",
187 | "name": "to",
188 | "type": "address"
189 | },
190 | {
191 | "indexed": false,
192 | "internalType": "uint256",
193 | "name": "value",
194 | "type": "uint256"
195 | }
196 | ],
197 | "name": "Transfer",
198 | "type": "event"
199 | },
200 | {
201 | "anonymous": false,
202 | "inputs": [
203 | {
204 | "indexed": true,
205 | "internalType": "address",
206 | "name": "_account",
207 | "type": "address"
208 | }
209 | ],
210 | "name": "UnBlacklisted",
211 | "type": "event"
212 | },
213 | { "anonymous": false, "inputs": [], "name": "Unpause", "type": "event" },
214 | {
215 | "inputs": [
216 | { "internalType": "address", "name": "owner", "type": "address" },
217 | { "internalType": "address", "name": "spender", "type": "address" }
218 | ],
219 | "name": "allowance",
220 | "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],
221 | "stateMutability": "view",
222 | "type": "function"
223 | },
224 | {
225 | "inputs": [
226 | { "internalType": "address", "name": "spender", "type": "address" },
227 | { "internalType": "uint256", "name": "value", "type": "uint256" }
228 | ],
229 | "name": "approve",
230 | "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],
231 | "stateMutability": "nonpayable",
232 | "type": "function"
233 | },
234 | {
235 | "inputs": [{ "internalType": "address", "name": "account", "type": "address" }],
236 | "name": "balanceOf",
237 | "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],
238 | "stateMutability": "view",
239 | "type": "function"
240 | },
241 | {
242 | "inputs": [{ "internalType": "address", "name": "_account", "type": "address" }],
243 | "name": "blacklist",
244 | "outputs": [],
245 | "stateMutability": "nonpayable",
246 | "type": "function"
247 | },
248 | {
249 | "inputs": [],
250 | "name": "blacklister",
251 | "outputs": [{ "internalType": "address", "name": "", "type": "address" }],
252 | "stateMutability": "view",
253 | "type": "function"
254 | },
255 | {
256 | "inputs": [{ "internalType": "uint256", "name": "_amount", "type": "uint256" }],
257 | "name": "burn",
258 | "outputs": [],
259 | "stateMutability": "nonpayable",
260 | "type": "function"
261 | },
262 | {
263 | "inputs": [
264 | { "internalType": "address", "name": "minter", "type": "address" },
265 | {
266 | "internalType": "uint256",
267 | "name": "minterAllowedAmount",
268 | "type": "uint256"
269 | }
270 | ],
271 | "name": "configureMinter",
272 | "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],
273 | "stateMutability": "nonpayable",
274 | "type": "function"
275 | },
276 | {
277 | "inputs": [],
278 | "name": "currency",
279 | "outputs": [{ "internalType": "string", "name": "", "type": "string" }],
280 | "stateMutability": "view",
281 | "type": "function"
282 | },
283 | {
284 | "inputs": [],
285 | "name": "decimals",
286 | "outputs": [{ "internalType": "uint8", "name": "", "type": "uint8" }],
287 | "stateMutability": "view",
288 | "type": "function"
289 | },
290 | {
291 | "inputs": [
292 | { "internalType": "string", "name": "tokenName", "type": "string" },
293 | { "internalType": "string", "name": "tokenSymbol", "type": "string" },
294 | { "internalType": "string", "name": "tokenCurrency", "type": "string" },
295 | { "internalType": "uint8", "name": "tokenDecimals", "type": "uint8" },
296 | {
297 | "internalType": "address",
298 | "name": "newMasterMinter",
299 | "type": "address"
300 | },
301 | { "internalType": "address", "name": "newPauser", "type": "address" },
302 | {
303 | "internalType": "address",
304 | "name": "newBlacklister",
305 | "type": "address"
306 | },
307 | { "internalType": "address", "name": "newOwner", "type": "address" }
308 | ],
309 | "name": "initialize",
310 | "outputs": [],
311 | "stateMutability": "nonpayable",
312 | "type": "function"
313 | },
314 | {
315 | "inputs": [{ "internalType": "address", "name": "_account", "type": "address" }],
316 | "name": "isBlacklisted",
317 | "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],
318 | "stateMutability": "view",
319 | "type": "function"
320 | },
321 | {
322 | "inputs": [{ "internalType": "address", "name": "account", "type": "address" }],
323 | "name": "isMinter",
324 | "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],
325 | "stateMutability": "view",
326 | "type": "function"
327 | },
328 | {
329 | "inputs": [],
330 | "name": "masterMinter",
331 | "outputs": [{ "internalType": "address", "name": "", "type": "address" }],
332 | "stateMutability": "view",
333 | "type": "function"
334 | },
335 | {
336 | "inputs": [
337 | { "internalType": "address", "name": "_to", "type": "address" },
338 | { "internalType": "uint256", "name": "_amount", "type": "uint256" }
339 | ],
340 | "name": "mint",
341 | "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],
342 | "stateMutability": "nonpayable",
343 | "type": "function"
344 | },
345 | {
346 | "inputs": [{ "internalType": "address", "name": "minter", "type": "address" }],
347 | "name": "minterAllowance",
348 | "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],
349 | "stateMutability": "view",
350 | "type": "function"
351 | },
352 | {
353 | "inputs": [],
354 | "name": "name",
355 | "outputs": [{ "internalType": "string", "name": "", "type": "string" }],
356 | "stateMutability": "view",
357 | "type": "function"
358 | },
359 | {
360 | "inputs": [],
361 | "name": "owner",
362 | "outputs": [{ "internalType": "address", "name": "", "type": "address" }],
363 | "stateMutability": "view",
364 | "type": "function"
365 | },
366 | {
367 | "inputs": [],
368 | "name": "pause",
369 | "outputs": [],
370 | "stateMutability": "nonpayable",
371 | "type": "function"
372 | },
373 | {
374 | "inputs": [],
375 | "name": "paused",
376 | "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],
377 | "stateMutability": "view",
378 | "type": "function"
379 | },
380 | {
381 | "inputs": [],
382 | "name": "pauser",
383 | "outputs": [{ "internalType": "address", "name": "", "type": "address" }],
384 | "stateMutability": "view",
385 | "type": "function"
386 | },
387 | {
388 | "inputs": [{ "internalType": "address", "name": "minter", "type": "address" }],
389 | "name": "removeMinter",
390 | "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],
391 | "stateMutability": "nonpayable",
392 | "type": "function"
393 | },
394 | {
395 | "inputs": [],
396 | "name": "symbol",
397 | "outputs": [{ "internalType": "string", "name": "", "type": "string" }],
398 | "stateMutability": "view",
399 | "type": "function"
400 | },
401 | {
402 | "inputs": [],
403 | "name": "totalSupply",
404 | "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],
405 | "stateMutability": "view",
406 | "type": "function"
407 | },
408 | {
409 | "inputs": [
410 | { "internalType": "address", "name": "to", "type": "address" },
411 | { "internalType": "uint256", "name": "value", "type": "uint256" }
412 | ],
413 | "name": "transfer",
414 | "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],
415 | "stateMutability": "nonpayable",
416 | "type": "function"
417 | },
418 | {
419 | "inputs": [
420 | { "internalType": "address", "name": "from", "type": "address" },
421 | { "internalType": "address", "name": "to", "type": "address" },
422 | { "internalType": "uint256", "name": "value", "type": "uint256" }
423 | ],
424 | "name": "transferFrom",
425 | "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],
426 | "stateMutability": "nonpayable",
427 | "type": "function"
428 | },
429 | {
430 | "inputs": [{ "internalType": "address", "name": "newOwner", "type": "address" }],
431 | "name": "transferOwnership",
432 | "outputs": [],
433 | "stateMutability": "nonpayable",
434 | "type": "function"
435 | },
436 | {
437 | "inputs": [{ "internalType": "address", "name": "_account", "type": "address" }],
438 | "name": "unBlacklist",
439 | "outputs": [],
440 | "stateMutability": "nonpayable",
441 | "type": "function"
442 | },
443 | {
444 | "inputs": [],
445 | "name": "unpause",
446 | "outputs": [],
447 | "stateMutability": "nonpayable",
448 | "type": "function"
449 | },
450 | {
451 | "inputs": [
452 | {
453 | "internalType": "address",
454 | "name": "_newBlacklister",
455 | "type": "address"
456 | }
457 | ],
458 | "name": "updateBlacklister",
459 | "outputs": [],
460 | "stateMutability": "nonpayable",
461 | "type": "function"
462 | },
463 | {
464 | "inputs": [
465 | {
466 | "internalType": "address",
467 | "name": "_newMasterMinter",
468 | "type": "address"
469 | }
470 | ],
471 | "name": "updateMasterMinter",
472 | "outputs": [],
473 | "stateMutability": "nonpayable",
474 | "type": "function"
475 | },
476 | {
477 | "inputs": [{ "internalType": "address", "name": "_newPauser", "type": "address" }],
478 | "name": "updatePauser",
479 | "outputs": [],
480 | "stateMutability": "nonpayable",
481 | "type": "function"
482 | }
483 | ]
484 |
--------------------------------------------------------------------------------
/src/store/accountAbstractionContext.tsx:
--------------------------------------------------------------------------------
1 | import { createContext, useCallback, useContext, useEffect, useState } from 'react'
2 | import { ethers } from 'ethers'
3 |
4 | import AccountAbstraction from '@safe-global/account-abstraction-kit-poc'
5 | import { SafeAuthInitOptions, SafeAuthPack } from '@safe-global/auth-kit'
6 | import { MoneriumPack, StripePack } from '@safe-global/onramp-kit'
7 | import { GelatoRelayPack } from '@safe-global/relay-kit'
8 | import { RelayResponse as GelatoRelayResponse } from '@gelatonetwork/relay-sdk'
9 | import Safe, { EthersAdapter } from '@safe-global/protocol-kit'
10 | import { MetaTransactionData, MetaTransactionOptions } from '@safe-global/safe-core-sdk-types'
11 |
12 | import { initialChain } from 'src/constants/chains'
13 | import usePolling from 'src/hooks/usePolling'
14 | import Chain from 'src/models/chain'
15 | import { ERC20Token } from 'src/models/erc20token'
16 | import getChain from 'src/utils/getChain'
17 | import { getERC20Info } from 'src/utils/getERC20Info'
18 | import getMoneriumInfo, { MoneriumInfo } from 'src/utils/getMoneriumInfo'
19 | import isMoneriumRedirect from 'src/utils/isMoneriumRedirect'
20 |
21 | type accountAbstractionContextValue = {
22 | ownerAddress?: string
23 | chainId: string
24 | safes: string[]
25 | tokenAddress: string
26 | erc20token?: ERC20Token
27 | chain?: Chain
28 | isAuthenticated: boolean
29 | web3Provider?: ethers.BrowserProvider
30 | loginWeb3Auth: () => void
31 | logoutWeb3Auth: () => void
32 | setChainId: (chainId: string) => void
33 | safeSelected?: string
34 | safeBalance?: string
35 | erc20Balances?: Record