Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 26 additions & 2 deletions client/src/AppRoutes.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from 'react'
import { Route, Routes } from 'react-router-dom'
import React, { useEffect } from 'react'
import { Outlet, Route, Routes } from 'react-router-dom'
import Signup from './pages/Signup'
import Wallet from './pages/Wallet'
import Recover from './pages/Recover'
Expand All @@ -11,10 +11,34 @@ import Archive from './pages/Archive'
import TgSignup from './pages/TgSignup'
import TgRecover from './pages/TgRecover'
import SaveRecoverySecret from './pages/SaveRecoverySecret'
import querystring from 'query-string'
import TgRouter from './pages/TgRouter'
import useMultipleWallet from './hooks/useMultipleWallet'

const UserIdHandler = () => {
const qs = querystring.parse(location.search) as Record<string, string>
const { userId } = qs

const { wallet, containWallet, switchWallet } = useMultipleWallet()

useEffect(() => {
if (wallet?.address && wallet.pk && wallet.phone === userId) {
return
} else if (containWallet(userId)) {
switchWallet(userId)
}
}, [wallet, switchWallet, containWallet, userId])

return (
<Outlet />
)
}

const AppRoutes = (): React.JSX.Element => {
return (
<Routes>
<Route index element={<UserIdHandler />} />

{/* <Route exact path='/' element={() => <Landing />} /> */}
<Route path={paths.root} element={<Wallet />} />
<Route path={paths.wallet} element={<Wallet />} />
Expand Down
51 changes: 10 additions & 41 deletions client/src/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,12 @@ import {
hashMessage,
isAddress,
type ContractTransactionResponse,
Contract,
JsonRpcProvider
type Contract
} from 'ethers'
import config from '../config'
import Constants from '../../../shared/constants'
import web3, { provider, getTokenContract, getTokenMetadataContract } from '../../../shared/web3'
import stringify from 'json-stable-stringify'
import IERC165 from '../../abi/IERC165.json'

import IERC1155MetadataURI from '../../abi/IERC1155MetadataURI.json'
import IERC1155 from '../../abi/IERC1155.json'
import IERC721Metadata from '../../abi/IERC721Metadata.json'
import IERC721 from '../../abi/IERC721.json'
import IERC20Metadata from '../../abi/IERC20Metadata.json'
import IERC20 from '../../abi/IERC20.json'

export interface HeadersConfig {
secret: string
Expand Down Expand Up @@ -56,19 +48,6 @@ const apiBase = axios.create({
// web3.eth.defaultChain = config.chainId

// Contract.setProvider(web3.currentProvider)
const provider = new JsonRpcProvider(config.rpc)

const getTokenContract = {
ERC20: (address): Contract => new Contract(address, IERC20, provider),
ERC721: (address): Contract => new Contract(address, IERC721, provider),
ERC1155: (address): Contract => new Contract(address, IERC1155, provider)
}

const getTokenMetadataContract = {
ERC20: (address): Contract => new Contract(address, IERC20Metadata, provider),
ERC721: (address): Contract => new Contract(address, IERC721Metadata, provider),
ERC1155: (address): Contract => new Contract(address, IERC1155MetadataURI, provider)
}

let activeWallet: Wallet | undefined
const apis = {
Expand Down Expand Up @@ -133,25 +112,15 @@ const apis = {
}
},
getBalance: async ({ address }: { address: string }): Promise<bigint> => {
return (await provider.getBalance(address))
return await provider.getBalance(address)
},

getTokenBalance: async ({ address, contractAddress, tokenType = '', tokenId }): Promise<bigint> => {
if (!utils.isValidTokenType(tokenType)) {
throw new Error(`Unknown token type: ${tokenType}`)
}
const c = getTokenContract[tokenType](contractAddress)
if (tokenType === 'ERC20') {
return (await c.balanceOf(address)) as bigint
} else if (tokenType === 'ERC721') {
const owner = (await c.ownerOf(tokenId)) as string
return owner.toLowerCase() === address.toLowerCase() ? 1n : 0n
} else if (tokenType === 'ERC1155') {
return c.balanceOf(address, tokenId) as bigint
} else {
throw Error('unreachable')
}
},
getTokenBalance: async ({ address, contractAddress, tokenType = '', tokenId }): Promise<bigint> => await web3.getTokenBalance({
address,
contractAddress,
tokenType,
tokenId
}),

getTokenMetadata: async ({ tokenType, contractAddress, tokenId }): Promise<TokenMetaData> => {
if (!utils.isValidTokenType(tokenType)) {
Expand Down Expand Up @@ -266,7 +235,7 @@ const apis = {
return {}
},
getNFTType: async (contractAddress): Promise<string | null> => {
const c = new Contract(contractAddress, IERC165, provider)
const c = getTokenContract.ERC165(contractAddress)
const is721 = await c.supportsInterface(Constants.TokenInterfaces.ERC721) as boolean
if (is721) {
return 'ERC721'
Expand Down
14 changes: 5 additions & 9 deletions client/src/components/Container.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,21 @@ import MenuIcon from '../../assets/menu.svg'
import { Col, Main, Modal, Row } from './Layout'
import { Button, LinkWrarpper } from './Controls'
import { walletActions } from '../state/modules/wallet'
import { useDispatch, useSelector } from 'react-redux'
import { useDispatch } from 'react-redux'
import paths from '../pages/paths'
import apis from '../api/index'
import { useNavigate } from 'react-router'
import { type RootState } from '../state/rootReducer'
import { type WalletState } from '../state/modules/wallet/reducers'
import config from '../config'
import useMultipleWallet from '../hooks/useMultipleWallet'

const MainContainer = ({ children, withMenu = false }): React.JSX.Element => {
const navigate = useNavigate()
const dispatch = useDispatch()
const wallet = useSelector<RootState, WalletState>(state => state.wallet ?? {})
const address = Object.keys(wallet).find(e => apis.web3.isValidAddress(e))
const { wallet } = useMultipleWallet()

const [menuVisible, setMenuVisible] = useState(false)
const [logoutModalVisible, setLogoutModalVisible] = useState(false)

const state = wallet[address ?? '']
const p = state?.p
const p = wallet?.p
const logout = (): void => {
dispatch(walletActions.deleteAllWallet())
setLogoutModalVisible(false)
Expand All @@ -44,7 +40,7 @@ const MainContainer = ({ children, withMenu = false }): React.JSX.Element => {
<IconImg onClick={() => { setMenuVisible(!menuVisible) }} src={MenuIcon as string} />
{menuVisible &&
<MenuItems>
{wallet && <MenuItemLink onClick={() => { navigate(paths.wallet) }}>{wallet[address ?? ''].phone}</MenuItemLink>}
{wallet && <MenuItemLink onClick={() => { navigate(paths.wallet) }}>{wallet.phone}</MenuItemLink>}
<MenuItemLink onClick={() => { setLogoutModalVisible(true) }}>Logout</MenuItemLink>
</MenuItems>}
</MenuIconContainer>}
Expand Down
1 change: 0 additions & 1 deletion client/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@ const config = {
},
gasPrice: Number(process.env.GAS_PRICE ?? 1000),
chainId: Number(process.env.CHAIN_ID ?? 1666600000),
rpc: process.env.RPC ?? 'https://api.s0.t.hmny.io',
rootUrl: process.env.ROOT_URL ?? 'https://smswallet.xyz',
explorer: process.env.EXPLORER_URL ?? 'https://explorer.harmony.one/#/tx/{{txId}}',
explorerHistory: process.env.EXPLORER_URL ?? 'https://explorer.harmony.one/#/address/{{address}}',
Expand Down
59 changes: 59 additions & 0 deletions client/src/hooks/useMultipleWallet.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import React, { useCallback, useContext, useState } from "react"
import { useSelector } from "react-redux"
import { RootState } from "../state/rootReducer"
import { WalletState, Wallet } from "../state/modules/wallet/reducers"
import apis from '../api'

type WalletType = {
wallet?: Wallet
switchWallet: (phone: string) => Wallet | undefined
containWallet: (phone: string) => boolean
}

const WalletContext = React.createContext<WalletType>({
switchWallet: () => undefined,
containWallet: () => true
})

type MultipleWalletProviderProps = {
children: React.ReactNode
}

export const MultipleWalletProvider = ({ children }: MultipleWalletProviderProps) => {
const wallets = useSelector<RootState, WalletState>(state => state.wallet || {})

const [address, setAddress] = useState<string>()

const containWallet = useCallback((phone: string) =>
Object.keys(wallets).some(e => wallets[e].phone === phone && apis.web3.isValidAddress(e)),
[wallets]
)

const switchWallet = useCallback((phone: string) => {
const address = Object.keys(wallets).find(e => wallets[e].phone === phone && apis.web3.isValidAddress(e))

if (!address) {
return undefined
}

setAddress(address)

return wallets[address]
}, [wallets])

return (
<WalletContext.Provider
value={{
wallet: address ? wallets[address] : undefined,
switchWallet,
containWallet
}}
>
{children}
</WalletContext.Provider>
)
}

const useWalletAddress = () => useContext(WalletContext)

export default useWalletAddress
5 changes: 4 additions & 1 deletion client/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import 'react-phone-number-input/style.css'
import { HistoryRouter } from 'redux-first-history/rr6'
import { PersistGate } from 'redux-persist/es/integration/react'
import { Loading } from './components/Misc'
import { MultipleWalletProvider } from './hooks/useMultipleWallet'

// eslint-disable-next-line @typescript-eslint/no-empty-function
document.body.ontouchstart = function (): void {}
Expand All @@ -26,7 +27,9 @@ if (container != null) {
<Provider store={store}>
<PersistGate loading={<Loading/>} persistor={persistor}>
<HistoryRouter history={history}>
<Routes />
<MultipleWalletProvider>
<Routes />
</MultipleWalletProvider>
<ToastContainer />
</HistoryRouter>
</PersistGate>
Expand Down
57 changes: 41 additions & 16 deletions client/src/pages/ApproveTransaction.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, { useState, useEffect } from 'react'
import { useDispatch, useSelector } from 'react-redux'
import { useDispatch } from 'react-redux'
import paths from './paths'
import MainContainer from '../components/Container'
import querystring from 'query-string'
Expand All @@ -11,10 +11,9 @@ import { Row } from '../components/Layout'
import { utils } from '../utils'
import { pick } from 'lodash'
import { globalActions } from '../state/modules/global'
import { type RootState } from '../state/rootReducer'
import { type WalletState } from '../state/modules/wallet/reducers'
import { type TransactionReceipt } from 'ethers'
import { Navigate, useNavigate } from 'react-router'
import useMultipleWallet from '../hooks/useMultipleWallet'

export interface CallData {
method?: string
Expand Down Expand Up @@ -42,17 +41,24 @@ export interface ApproveTransactionParams {
onComplete?: (txr: TransactionReceipt) => any
}

export const ApproveTransaction = ({ calldata, caller, callback, comment, inputAmount, dest, onComplete }: ApproveTransactionParams): React.JSX.Element => {
const wallet = useSelector((state: RootState) => state.wallet || {})
const address = Object.keys(wallet).find(e => apis.web3.isValidAddress(e))
export const ApproveTransaction = ({
calldata,
caller,
callback,
comment,
inputAmount,
dest,
onComplete
}: ApproveTransactionParams): React.JSX.Element => {
const { wallet } = useMultipleWallet()

const [showDetails, setShowDetails] = useState(false)
const { balance: amount, formatted: amountFormatted } = utils.toBalance(inputAmount ?? '0')

if (!address || !wallet[address]?.pk) {
if (wallet?.pk === undefined) {
return <Navigate to={paths.signup} />
}
const pk = wallet[address]?.pk
const pk = wallet.pk

const execute = async (): Promise<void> => {
if (!calldata) {
Expand Down Expand Up @@ -97,7 +103,7 @@ export const ApproveTransaction = ({ calldata, caller, callback, comment, inputA
const returnUrl = new URL(callback)
returnUrl.searchParams.append('success', 'true')
returnUrl.searchParams.append('hash', receipt.hash)
returnUrl.searchParams.append('address', address)
returnUrl.searchParams.append('address', wallet.address)
toast.success(`Returning to app at ${returnUrl.hostname}`)
setTimeout(() => {
location.href = returnUrl.href
Expand Down Expand Up @@ -152,7 +158,11 @@ export const ApproveTransaction = ({ calldata, caller, callback, comment, inputA
<SmallText>Method: {calldata.method}</SmallText>
<SmallText>Parameters:</SmallText>
{(calldata.parameters ?? []).map((p, i) => {
return <SmallText style={{ wordBreak: 'break-all' }} key={`${i}`}>{JSON.stringify(pick(p, ['name', 'value', 'type']))}</SmallText>
return (
<SmallText style={{ wordBreak: 'break-all' }} key={`${i}`}>
{JSON.stringify(pick(p, ['name', 'value', 'type']))}
</SmallText>
)
})}
</>)}
</>}
Expand Down Expand Up @@ -180,15 +190,21 @@ export interface ApproveTransactionQuery {
const ApproveTransactionPage = (): React.JSX.Element => {
const dispatch = useDispatch()
const navigate = useNavigate()
const { caller, comment, amount: inputAmount, dest, calldata: calldataB64Encoded, phone, callback: callbackEncoded } = querystring.parse(location.search) as ApproveTransactionQuery
const {
caller,
comment,
amount: inputAmount,
dest,
calldata: calldataB64Encoded,
phone,
callback: callbackEncoded
} = querystring.parse(location.search) as ApproveTransactionQuery

const callback = utils.safeURL(Buffer.from(decodeURIComponent(callbackEncoded ?? ''), 'base64').toString())

const calldata = decodeCalldata(calldataB64Encoded)

const wallet = useSelector<RootState, WalletState>(state => state.wallet ?? {})
const address = Object.keys(wallet).find(e => apis.web3.isValidAddress(e)) ?? ''
const pk = wallet[address]?.pk
const { wallet } = useMultipleWallet()
const pk = wallet?.pk

useEffect(() => {
if (phone) {
Expand All @@ -212,7 +228,16 @@ const ApproveTransactionPage = (): React.JSX.Element => {
)
}

return <ApproveTransaction comment={comment} callback={callback} caller={caller} inputAmount={inputAmount} dest={dest} calldata={calldata} />
return (
<ApproveTransaction
comment={comment}
callback={callback}
caller={caller}
inputAmount={inputAmount}
dest={dest}
calldata={calldata}
/>
)
}

export default ApproveTransactionPage
Loading