Skip to content

Commit

Permalink
Merge pull request #443 from deltaDAO/development
Browse files Browse the repository at this point in the history
Development
  • Loading branch information
moritzkirstein authored Oct 18, 2023
2 parents ed6322a + 637b746 commit 249d1a7
Show file tree
Hide file tree
Showing 43 changed files with 42,486 additions and 1,807 deletions.
6 changes: 6 additions & 0 deletions app.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,12 @@ module.exports = {
process.env.NEXT_PUBLIC_DOCKER_HUB_PROXY_URL ||
'https://dockerhub-proxy.delta-dao.com',

automationConfig: {
networkTokenFundDefaultValue: '2',
erc20ApprovalDefaultValue: '50',
roughTxGasEstimate: 0.02
},

// Display alert banner for the developer preview deployment
showPreviewAlert: process.env.NEXT_PUBLIC_SHOW_PREVIEW_ALERT || 'false'
}
2 changes: 1 addition & 1 deletion content/pages/imprint.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,4 @@ Germany<br/><br/>
**Commercial register**: Handelsregister B des Amtsgerichts Hamburg, HRB 170364<br/>
**USt – IdNr**: DE346013532<br/><br/>

The European Commission provides a platform for online dispute resolution, which you can find here: <https://ec.europa.eu/consumers/odr/>. We are not obliged or willing to participate in a dispute resolution procedure before a consumer arbitration board.
The European Commission provides a platform for online dispute resolution, which you can find here: <https://ec.europa.eu/consumers/odr/>. We are not obliged or willing to participate in a dispute resolution procedure before a consumer arbitration board.
42,926 changes: 41,187 additions & 1,739 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@
"serve": "^14.1.2",
"stream-http": "^3.2.0",
"tsconfig-paths-webpack-plugin": "^4.1.0",
"typescript": "^4.9.3"
"typescript": "^5.2.2"
},
"overrides": {
"graphql": "15.8.0"
Expand Down
18 changes: 16 additions & 2 deletions src/@context/Asset.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {
verifyRawServiceCredential
} from '@components/Publish/_utils'
import { useAccount, useNetwork } from 'wagmi'
import { useAutomation } from './Automation/AutomationProvider'

export interface AssetProviderValue {
isInPurgatory: boolean
Expand Down Expand Up @@ -56,6 +57,7 @@ function AssetProvider({
}): ReactElement {
const { appConfig } = useMarketMetadata()
const { address: accountId } = useAccount()
const { autoWallet, isAutomationEnabled } = useAutomation()
const { chain } = useNetwork()

const { isDDOWhitelisted } = useAddressConfig()
Expand Down Expand Up @@ -155,18 +157,30 @@ function AssetProvider({
const fetchAccessDetails = useCallback(async (): Promise<void> => {
if (!asset?.chainId || !asset?.services?.length) return

const accountIdToCheck =
isAutomationEnabled && autoWallet?.address
? autoWallet.address
: accountId

const accessDetails = await getAccessDetails(
asset.chainId,
asset.services[0].datatokenAddress,
asset.services[0].timeout,
accountId
accountIdToCheck
)
setAsset((prevState) => ({
...prevState,
accessDetails
}))
LoggerInstance.log(`[asset] Got access details for ${did}`, accessDetails)
}, [asset?.chainId, asset?.services, accountId, did])
}, [
asset?.chainId,
asset?.services,
accountId,
did,
autoWallet?.address,
isAutomationEnabled
])

// -----------------------------------
// Helper: Get and set asset Service Credential state
Expand Down
238 changes: 238 additions & 0 deletions src/@context/Automation/AutomationProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,238 @@
import { LoggerInstance } from '@oceanprotocol/lib'
import { Wallet, ethers } from 'ethers'
import React, {
createContext,
useCallback,
useContext,
useEffect,
useState
} from 'react'
import { useProvider, useBalance as useWagmiBalance } from 'wagmi'
import { accountTruncate } from '../../@utils/wallet'
import { useUserPreferences } from '../UserPreferences'
import { toast } from 'react-toastify'

import { useMarketMetadata } from '../MarketMetadata'
import DeleteAutomationModal from './DeleteAutomationModal'
import useBalance from '../../@hooks/useBalance'

export enum AUTOMATION_MODES {
SIMPLE = 'simple',
ADVANCED = 'advanced'
}

export interface NativeTokenBalance {
symbol: string
balance: string
}
export interface AutomationProviderValue {
autoWallet: Wallet
autoWalletAddress: string
isAutomationEnabled: boolean
balance: UserBalance
nativeBalance: NativeTokenBalance
isLoading: boolean
decryptPercentage: number
hasValidEncryptedWallet: boolean
updateBalance: () => Promise<void>
setIsAutomationEnabled: (isEnabled: boolean) => void
deleteCurrentAutomationWallet: () => void
importAutomationWallet: (encryptedJson: string) => Promise<boolean>
decryptAutomationWallet: (password: string) => Promise<boolean>
}

// Refresh interval for balance retrieve - 20 sec
const refreshInterval = 20000

// Context
const AutomationContext = createContext({} as AutomationProviderValue)

// Provider
function AutomationProvider({ children }) {
const { getApprovedTokenBalances } = useBalance()
const { approvedBaseTokens } = useMarketMetadata()
const { automationWalletJSON, setAutomationWalletJSON } = useUserPreferences()

const [autoWallet, setAutoWallet] = useState<Wallet>()
const [isAutomationEnabled, setIsAutomationEnabled] = useState<boolean>(false)
const [isLoading, setIsLoading] = useState<boolean>(false)
const [autoWalletAddress, setAutoWalletAddress] = useState<string>()
const [decryptPercentage, setDecryptPercentage] = useState<number>()
const [hasValidEncryptedWallet, setHasValidEncryptedWallet] =
useState<boolean>()

const { data: balanceNativeToken } = useWagmiBalance({
address: autoWallet?.address as `0x${string}`
})

const [nativeBalance, setNativeBalance] = useState<NativeTokenBalance>()
const [balance, setBalance] = useState<UserBalance>({})

const [hasDeleteRequest, setHasDeleteRequest] = useState(false)

const wagmiProvider = useProvider()

useEffect(() => {
if (!automationWalletJSON) setAutoWalletAddress(undefined)
else
setAutoWalletAddress(
ethers.utils.getJsonWalletAddress(automationWalletJSON)
)
}, [automationWalletJSON])

useEffect(() => {
if (autoWallet && !isAutomationEnabled) {
toast.info(`Automation disabled`)
return
}

if (autoWallet?.address)
toast.success(
`Successfully enabled automation wallet with address ${accountTruncate(
autoWallet?.address
)}`
)
}, [isAutomationEnabled, autoWallet])

const updateBalance = useCallback(async () => {
if (!autoWallet) return

try {
if (balanceNativeToken)
setNativeBalance({
symbol: balanceNativeToken?.symbol.toLowerCase() || 'ETH',
balance: balanceNativeToken?.formatted
})

if (approvedBaseTokens?.length > 0) {
const newBalance = await getApprovedTokenBalances(autoWallet?.address)
setBalance(newBalance)
} else setBalance(undefined)
} catch (error) {
LoggerInstance.error('[AutomationProvider] Error: ', error.message)
}
}, [
autoWallet,
balanceNativeToken,
approvedBaseTokens,
getApprovedTokenBalances
])

// periodic refresh of automation wallet balance
useEffect(() => {
updateBalance()

const balanceInterval = setInterval(() => updateBalance(), refreshInterval)

return () => {
clearInterval(balanceInterval)
}
}, [updateBalance])

const deleteCurrentAutomationWallet = () => {
setHasDeleteRequest(true)
}

const removeAutomationWalletAndCleanup = () => {
setIsLoading(true)
setIsAutomationEnabled(false)
setAutoWallet(undefined)
setAutoWalletAddress(undefined)
setAutomationWalletJSON(undefined)
setBalance(undefined)
toast.info('The automation wallet was removed from your machine.')
setHasDeleteRequest(false)
setIsLoading(false)
}

useEffect(() => {
setHasValidEncryptedWallet(ethers.utils.isAddress(autoWalletAddress))
}, [autoWalletAddress])

const importAutomationWallet = async (encryptedJson: string) => {
if (
ethers.utils.isAddress(ethers.utils.getJsonWalletAddress(encryptedJson))
) {
setAutomationWalletJSON(encryptedJson)
return true
} else {
toast.error('Could not import Wallet. Invalid address.')
LoggerInstance.error(
'[AutomationProvider] Could not import Wallet. Invalid address.'
)
return false
}
}

const decryptAutomationWallet = useCallback(
async (password: string) => {
try {
setIsLoading(true)
if (!automationWalletJSON)
throw new Error('No JSON to decrypt in local storage.')

LoggerInstance.log(
'[AutomationProvider] Start decrypting wallet from local storage'
)
const wallet = await ethers.Wallet.fromEncryptedJson(
automationWalletJSON,
password,
(percent) => setDecryptPercentage(percent)
)
const connectedWallet = wallet.connect(wagmiProvider)
LoggerInstance.log('[AutomationProvider] Finished decrypting:', {
connectedWallet
})
setAutoWallet(connectedWallet)
toast.success(
`Successfully imported wallet ${connectedWallet.address} for automation.`
)
return true
} catch (e) {
toast.error(
`Could not decrypt the automation wallet. See console for more information.`
)
LoggerInstance.error(e)
return false
} finally {
setIsLoading(false)
}
},
[wagmiProvider, automationWalletJSON]
)

return (
<AutomationContext.Provider
value={{
autoWallet,
autoWalletAddress,
balance,
nativeBalance,
isAutomationEnabled,
isLoading,
decryptPercentage,
hasValidEncryptedWallet,
setIsAutomationEnabled,
updateBalance,
deleteCurrentAutomationWallet,
importAutomationWallet,
decryptAutomationWallet
}}
>
{children}
<DeleteAutomationModal
hasDeleteRequest={hasDeleteRequest}
setHasDeleteRequest={setHasDeleteRequest}
disabled={isLoading}
onDeleteConfirm={() => removeAutomationWalletAndCleanup()}
/>
</AutomationContext.Provider>
)
}

// Helper hook to access the provider values
const useAutomation = (): AutomationProviderValue =>
useContext(AutomationContext)

export { AutomationContext, AutomationProvider, useAutomation }
export default AutomationProvider
21 changes: 21 additions & 0 deletions src/@context/Automation/DeleteAutomationModal.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
.modal {
padding: calc(var(--spacer) / 2) var(--spacer);
}

.modalActions {
display: flex;
justify-content: space-between;
margin: calc(var(--spacer) / 2) 0;
}

.modalCancelBtn {
background-color: var(--brand-alert-red);
}

.modalConfirmBtn {
background-color: var(--brand-alert-green);
}

.modalHighlight {
color: var(--brand-alert-red);
}
52 changes: 52 additions & 0 deletions src/@context/Automation/DeleteAutomationModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import React, { ReactElement } from 'react'
import Modal from '../../components/@shared/atoms/Modal'
import styles from './DeleteAutomationModal.module.css'
import Button from '../../components/@shared/atoms/Button'
import Loader from '../../components/@shared/atoms/Loader'

export default function DeleteAutomationModal({
disabled,
hasDeleteRequest,
setHasDeleteRequest,
onDeleteConfirm
}: {
disabled: boolean
hasDeleteRequest: boolean
setHasDeleteRequest: (hasDeleteRequest: boolean) => void
onDeleteConfirm: () => void
}): ReactElement {
return (
<Modal
title="Automation Wallet"
onToggleModal={() => setHasDeleteRequest(!hasDeleteRequest)}
isOpen={hasDeleteRequest}
className={styles.modal}
>
<div className={styles.modalContent}>
If you delete the wallet you will not be able to access related
offerings from the portal without reimporting. Do you want to continue?
</div>

<div className={styles.modalActions}>
<Button
size="small"
className={styles.modalCancelBtn}
onClick={() => setHasDeleteRequest(false)}
disabled={disabled}
>
Cancel
</Button>
<Button
size="small"
className={styles.modalConfirmBtn}
onClick={() => {
onDeleteConfirm()
}}
disabled={disabled}
>
{disabled ? <Loader message={`Loading...`} /> : `Confirm`}
</Button>
</div>
</Modal>
)
}
Loading

1 comment on commit 249d1a7

@vercel
Copy link

@vercel vercel bot commented on 249d1a7 Oct 18, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.