Skip to content

Commit

Permalink
Merge pull request #98 from celo-tools/1.7.0
Browse files Browse the repository at this point in the history
1.7.0 - NFT support
  • Loading branch information
jmrossy authored May 3, 2022
2 parents 41277d9 + dacab58 commit d7e478a
Show file tree
Hide file tree
Showing 52 changed files with 2,085 additions and 66 deletions.
2 changes: 1 addition & 1 deletion electron/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ function setCspHeader() {
...details.responseHeaders,
// Should match header in /netlify/_headers and build.sh
'Content-Security-Policy': [
"default-src 'self'; script-src 'self' 'sha256-a0xx6QQjQFEl3BVHxY4soTXMFurPf9rWKnRLQLOkzg4='; connect-src 'self' https://*.celowallet.app https://*.celo.org wss://walletconnect.celo.org wss://*.walletconnect.com wss://*.walletconnect.org https://api.github.com https://eth-mainnet.alchemyapi.io https://unstoppabledomains.g.alchemy.com; img-src 'self' data:; style-src 'self' 'unsafe-inline'; font-src 'self' data:; base-uri 'self'; form-action 'self'",
"default-src 'self'; script-src 'self' 'sha256-a0xx6QQjQFEl3BVHxY4soTXMFurPf9rWKnRLQLOkzg4='; connect-src 'self' https://*.celowallet.app https://*.celo.org wss://walletconnect.celo.org wss://*.walletconnect.com wss://*.walletconnect.org https://api.github.com https://eth-mainnet.alchemyapi.io https://unstoppabledomains.g.alchemy.com https://cloudflare-ipfs.com; img-src 'self' data: https://cloudflare-ipfs.com; style-src 'self' 'unsafe-inline'; font-src 'self' data:; base-uri 'self'; form-action 'self'; frame-ancestors 'none'",
],
},
})
Expand Down
2 changes: 1 addition & 1 deletion netlify/_headers
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Configure Netlify custom headers
# CSP header should match electron copy in /electron/main.js
/*
Content-Security-Policy: default-src 'self'; script-src 'self' 'sha256-a0xx6QQjQFEl3BVHxY4soTXMFurPf9rWKnRLQLOkzg4='; connect-src 'self' https://*.celowallet.app https://*.celo.org wss://walletconnect.celo.org wss://*.walletconnect.com wss://*.walletconnect.org https://eth-mainnet.alchemyapi.io https://unstoppabledomains.g.alchemy.com; img-src 'self' data:; style-src 'self' 'unsafe-inline'; font-src 'self' data:; base-uri 'self'; form-action 'self'
Content-Security-Policy: default-src 'self'; script-src 'self' 'sha256-a0xx6QQjQFEl3BVHxY4soTXMFurPf9rWKnRLQLOkzg4='; connect-src 'self' https://*.celowallet.app https://*.celo.org wss://walletconnect.celo.org wss://*.walletconnect.com wss://*.walletconnect.org https://eth-mainnet.alchemyapi.io https://unstoppabledomains.g.alchemy.com https://cloudflare-ipfs.com; img-src 'self' data: https://cloudflare-ipfs.com; style-src 'self' 'unsafe-inline'; font-src 'self' data:; base-uri 'self'; form-action 'self'; frame-ancestors 'none'
X-Frame-Options: DENY
X-Content-Type-Options: nosniff
2 changes: 1 addition & 1 deletion package-electron.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "celo-web-wallet",
"version": "1.6.2",
"version": "1.7.0",
"description": "A lightweight web and desktop wallet for the Celo network",
"main": "main.js",
"keywords": [
Expand Down
3 changes: 1 addition & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "celo-web-wallet",
"version": "1.6.2",
"version": "1.7.0",
"description": "A lightweight web and desktop wallet for the Celo network",
"keywords": [
"Celo",
Expand Down Expand Up @@ -88,7 +88,6 @@
"jasmine": "^4.0.2",
"node-fetch": "^2.6.7",
"prettier": "^2.5.1",
"redux-mock-store": "^1.5.4",
"style-loader": "^3.3.1",
"ts-loader": "^9.2.7",
"typescript": "^4.5.5",
Expand Down
38 changes: 24 additions & 14 deletions src/app/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ import { HomeNavigator } from 'src/features/home/HomeNavigator'
import { HomeScreen } from 'src/features/home/HomeScreen'
import { LockConfirmationScreen } from 'src/features/lock/LockConfirmationScreen'
import { LockFormScreen } from 'src/features/lock/LockFormScreen'
import { NftDashboardScreen } from 'src/features/nft/NftDashboardScreen'
import { NftDetailsScreen } from 'src/features/nft/NftDetailsScreen'
import { NftSendConfirmScreen } from 'src/features/nft/NftSendConfirmScreen'
import { NftSendFormScreen } from 'src/features/nft/NftSendFormScreen'
import { ImportAccountScreen } from 'src/features/onboarding/import/ImportAccountScreen'
import { ImportChoiceScreen } from 'src/features/onboarding/import/ImportChoiceScreen'
import { LedgerImportScreen } from 'src/features/onboarding/import/LedgerImportScreen'
Expand Down Expand Up @@ -77,20 +81,6 @@ export const App = () => {
<Routes>
<Route path="/" element={<HomeNavigator />}>
<Route index element={<HomeScreen />} />
<Route path="tx" element={<TransactionReview />} />
<Route path="send" element={<SendFormScreen />} />
<Route path="send-review" element={<SendConfirmationScreen />} />
<Route path="exchange-review" element={<ExchangeConfirmationScreen />} />
<Route path="exchange" element={<ExchangeFormScreen />} />
<Route path="lock" element={<LockFormScreen />} />
<Route path="lock-review" element={<LockConfirmationScreen />} />
<Route path="validators" element={<ExploreValidatorsScreen />} />
<Route path="stake" element={<StakeFormScreen />} />
<Route path="stake-review" element={<StakeConfirmationScreen />} />
<Route path="stake-rewards" element={<StakeRewardsScreen />} />
<Route path="governance" element={<GovernanceFormScreen />} />
<Route path="governance-review" element={<GovernanceConfirmationScreen />} />
<Route path="balances" element={<BalanceDetailsScreen />} />
<Route path="account" element={<ViewAccountScreen />} />
<Route path="accounts" element={<AccountsNavigator />}>
<Route index element={<AccountsAndContactsScreen />} />
Expand All @@ -101,7 +91,27 @@ export const App = () => {
<Route path="ledger" element={<AddLedgerScreen />} />
<Route path="set-pin" element={<AddSetPasswordScreen />} />
</Route>
<Route path="balances" element={<BalanceDetailsScreen />} />
<Route path="exchange" element={<ExchangeFormScreen />} />
<Route path="exchange-review" element={<ExchangeConfirmationScreen />} />
<Route path="governance" element={<GovernanceFormScreen />} />
<Route path="governance-review" element={<GovernanceConfirmationScreen />} />
<Route path="lock" element={<LockFormScreen />} />
<Route path="lock-review" element={<LockConfirmationScreen />} />
<Route path="nft">
<Route index element={<NftDashboardScreen />} />
<Route path="details" element={<NftDetailsScreen />} />
<Route path="send" element={<NftSendFormScreen />} />
<Route path="review" element={<NftSendConfirmScreen />} />
</Route>
<Route path="send" element={<SendFormScreen />} />
<Route path="send-review" element={<SendConfirmationScreen />} />
<Route path="settings" element={<SettingsScreen />} />
<Route path="stake" element={<StakeFormScreen />} />
<Route path="stake-review" element={<StakeConfirmationScreen />} />
<Route path="stake-rewards" element={<StakeRewardsScreen />} />
<Route path="tx" element={<TransactionReview />} />
<Route path="validators" element={<ExploreValidatorsScreen />} />
</Route>

<Route path="/setup" element={<OnboardingNavigator />}>
Expand Down
2 changes: 2 additions & 0 deletions src/app/rootReducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { feedReducer } from 'src/features/feed/feedSlice'
import { feeReducer } from 'src/features/fees/feeSlice'
import { governanceReducer } from 'src/features/governance/governanceSlice'
import { lockReducer } from 'src/features/lock/lockSlice'
import { persistedNftReducer } from 'src/features/nft/nftSlice'
import { persistedSettingsReducer } from 'src/features/settings/settingsSlice'
import { persistedTokenPriceReducer } from 'src/features/tokenPrice/tokenPriceSlice'
import { persistedTokensReducer } from 'src/features/tokens/tokensSlice'
Expand All @@ -27,6 +28,7 @@ export const rootReducer = combineReducers({
tokenPrice: persistedTokenPriceReducer,
validators: persistedValidatorsReducer,
governance: governanceReducer,
nft: persistedNftReducer,
settings: persistedSettingsReducer,
walletConnect: walletConnectReducer,
txFlow: txFlowReducer,
Expand Down
36 changes: 35 additions & 1 deletion src/app/rootSaga.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,25 @@ import {
lockTokenSaga,
lockTokenSagaName,
} from 'src/features/lock/lockToken'
import {
addNftContractActions,
addNftContractReducer,
addNftContractSaga,
addNftContractSagaName,
} from 'src/features/nft/addNftContract'
import {
fetchNftImagesSaga,
fetchNftsActions,
fetchNftsReducer,
fetchNftsSaga,
fetchNftsSagaName,
} from 'src/features/nft/fetchNfts'
import {
sendNftActions,
sendNftReducer,
sendNftSaga,
sendNftSagaName,
} from 'src/features/nft/sendNft'
import {
changePasswordActions,
changePasswordReducer,
Expand Down Expand Up @@ -126,7 +145,7 @@ function* init() {
}

// All regular sagas must be included here
const sagas = [walletStatusPoller, watchWalletConnect]
const sagas = [walletStatusPoller, watchWalletConnect, fetchNftImagesSaga]

// All monitored sagas must be included here
export const monitoredSagas: {
Expand Down Expand Up @@ -217,6 +236,21 @@ export const monitoredSagas: {
reducer: governanceVoteReducer,
actions: governanceVoteActions,
},
[fetchNftsSagaName]: {
saga: fetchNftsSaga,
reducer: fetchNftsReducer,
actions: fetchNftsActions,
},
[sendNftSagaName]: {
saga: sendNftSaga,
reducer: sendNftReducer,
actions: sendNftActions,
},
[addNftContractSagaName]: {
saga: addNftContractSaga,
reducer: addNftContractReducer,
actions: addNftContractActions,
},
[editAccountSagaName]: {
saga: editAccountSaga,
reducer: editAccountReducer,
Expand Down
3 changes: 3 additions & 0 deletions src/blockchain/ABIs/erc721.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export const ABI = JSON.parse(
'[{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"approved","type":"address"},{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"operator","type":"address"},{"indexed":false,"internalType":"bool","name":"approved","type":"bool"}],"name":"ApprovalForAll","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"Transfer","type":"event"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"approve","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"balance","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"getApproved","outputs":[{"internalType":"address","name":"operator","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"operator","type":"address"}],"name":"isApprovedForAll","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"ownerOf","outputs":[{"internalType":"address","name":"owner","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"safeTransferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"safeTransferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"operator","type":"address"},{"internalType":"bool","name":"_approved","type":"bool"}],"name":"setApprovalForAll","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"transferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"tokenURI","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"uint256","name":"index","type":"uint256"}],"name":"tokenOfOwnerByIndex","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"index","type":"uint256"}],"name":"tokenByIndex","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"}]'
)
33 changes: 27 additions & 6 deletions src/blockchain/contracts.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { Contract } from 'ethers'
import { Contract, utils } from 'ethers'
import { ABI as AccountsAbi } from 'src/blockchain/ABIs/accounts'
import { ABI as ElectionAbi } from 'src/blockchain/ABIs/election'
import { ABI as Erc20Abi } from 'src/blockchain/ABIs/erc20'
import { ABI as Erc721Abi } from 'src/blockchain/ABIs/erc721'
import { ABI as EscrowAbi } from 'src/blockchain/ABIs/escrow'
import { ABI as ExchangeAbi } from 'src/blockchain/ABIs/exchange'
import { ABI as GoldTokenAbi } from 'src/blockchain/ABIs/goldToken'
Expand All @@ -12,7 +13,7 @@ import { ABI as StableTokenAbi } from 'src/blockchain/ABIs/stableToken'
import { ABI as ValidatorsAbi } from 'src/blockchain/ABIs/validators'
import { getSigner } from 'src/blockchain/signer'
import { CeloContract, config } from 'src/config'
import { areAddressesEqual } from 'src/utils/addresses'
import { areAddressesEqual, normalizeAddress } from 'src/utils/addresses'

let contractCache: Partial<Record<CeloContract, Contract>> = {}
let tokenContractCache: Partial<Record<string, Contract>> = {} // token address to contract
Expand All @@ -28,13 +29,22 @@ export function getContract(c: CeloContract) {
return contract
}

export function getErc20Contract(tokenAddress: Address) {
return getTokenContract(tokenAddress, Erc20Abi)
}

export function getErc721Contract(tokenAddress: Address) {
return getTokenContract(tokenAddress, Erc721Abi)
}

// Search for token contract by address
export function getTokenContract(tokenAddress: Address) {
const cachedContract = tokenContractCache[tokenAddress]
function getTokenContract(tokenAddress: Address, abi: string) {
const normalizedAddr = normalizeAddress(tokenAddress)
const cachedContract = tokenContractCache[normalizedAddr]
if (cachedContract) return cachedContract
const signer = getSigner().signer
const contract = new Contract(tokenAddress, Erc20Abi, signer)
tokenContractCache[tokenAddress] = contract
const contract = new Contract(normalizedAddr, abi, signer)
tokenContractCache[normalizedAddr] = contract
return contract
}

Expand Down Expand Up @@ -89,6 +99,17 @@ export function getContractName(address: Address): CeloContract | null {
return null
}

let erc721Interface: utils.Interface

// Normally, interfaces are retrieved through the getContract() function
// but ERC721 is an exception because no core celo contracts use it
export function getErc721AbiInterface() {
if (!erc721Interface) {
erc721Interface = new utils.Interface(Erc721Abi)
}
return erc721Interface
}

// Necessary if the signer changes, as in after a logout
export function clearContractCache() {
contractCache = {}
Expand Down
19 changes: 16 additions & 3 deletions src/components/animation/Fade.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import { PropsWithChildren, useEffect, useState } from 'react'
import { Stylesheet } from 'src/styles/types'

interface Props {
show: boolean
duration?: string
show: boolean // Should the elements be on the page
duration?: string // Duration of fade animation
transparent?: boolean // Remove elements or just make them transparent when not shown
}

export function Fade(props: PropsWithChildren<Props>) {
const { show, duration, children } = props
const { show, duration, transparent, children } = props
const [render, setRender] = useState(show)

useEffect(() => {
Expand All @@ -27,5 +29,16 @@ export function Fade(props: PropsWithChildren<Props>) {
>
{children}
</div>
) : transparent ? (
<div css={style.transparent} onAnimationEnd={onAnimationEnd}>
{children}
</div>
) : null
}

const style: Stylesheet = {
transparent: {
visibility: 'hidden',
position: 'relative',
},
}
11 changes: 8 additions & 3 deletions src/components/buttons/BackButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,17 @@ import {
import { ArrowIcon } from 'src/components/icons/Arrow'
import { Color } from 'src/styles/Color'

export function BackButton(props: Omit<Omit<TransparentIconButtonProps, 'icon'>, 'onClick'>) {
const { styles, iconStyles, margin, title, color } = props
type Props = Omit<Omit<TransparentIconButtonProps, 'icon'>, 'onClick'> & {
onGoBack?: () => void
}

export function BackButton(props: Props) {
const { styles, iconStyles, margin, title, color, onGoBack } = props

const navigate = useNavigate()
const onClickBack = () => {
navigate(-1)
if (onGoBack) onGoBack()
else navigate(-1)
}

return (
Expand Down
4 changes: 2 additions & 2 deletions src/components/buttons/RefreshButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ interface Props {

export function RefreshButton({ width, height, onClick, styles }: Props) {
return (
<button css={{ ...defaultStyle, ...styles }} onClick={onClick} type="button">
<img src={RefreshIcon} width={width} height={height} alt="refresh" />
<button css={{ ...defaultStyle, ...styles }} onClick={onClick} title="Refresh" type="button">
<img src={RefreshIcon} width={width} height={height} alt="Refresh" />
</button>
)
}
Expand Down
2 changes: 1 addition & 1 deletion src/components/icons/Circle.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ export function CircleIcon({
margin,
}: {
color: string
size: string
size: string | number
margin?: string
}) {
return (
Expand Down
21 changes: 21 additions & 0 deletions src/components/icons/KebabMenu.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { CircleIcon } from 'src/components/icons/Circle'
import { Box } from 'src/components/layout/Box'

// A.k.a. three dots dropdown
export function KebabMenuIcon({
color,
size,
margin,
}: {
color: string
size: number
margin?: string
}) {
return (
<Box direction="column" align="center" margin={margin}>
<CircleIcon size={size} color={color} />
<CircleIcon size={size} color={color} margin={`${size * 0.8}px 0 0 0`} />
<CircleIcon size={size} color={color} margin={`${size * 0.8}px 0 0 0`} />
</Box>
)
}
1 change: 1 addition & 0 deletions src/components/icons/nft.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit d7e478a

Please sign in to comment.