Skip to content

Commit

Permalink
feat(onramp-kit): Monerium integration (safe-global#372)
Browse files Browse the repository at this point in the history
* Allow to pass EthAdapter from outside

* Use EthAdapter as parameter

* Add auth-kit

* Extract refreshToken to outside

* Rename to packs and upgrade monerium sdk

* Add socket listener connection

* Add subscriptions to sockets

* Upgrade monerium sdk to support node 16

* Add test
  • Loading branch information
yagopv authored May 11, 2023
1 parent 76c7a82 commit fb98dc5
Show file tree
Hide file tree
Showing 36 changed files with 5,889 additions and 189 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -60,3 +60,6 @@ typechain
openapi/

.idea

.yalc
yalc.lock
8 changes: 8 additions & 0 deletions packages/onramp-kit/example/client/.env.sample
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,11 @@ VITE_STRIPE_PUBLIC_KEY=
# More info here:
# https://docs.safe.global/learn/safe-core/safe-core-account-abstraction-sdk/onramp-kit#prerequisites
VITE_SAFE_STRIPE_BACKEND_BASE_URL=

# Configure the Monerium client ID. You need to get one from Monerium.
# More info here:
# https://monerium.dev/docs/getting-started/create-app
VITE_MONERIUM_CLIENT_ID=

# Get the web3auth client id from https://web3auth.io
VITE_WEB3AUTH_CLIENT_ID=
13 changes: 10 additions & 3 deletions packages/onramp-kit/example/client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,18 @@
"dependencies": {
"@emotion/react": "^11.10.5",
"@emotion/styled": "^11.10.5",
"@monerium/sdk": "^2.3.0",
"@mui/material": "^5.11.7",
"@safe-global/safe-react-components": "^2.0.1",
"ethers": "^6.0.3",
"@safe-global/api-kit": "^1.1.0",
"@safe-global/auth-kit": "^1.0.0",
"@safe-global/protocol-kit": "^1.0.0",
"@safe-global/safe-react-components": "^2.0.5",
"@web3auth/modal": "^4.3.1",
"@web3auth/openlogin-adapter": "^4.3.0",
"ethers": "^5.7.0",
"react": "^18.2.0",
"react-dom": "^18.2.0"
"react-dom": "^18.2.0",
"react-router-dom": "^6.10.0"
},
"devDependencies": {
"@esbuild-plugins/node-globals-polyfill": "^0.2.3",
Expand Down
95 changes: 5 additions & 90 deletions packages/onramp-kit/example/client/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,99 +1,14 @@
import { useEffect, useState, useRef } from 'react'
import { isAddress } from '@ethersproject/address'
import { SafeOnRampKit, StripeSession, StripePack } from '../../../src'
import { Grid, TextField, Button } from '@mui/material'
import { Outlet } from 'react-router-dom'

import AppBar from './AppBar'

const isSessionValid = (sessionId: string) => sessionId.length === 28
import { AuthProvider } from './AuthContext'

function App() {
const [walletAddress, setWalletAddress] = useState<string>('')
const [sessionId, setSessionId] = useState<string>('')
const [onRampClient, setOnRampClient] = useState<SafeOnRampKit<StripePack>>()
const stripeRootRef = useRef<HTMLDivElement>(null)

const handleCreateSession = async () => {
if (!isSessionValid(sessionId) && !isAddress(walletAddress)) return

if (stripeRootRef.current) {
stripeRootRef.current.innerHTML = ''
}

const sessionData = (await onRampClient?.open({
element: '#stripe-root',
sessionId: sessionId,
theme: 'light',
defaultOptions: {
transaction_details: {
wallet_address: walletAddress,
supported_destination_networks: ['ethereum', 'polygon'],
supported_destination_currencies: ['usdc'],
lock_wallet_address: true
},
customer_information: {
email: '[email protected]'
}
}
})) as StripeSession

onRampClient?.subscribe('onramp_ui_loaded', () => {
console.log('UI loaded')
})

onRampClient?.subscribe('onramp_session_updated', (e) => {
console.log('Session Updated', e.payload)
})

setWalletAddress(sessionData?.transaction_details?.wallet_address || '')
}

useEffect(() => {
;(async () => {
const onRampClient = await SafeOnRampKit.init(
new StripePack({
stripePublicKey: import.meta.env.VITE_STRIPE_PUBLIC_KEY,
onRampBackendUrl: import.meta.env.VITE_SAFE_STRIPE_BACKEND_BASE_URL
})
)

setOnRampClient(onRampClient)
})()
}, [])

return (
<>
<AuthProvider>
<AppBar />
<Grid container p={2} height="90vh">
<Grid item sm={12} md={4} p={2} sx={{ borderRight: `1px solid #303030` }}>
<TextField
id="wallet-address"
label="Wallet address"
placeholder="Enter the address you want to initialize the session with"
variant="outlined"
value={walletAddress}
onChange={(event) => setWalletAddress(event.target.value)}
sx={{ width: '100%' }}
/>
<TextField
id="session-id"
label="Session id"
placeholder="Enter the session id if you have one"
variant="outlined"
value={sessionId}
onChange={(event) => setSessionId(event.target.value)}
sx={{ width: '100%', mt: 2 }}
/>
<br />
<Button variant="contained" onClick={handleCreateSession} sx={{ mt: 3 }}>
Create session
</Button>
</Grid>
<Grid item sm={12} md={8} p={2}>
<div id="stripe-root" ref={stripeRootRef}></div>
</Grid>
</Grid>
</>
<Outlet />
</AuthProvider>
)
}

Expand Down
73 changes: 69 additions & 4 deletions packages/onramp-kit/example/client/src/AppBar.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,77 @@
import { AppBar as MuiAppBar, Typography, styled } from '@mui/material'
import { Link as RouterLink } from 'react-router-dom'
import {
AppBar as MuiAppBar,
Typography,
styled,
Link,
Button,
Box,
MenuItem,
Select,
SelectChangeEvent
} from '@mui/material'
import { EthHashInfo } from '@safe-global/safe-react-components'

import { useAuth } from './AuthContext'

const AppBar = () => {
const { logIn, logOut, isLoggedIn, data, selectedSafe, setSelectedSafe } = useAuth()

return (
<StyledAppBar position="static" color="default">
<Typography variant="h3" pl={4} fontWeight={700}>
Payments Provider Demo
<Typography variant="h3" pl={3} fontWeight={700}>
OnRamp
</Typography>
<nav>
<Link to={`/stripe`} component={RouterLink} pl={2} sx={{ textDecoration: 'none' }}>
Stripe
</Link>
<Link to={`/monerium`} component={RouterLink} pl={2} sx={{ textDecoration: 'none' }}>
Monerium
</Link>
</nav>
<Box mr={5} display="flex" justifyContent="flex-end" alignItems="center" width="100%">
{isLoggedIn ? (
<>
<EthHashInfo name="Owner" address={data?.eoa || ''} showCopyButton />

{data && data?.safes && data?.safes?.length > 0 && (
<Select
value={selectedSafe}
onChange={(event: SelectChangeEvent<string>) =>
setSelectedSafe?.(event.target.value)
}
sx={{ height: '54px' }}
>
{data?.safes.map((safe, index) => (
<MenuItem key={safe} value={safe}>
<EthHashInfo name={`Safe ${index + 1}`} address={safe} showCopyButton />
</MenuItem>
))}
</Select>
)}

{data && data?.safes && !data?.safes?.length && (
<Button
color="primary"
variant="text"
href="https://app.safe.global/new-safe/create"
target="_blank"
>
Deploy new Safe
</Button>
)}

<Button variant="contained" color="error" onClick={logOut} sx={{ ml: 2 }}>
Disconnect
</Button>
</>
) : (
<Button variant="contained" onClick={logIn}>
Connect
</Button>
)}
</Box>
</StyledAppBar>
)
}
Expand All @@ -17,7 +83,6 @@ const StyledAppBar = styled(MuiAppBar)`
background: ${({ theme }) => theme.palette.background.paper};
height: 70px;
align-items: center;
justify-content: space-between;
flex-direction: row;
border-bottom: 2px solid ${({ theme }) => theme.palette.background.paper};
box-shadow: none;
Expand Down
143 changes: 143 additions & 0 deletions packages/onramp-kit/example/client/src/AuthContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
import React, { createContext, useState, useEffect } from 'react'
import { Web3AuthOptions } from '@web3auth/modal'
import { CHAIN_NAMESPACES, SafeEventEmitterProvider, WALLET_ADAPTERS } from '@web3auth/base'
import { OpenloginAdapter } from '@web3auth/openlogin-adapter'
import { SafeAuthKit, Web3AuthModalPack, SafeAuthSignInData } from '@safe-global/auth-kit'

type AuthContextProviderProps = {
children: React.ReactNode
}

type AuthContextType = {
isLoggedIn: boolean
provider?: SafeEventEmitterProvider
data?: SafeAuthSignInData
selectedSafe: string
setSelectedSafe?: (safe: string) => void
logIn?: () => void
logOut?: () => void
}

export const AuthContext = createContext<AuthContextType>({
isLoggedIn: false,
selectedSafe: ''
})

const AuthProvider = ({ children }: AuthContextProviderProps) => {
const [isLoggedIn, setIsLoggedIn] = useState(false)
const [safeAuth, setSafeAuth] = useState<SafeAuthKit<Web3AuthModalPack>>()
const [safeAuthSignInResponse, setSafeAuthSignInResponse] = useState<SafeAuthSignInData>()
const [provider, setProvider] = useState<SafeEventEmitterProvider | undefined>()
const [selectedSafe, setSelectedSafe] = useState('')

useEffect(() => {
;(async () => {
const options: Web3AuthOptions = {
clientId: import.meta.env.VITE_WEB3AUTH_CLIENT_ID || '',
web3AuthNetwork: 'testnet',
chainConfig: {
chainNamespace: CHAIN_NAMESPACES.EIP155,
chainId: '0x5',
rpcTarget: 'https://rpc.ankr.com/eth_goerli'
},
uiConfig: {
theme: 'dark',
loginMethodsOrder: ['google', 'facebook']
}
}

const modalConfig = {
[WALLET_ADAPTERS.TORUS_EVM]: {
label: 'torus',
showOnModal: false
},
[WALLET_ADAPTERS.METAMASK]: {
label: 'metamask',
showOnDesktop: true,
showOnMobile: false
}
}

const openloginAdapter = new OpenloginAdapter({
loginSettings: {
mfaLevel: 'mandatory'
},
adapterSettings: {
uxMode: 'popup',
whiteLabel: {
name: 'Safe'
}
}
})

const web3AuthPack = new Web3AuthModalPack(options, [openloginAdapter], modalConfig)

const safeAuthKit = await SafeAuthKit.init(web3AuthPack, {
txServiceUrl: 'https://safe-transaction-goerli.safe.global'
})

const provider = safeAuthKit.getProvider()

if (provider) {
const response = await safeAuthKit.signIn()
setSafeAuthSignInResponse(response)
setSelectedSafe(response?.safes?.[0] || '')
setProvider(provider as SafeEventEmitterProvider)

setIsLoggedIn(true)
}

setSafeAuth(safeAuthKit)
})()
}, [])

const logIn = async () => {
if (!safeAuth) return

const response = await safeAuth.signIn()
console.log('SIGN IN RESPONSE: ', response)

setSafeAuthSignInResponse(response)
setSelectedSafe(response?.safes?.[0] || '')
setProvider(safeAuth.getProvider() as SafeEventEmitterProvider)
setIsLoggedIn(true)
}

const logOut = async () => {
if (!safeAuth) return

await safeAuth.signOut()

setProvider(undefined)
setSafeAuthSignInResponse(undefined)
setIsLoggedIn(false)
}

return (
<AuthContext.Provider
value={{
isLoggedIn,
provider,
data: safeAuthSignInResponse,
logIn,
logOut,
selectedSafe,
setSelectedSafe
}}
>
{children}
</AuthContext.Provider>
)
}

const useAuth = () => {
const context = React.useContext(AuthContext)

if (context === undefined) {
throw new Error('useAuth must be used within a AuthContextProvider')
}

return context
}

export { AuthProvider, useAuth }
Loading

0 comments on commit fb98dc5

Please sign in to comment.