diff --git a/apps/autonolas-registry/common-util/Contracts/index.jsx b/apps/autonolas-registry/common-util/Contracts/index.jsx
index 24eac181..e3fbbd77 100644
--- a/apps/autonolas-registry/common-util/Contracts/index.jsx
+++ b/apps/autonolas-registry/common-util/Contracts/index.jsx
@@ -1,9 +1,10 @@
import { ethers } from 'ethers';
+import { mainnet } from 'viem/chains';
import Web3 from 'web3';
import { isL1Network } from '@autonolas/frontend-library';
-import { TOKENOMICS } from 'libs/util-contracts/src/lib/abiAndAddresses/tokenomics';
+import { DISPENSER, TOKENOMICS } from 'libs/util-contracts/src/lib/abiAndAddresses';
import {
AGENT_REGISTRY_CONTRACT,
@@ -218,3 +219,13 @@ export const getTokenomicsEthersContract = (address) => {
const contract = new ethers.Contract(address, TOKENOMICS.abi, provider);
return contract;
};
+
+/**
+ * @returns dispenser contract
+ */
+export const getDispenserContract = () => {
+ const abi = DISPENSER.abi;
+ const address = DISPENSER.addresses[mainnet.id];
+ const contract = getContract(abi, address);
+ return contract;
+};
diff --git a/apps/autonolas-registry/common-util/Details/DetailsSubInfo/RewardsSection.tsx b/apps/autonolas-registry/common-util/Details/DetailsSubInfo/RewardsSection.tsx
index 41d8221f..497f22ab 100644
--- a/apps/autonolas-registry/common-util/Details/DetailsSubInfo/RewardsSection.tsx
+++ b/apps/autonolas-registry/common-util/Details/DetailsSubInfo/RewardsSection.tsx
@@ -1,43 +1,89 @@
-import { Col, Flex, Row } from 'antd';
-import { FC, useEffect, useState } from 'react';
+import { Button, Flex, Table, Typography } from 'antd';
+import { ColumnsType } from 'antd/es/table';
+import { FC, useCallback, useEffect, useMemo, useState } from 'react';
import { Address } from 'viem';
import { getPendingIncentives, useClaimableIncentives } from 'libs/common-contract-functions/src';
import { UNICODE_SYMBOLS } from 'libs/util-constants/src/lib/symbols';
import { TOKENOMICS } from 'libs/util-contracts/src';
+import { notifyError, notifySuccess } from 'libs/util-functions/src';
import { getEthersProviderForEthereum, getTokenomicsEthersContract } from 'common-util/Contracts';
+import { claimOwnerIncentivesRequest } from 'common-util/functions/requests';
+import { useHelpers } from 'common-util/hooks';
-import { RewardsStatistic } from '../styles';
-import { useTokenomicsUnitType } from './hooks';
+import { getTokenomicsUnitType } from './utils';
-type RewardsColumnProps = { title: string; statistic: null | string; loading?: boolean };
-
-const RewardColumn = ({ title, statistic, loading }: RewardsColumnProps) => (
-
-
-
-);
+const { Text } = Typography;
type RewardsSectionProps = {
ownerAddress: Address;
+ isOwner: boolean;
id: string;
type: string;
dataTestId: string;
};
-export const RewardsSection: FC = ({ ownerAddress, id, type, dataTestId }) => {
+type DataSourceItem = {
+ id: 'claimable' | 'pending';
+ label: string;
+ donations: string;
+ topUps: string;
+};
+const getColumns = (
+ canClaim: boolean,
+ handleClaim: () => void,
+ isClaimLoading: boolean,
+): ColumnsType => [
+ {
+ title: 'Type',
+ key: 'type',
+ dataIndex: 'label',
+ render: (label, { id }) => (
+
+ {label}
+ {id === 'claimable' && canClaim && (
+
+ )}
+
+ ),
+ width: '40%',
+ },
+ {
+ title: 'Donations',
+ key: 'donations',
+ dataIndex: 'donations',
+ render: (donations) => {`${donations} ETH`},
+ },
+ {
+ title: 'Top Ups',
+ key: 'topUps',
+ dataIndex: 'topUps',
+ render: (topUps) => {`${topUps} OLAS`},
+ },
+];
+
+export const RewardsSection: FC = ({ ownerAddress, isOwner, id, type, dataTestId }) => {
+ const { account } = useHelpers();
+
+ const [isClaimLoading, setIsClaimLoading] = useState(false);
const [isPendingIncentivesLoading, setIsPendingIncentivesLoading] = useState(true);
const [pendingIncentives, setPendingIncentives] = useState<{
- reward: string;
- topUp: string;
+ pendingReward: string;
+ pendingTopUp: string;
} | null>(null);
- const tokenomicsUnitType = useTokenomicsUnitType(type);
+ const tokenomicsUnitType = getTokenomicsUnitType(type);
+
const {
- isFetching,
reward: claimableReward,
- topUp: claimableTopup,
+ rewardWei: claimableRewardWei,
+ topUp: claimableTopUp,
+ topUpWei: claimableTopUpWei,
+ isFetching: isClaimableIncentivesLoading,
+ refetch,
} = useClaimableIncentives(
TOKENOMICS.addresses[1],
TOKENOMICS.abi,
@@ -46,52 +92,99 @@ export const RewardsSection: FC = ({ ownerAddress, id, type
tokenomicsUnitType,
);
- useEffect(() => {
+ const fetchPendingIncentives = useCallback(async () => {
const provider = getEthersProviderForEthereum();
const contract = getTokenomicsEthersContract(TOKENOMICS.addresses[1]);
- getPendingIncentives(provider, contract, `${tokenomicsUnitType}`, id)
- .then((data) => setPendingIncentives(data))
- .catch((error) => console.error(error))
- .finally(() => setIsPendingIncentivesLoading(false));
- }, [ownerAddress, id, tokenomicsUnitType]);
+ try {
+ const incentives = await getPendingIncentives({
+ provider,
+ contract,
+ unitType: `${tokenomicsUnitType}`,
+ unitId: id,
+ });
+ setPendingIncentives(incentives);
+ } catch (error) {
+ console.error(error);
+ } finally {
+ setIsPendingIncentivesLoading(false);
+ }
+ }, [tokenomicsUnitType, id]);
+
+ useEffect(() => {
+ fetchPendingIncentives();
+ }, [fetchPendingIncentives]);
+
+ const rewards = useMemo(() => {
+ if (isClaimableIncentivesLoading || isPendingIncentivesLoading) return [];
+ if (claimableReward == undefined || claimableTopUp === undefined || pendingIncentives === null)
+ return [];
+
+ return [
+ {
+ id: 'claimable',
+ label: 'Claimable',
+ donations: claimableReward,
+ topUps: claimableTopUp,
+ },
+ {
+ id: 'pending',
+ label: 'Pending, next epoch',
+ donations: pendingIncentives.pendingReward,
+ topUps: pendingIncentives.pendingTopUp,
+ },
+ ] as DataSourceItem[];
+ }, [
+ isClaimableIncentivesLoading,
+ isPendingIncentivesLoading,
+ claimableReward,
+ claimableTopUp,
+ pendingIncentives,
+ ]);
+
+ const canClaim = useMemo(() => {
+ if (!isOwner) return false;
+ if (claimableRewardWei === undefined || claimableTopUpWei === undefined) return false;
+
+ return claimableRewardWei > 0 || claimableTopUpWei > 0;
+ }, [isOwner, claimableRewardWei, claimableTopUpWei]);
+
+ const handleClaim = useCallback(async () => {
+ if (!account) return;
+ if (!canClaim) return;
+
+ try {
+ setIsClaimLoading(true);
+ const params = {
+ account: account as Address,
+ unitIds: [id],
+ unitTypes: [`${tokenomicsUnitType}`],
+ };
+
+ await claimOwnerIncentivesRequest(params);
+ notifySuccess('Rewards claimed');
+ refetch();
+ fetchPendingIncentives();
+ } catch (error) {
+ notifyError();
+ console.error(error);
+ } finally {
+ setIsClaimLoading(false);
+ }
+ }, [account, id, tokenomicsUnitType, canClaim, fetchPendingIncentives, refetch]);
return (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Make donation {UNICODE_SYMBOLS.EXTERNAL_LINK}
-
-
-
+
+
+ Make donation {UNICODE_SYMBOLS.EXTERNAL_LINK}
+
);
};
diff --git a/apps/autonolas-registry/common-util/Details/DetailsSubInfo/index.jsx b/apps/autonolas-registry/common-util/Details/DetailsSubInfo/index.jsx
index 976143d2..93165a14 100644
--- a/apps/autonolas-registry/common-util/Details/DetailsSubInfo/index.jsx
+++ b/apps/autonolas-registry/common-util/Details/DetailsSubInfo/index.jsx
@@ -20,7 +20,7 @@ import { getTokenDetailsRequest } from '../utils';
import { RewardsSection } from './RewardsSection';
import { ServiceStatus } from './ServiceStatus';
import { ViewHashAndCode } from './ViewHashAndCode';
-import { useTokenomicsUnitType } from './hooks';
+import { getTokenomicsUnitType } from './utils';
const navTypesForRewards = [NAV_TYPES.COMPONENT, NAV_TYPES.AGENT];
@@ -56,7 +56,7 @@ export const DetailsSubInfo = ({
const { operatorWhitelistTitle, operatorWhitelistValue, operatorStatusValue } =
useOperatorWhitelistComponent(id);
- const tokenomicsUnitType = useTokenomicsUnitType(type);
+ const tokenomicsUnitType = getTokenomicsUnitType(type);
const viewHashAndCodeButtons = (
) : (
{value}
@@ -231,7 +232,7 @@ export const DetailsSubInfo = ({
);
}),
- [detailsValues, ownerAddress, id, type],
+ [detailsValues, ownerAddress, isOwner, id, type],
);
// get token address for service
diff --git a/apps/autonolas-registry/common-util/Details/DetailsSubInfo/hooks.ts b/apps/autonolas-registry/common-util/Details/DetailsSubInfo/utils.ts
similarity index 82%
rename from apps/autonolas-registry/common-util/Details/DetailsSubInfo/hooks.ts
rename to apps/autonolas-registry/common-util/Details/DetailsSubInfo/utils.ts
index 3b0801d6..1071a62b 100644
--- a/apps/autonolas-registry/common-util/Details/DetailsSubInfo/hooks.ts
+++ b/apps/autonolas-registry/common-util/Details/DetailsSubInfo/utils.ts
@@ -2,7 +2,7 @@ import { TOKENOMICS_UNIT_TYPES } from 'libs/util-constants/src';
import { NAV_TYPES } from '../../../util/constants';
-export const useTokenomicsUnitType = (type?: string) => {
+export const getTokenomicsUnitType = (type?: string) => {
if (type === NAV_TYPES.COMPONENT) return TOKENOMICS_UNIT_TYPES.COMPONENT;
if (type === NAV_TYPES.AGENT) return TOKENOMICS_UNIT_TYPES.AGENT;
return;
diff --git a/apps/autonolas-registry/common-util/Details/index.tsx b/apps/autonolas-registry/common-util/Details/index.tsx
index 6df25f94..d1bbbe7d 100644
--- a/apps/autonolas-registry/common-util/Details/index.tsx
+++ b/apps/autonolas-registry/common-util/Details/index.tsx
@@ -2,6 +2,7 @@ import { Button, Col, Row, Typography } from 'antd';
import capitalize from 'lodash/capitalize';
import get from 'lodash/get';
import { FC, useCallback, useState } from 'react';
+import { Address } from 'viem';
import { GenericObject, Loader, NA } from '@autonolas/frontend-library';
@@ -20,9 +21,9 @@ const { Text } = Typography;
type DetailsProps = {
id: string;
type: NavTypesValues;
- getDetails: (id: string) => Promise; // TODO: Define the return type
- getTokenUri?: (id: string) => Promise;
- getOwner?: (id: string) => Promise;
+ getDetails: (id: string) => Promise<{ unitHash: Address; dependencies: string[] }>;
+ getTokenUri: (id: string) => Promise;
+ getOwner: (id: string) => Promise;
handleUpdate?: () => void;
handleHashUpdate?: () => void;
navigateToDependency?: (id: string, type: NavTypesValues) => void;
diff --git a/apps/autonolas-registry/common-util/Details/styles.jsx b/apps/autonolas-registry/common-util/Details/styles.tsx
similarity index 75%
rename from apps/autonolas-registry/common-util/Details/styles.jsx
rename to apps/autonolas-registry/common-util/Details/styles.tsx
index 6345cd85..8fd27d73 100644
--- a/apps/autonolas-registry/common-util/Details/styles.jsx
+++ b/apps/autonolas-registry/common-util/Details/styles.tsx
@@ -1,4 +1,4 @@
-import { Image, Statistic, Typography } from 'antd';
+import { Image, Typography } from 'antd';
import styled from 'styled-components';
import { COLOR } from 'libs/ui-theme/src';
@@ -74,25 +74,4 @@ export const SectionContainer = styled.div`
.ant-form-item-label > label {
left: -4px;
}
-`;
-
-export const RewardsStatistic = styled((props) => )`
- .ant-statistic-title {
- font-size: 14px;
- line-height: 20px;
- }
- .ant-statistic-content {
- font-size: 24px;
- font-style: normal;
- font-weight: 700;
- line-height: 32px;
- letter-spacing: -0.72px;
- }
- .ant-skeleton-title {
- margin: 0;
- max-width: 200px;
- }
- .ant-statistic-skeleton {
- padding-top: 12px;
- }
-`;
+`;
\ No newline at end of file
diff --git a/apps/autonolas-registry/common-util/Details/useDetails.jsx b/apps/autonolas-registry/common-util/Details/useDetails.ts
similarity index 71%
rename from apps/autonolas-registry/common-util/Details/useDetails.jsx
rename to apps/autonolas-registry/common-util/Details/useDetails.ts
index 7b85c64f..a81d81bc 100644
--- a/apps/autonolas-registry/common-util/Details/useDetails.jsx
+++ b/apps/autonolas-registry/common-util/Details/useDetails.ts
@@ -1,18 +1,34 @@
import { useCallback, useEffect, useMemo, useState } from 'react';
+import { Address } from 'viem';
-import { NA, areAddressesEqual, notifyError } from '@autonolas/frontend-library';
+import { NA } from 'libs/util-constants/src/lib/symbols';
+import { areAddressesEqual, notifyError } from 'libs/util-functions/src';
+
+import { NavTypesValues } from 'util/constants';
import { useHelpers } from '../hooks';
import { useSvmConnectivity } from '../hooks/useSvmConnectivity';
-export const useDetails = ({ id, type, getDetails, getOwner, getTokenUri }) => {
+export const useDetails = ({
+ id,
+ type,
+ getDetails,
+ getOwner,
+ getTokenUri,
+}: {
+ id: string;
+ type: NavTypesValues;
+ getDetails: (id: string) => Promise<{ unitHash: Address; dependencies: string[] }>;
+ getTokenUri: (id: string) => Promise;
+ getOwner: (id: string) => Promise;
+}) => {
const { account, chainId, isSvm } = useHelpers();
const { walletPublicKey } = useSvmConnectivity();
const [isLoading, setIsLoading] = useState(true);
const [info, setInfo] = useState({});
const [ownerAddress, setDetailsOwner] = useState(NA);
- const [tokenUri, setTokenUri] = useState(null);
+ const [tokenUri, setTokenUri] = useState(null);
// fetch details such as service details, owner of agent/component/service,
// token uri
@@ -57,13 +73,13 @@ export const useDetails = ({ id, type, getDetails, getOwner, getTokenUri }) => {
const isOwner = useMemo(() => {
if (isSvm) {
if (walletPublicKey && ownerAddress) {
- return areAddressesEqual(walletPublicKey, ownerAddress);
+ return areAddressesEqual(walletPublicKey.toString(), ownerAddress);
}
return false;
}
if (account && ownerAddress) {
- return areAddressesEqual(account, ownerAddress);
+ return areAddressesEqual(account.toString(), ownerAddress);
}
return false;
diff --git a/apps/autonolas-registry/common-util/Details/utils.jsx b/apps/autonolas-registry/common-util/Details/utils.ts
similarity index 75%
rename from apps/autonolas-registry/common-util/Details/utils.jsx
rename to apps/autonolas-registry/common-util/Details/utils.ts
index 7d2c7ca3..d42823ab 100644
--- a/apps/autonolas-registry/common-util/Details/utils.jsx
+++ b/apps/autonolas-registry/common-util/Details/utils.ts
@@ -1,14 +1,16 @@
+import { Address } from 'viem';
+
import { getOperatorWhitelistContract, getServiceRegistryTokenUtilityContract } from '../Contracts';
import { sendTransaction } from '../functions';
-export const getTokenDetailsRequest = async (serviceId) => {
+export const getTokenDetailsRequest = async (serviceId: string) => {
const contract = getServiceRegistryTokenUtilityContract();
const deposit = await contract.methods.mapServiceIdTokenDeposit(serviceId).call();
return deposit;
};
/* ----- operator whitelist functions ----- */
-export const checkIfServiceRequiresWhitelisting = async (serviceId) => {
+export const checkIfServiceRequiresWhitelisting = async (serviceId: string) => {
const contract = getOperatorWhitelistContract();
// if true: it is whitelisted by default
// else we can whitelist using the input field
@@ -16,13 +18,21 @@ export const checkIfServiceRequiresWhitelisting = async (serviceId) => {
return response;
};
-export const checkIfServiceIsWhitelisted = async (serviceId, operatorAddress) => {
+export const checkIfServiceIsWhitelisted = async (serviceId: string, operatorAddress: Address) => {
const contract = getOperatorWhitelistContract();
const response = await contract.methods.isOperatorWhitelisted(serviceId, operatorAddress).call();
return response;
};
-export const setOperatorsCheckRequest = async ({ account, serviceId, isChecked }) => {
+export const setOperatorsCheckRequest = async ({
+ account,
+ serviceId,
+ isChecked,
+}: {
+ account: Address;
+ serviceId: string;
+ isChecked: boolean;
+}) => {
const contract = getOperatorWhitelistContract();
const fn = contract.methods.setOperatorsCheck(serviceId, isChecked).send({ from: account });
const response = await sendTransaction(fn, account);
@@ -34,6 +44,11 @@ export const setOperatorsStatusesRequest = async ({
serviceId,
operatorAddresses,
operatorStatuses,
+}: {
+ account: Address;
+ serviceId: string;
+ operatorAddresses: Address[];
+ operatorStatuses: boolean[];
}) => {
const contract = getOperatorWhitelistContract();
const fn = contract.methods
diff --git a/apps/autonolas-registry/common-util/Login/config.tsx b/apps/autonolas-registry/common-util/Login/config.tsx
index 90876702..3990910e 100644
--- a/apps/autonolas-registry/common-util/Login/config.tsx
+++ b/apps/autonolas-registry/common-util/Login/config.tsx
@@ -83,7 +83,7 @@ export const EVM_SUPPORTED_CHAINS = SUPPORTED_CHAINS.map((chain) => {
};
});
-type SolanaChain = {
+export type SolanaChain = {
id: number | null;
networkDisplayName: string;
networkName: string;
diff --git a/apps/autonolas-registry/common-util/functions/index.jsx b/apps/autonolas-registry/common-util/functions/index.ts
similarity index 71%
rename from apps/autonolas-registry/common-util/functions/index.jsx
rename to apps/autonolas-registry/common-util/functions/index.ts
index dd28e049..8a2a1a34 100644
--- a/apps/autonolas-registry/common-util/functions/index.jsx
+++ b/apps/autonolas-registry/common-util/functions/index.ts
@@ -1,6 +1,9 @@
import { PublicKey } from '@solana/web3.js';
-import { ethers } from 'ethers';
+import { RuleObject } from 'antd/es/form';
+import { StoreValue } from 'antd/es/form/interface';
+import { Contract, FallbackProvider, JsonRpcProvider, ethers } from 'ethers';
import { isString } from 'lodash';
+import { Address } from 'viem';
import {
getChainIdOrDefaultToMainnet as getChainIdOrDefaultToMainnetFn,
@@ -8,17 +11,23 @@ import {
isValidAddress,
notifyError,
notifyWarning,
- sendTransaction as sendTransactionFn,
} from '@autonolas/frontend-library';
+import { sendTransaction as sendTransactionFn } from 'libs/util-functions/src';
+import { RpcUrl } from 'libs/util-functions/src/lib/sendTransaction/types';
+
import { VM_TYPE } from '../../util/constants';
import { RPC_URLS } from '../Contracts';
import { SUPPORTED_CHAINS } from '../Login';
-import { EVM_SUPPORTED_CHAINS, SVM_SUPPORTED_CHAINS } from '../Login/config';
+import { EVM_SUPPORTED_CHAINS, SVM_SUPPORTED_CHAINS, SolanaChain } from '../Login/config';
-export const getModalProvider = () => window?.MODAL_PROVIDER;
+// TODO: provide types for MODAL_PROVIDER
+// eslint-disable-next-line @typescript-eslint/no-explicit-any
+export const getModalProvider = () => (window as any)?.MODAL_PROVIDER;
-export const getWindowEthereum = () => window?.ethereum;
+// TODO: provide types for ethereum
+// eslint-disable-next-line @typescript-eslint/no-explicit-any
+export const getWindowEthereum = () => (window as any)?.ethereum;
export const getChainId = (chainId = null) => {
if (chainId) return chainId;
@@ -37,7 +46,7 @@ export const getChainId = (chainId = null) => {
export const getProvider = () => {
const defaultChainId = getChainId();
- const rpcUrl = RPC_URLS[defaultChainId];
+ const rpcUrl = typeof defaultChainId === 'number' ? (RPC_URLS as RpcUrl)[defaultChainId] : null;
if (!rpcUrl) {
throw new Error(`No RPC URL found for chainId: ${defaultChainId}`);
@@ -83,9 +92,10 @@ export const getEthersProvider = () => {
return new ethers.FallbackProvider([provider]);
};
-export const getIsValidChainId = (chainId) => getIsValidChainIdFn(SUPPORTED_CHAINS, chainId);
+export const getIsValidChainId = (chainId: string | number) =>
+ getIsValidChainIdFn(SUPPORTED_CHAINS, chainId);
-export const getChainIdOrDefaultToMainnet = (chainId) => {
+export const getChainIdOrDefaultToMainnet = (chainId: string | number) => {
const x = getChainIdOrDefaultToMainnetFn(SUPPORTED_CHAINS, chainId);
return x;
};
@@ -98,7 +108,11 @@ export const getChainIdOrDefaultToMainnet = (chainId) => {
* @param {object} builderIns - The object to check.
* @returns {boolean} - True if the object is a MethodsBuilder object, false otherwise.
*/
-const isMethodsBuilderInstance = (builderIns, registryAddress) => {
+const isMethodsBuilderInstance = (
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ builderIns: object & { _args?: any }, // TODO: provide better type
+ registryAddress: Address,
+) => {
if (typeof builderIns !== 'object' || builderIns === null) {
throw new Error('sendTransaction: Input must be an object.');
}
@@ -129,9 +143,13 @@ const isMethodsBuilderInstance = (builderIns, registryAddress) => {
* @param {string} extra.registryAddress - The address of the registry contract.
*
*/
-export const sendTransaction = (method, account, extra) => {
+export const sendTransaction = (
+ method: Contract,
+ account: Address,
+ extra?: { vmType: string; registryAddress: Address },
+) => {
const { vmType, registryAddress } = extra || {};
- if (vmType === VM_TYPE.SVM) {
+ if (vmType === VM_TYPE.SVM && registryAddress) {
// Check if something resembling an SVM method is being passed
if (!isMethodsBuilderInstance(method, registryAddress)) {
notifyError('Invalid method object');
@@ -142,12 +160,12 @@ export const sendTransaction = (method, account, extra) => {
return sendTransactionFn(method, account, {
supportedChains: SUPPORTED_CHAINS,
- rpcUrls: RPC_URLS,
+ rpcUrls: RPC_URLS as RpcUrl,
});
};
export const addressValidator = () => ({
- validator(_, value) {
+ validator(_: RuleObject, value: StoreValue) {
return isValidAddress(value)
? Promise.resolve()
: Promise.reject(new Error('Please enter valid addresses.'));
@@ -155,7 +173,10 @@ export const addressValidator = () => ({
});
// check if the provider is gnosis safe
-export const checkIfGnosisSafe = async (account, provider) => {
+export const checkIfGnosisSafe = async (
+ account: Address,
+ provider: JsonRpcProvider | FallbackProvider,
+) => {
try {
if (provider && provider.getCode) {
// TODO: getCode has some issues and throws error in console
@@ -166,6 +187,7 @@ export const checkIfGnosisSafe = async (account, provider) => {
console.error(error);
return false;
}
+ return false;
};
/**
@@ -174,12 +196,12 @@ export const checkIfGnosisSafe = async (account, provider) => {
* but now all networks have service manager token. Hence, this function
* defaults to true BUT can be overridden for specific networks in the future.
*/
-export const doesNetworkHaveValidServiceManagerTokenFn = (chainId) => !!chainId;
+export const doesNetworkHaveValidServiceManagerTokenFn = (chainId: number) => !!chainId;
-const doesPathIncludesComponents = (path) => !!path?.includes('components');
-const doesPathIncludesAgents = (path) => !!path?.includes('agents');
-export const doesPathIncludesServices = (path) => !!path?.includes('services');
-export const doesPathIncludesComponentsOrAgents = (path) => {
+const doesPathIncludesComponents = (path: string) => !!path?.includes('components');
+const doesPathIncludesAgents = (path: string) => !!path?.includes('agents');
+export const doesPathIncludesServices = (path: string) => !!path?.includes('services');
+export const doesPathIncludesComponentsOrAgents = (path: string) => {
if (!path) return false;
return doesPathIncludesComponents(path) || doesPathIncludesAgents(path);
};
@@ -189,15 +211,16 @@ export const notifyWrongNetwork = () => {
};
// functions for solana
-export const isPageWithSolana = (path) => {
+export const isPageWithSolana = (path: string) => {
if (!path) return false;
if (!isString(path)) return false;
- const checkPath = (chain) => path.toLowerCase().includes(chain.networkName.toLowerCase());
+ const checkPath = (chain: SolanaChain) =>
+ path.toLowerCase().includes(chain.networkName.toLowerCase());
return SVM_SUPPORTED_CHAINS.some(checkPath);
};
-export const isValidSolanaPublicKey = (publicKey) => {
+export const isValidSolanaPublicKey = (publicKey: PublicKey) => {
try {
const isValid = PublicKey.isOnCurve(publicKey);
return isValid;
diff --git a/apps/autonolas-registry/common-util/functions/requests.jsx b/apps/autonolas-registry/common-util/functions/requests.ts
similarity index 53%
rename from apps/autonolas-registry/common-util/functions/requests.jsx
rename to apps/autonolas-registry/common-util/functions/requests.ts
index fa2009ce..9e3c2a6a 100644
--- a/apps/autonolas-registry/common-util/functions/requests.jsx
+++ b/apps/autonolas-registry/common-util/functions/requests.ts
@@ -1,7 +1,11 @@
-import { notifyError } from '@autonolas/frontend-library';
+import { Address } from 'viem';
+
+import { getEstimatedGasLimit, notifyError } from 'libs/util-functions/src';
+
+import { sendTransaction } from 'common-util/functions';
import { DEFAULT_SERVICE_CREATION_ETH_TOKEN_ZEROS } from '../../util/constants';
-import { getServiceOwnerMultisigContract } from '../Contracts';
+import { getDispenserContract, getServiceOwnerMultisigContract } from '../Contracts';
import { checkIfGnosisSafe, getEthersProvider } from './index';
const FALLBACK_HANDLER_STORAGE_SLOT =
@@ -12,7 +16,7 @@ const FALLBACK_HANDLER_STORAGE_SLOT =
* BE code: https://github.com/valory-xyz/autonolas-registries/pull/54#discussion_r1031510182
* @returns {Promise} true if the owner address can mint
*/
-export const checkIfERC721Receive = async (account, ownerAddress) => {
+export const checkIfERC721Receive = async (account: Address, ownerAddress: Address) => {
const provider = getEthersProvider();
const isSafe = await checkIfGnosisSafe(account, provider);
@@ -23,6 +27,9 @@ export const checkIfERC721Receive = async (account, ownerAddress) => {
const owners = await contract.methods.getOwners().call();
if (Number(threshold) > 0 && owners.length > 0) {
+ // TODO: check and fix error: Property 'getStorageAt' does not exist on type 'JsonRpcProvider | FallbackProvider'.
+ // Did you mean 'getStorage'?
+ // @ts-expect-error next-line
const contents = await provider.getStorageAt(account, FALLBACK_HANDLER_STORAGE_SLOT);
const isInvalidContent =
@@ -43,3 +50,26 @@ export const checkIfERC721Receive = async (account, ownerAddress) => {
return true;
};
+
+export const claimOwnerIncentivesRequest = async ({
+ account,
+ unitTypes,
+ unitIds,
+}: {
+ account: Address;
+ unitTypes: string[];
+ unitIds: string[];
+}) => {
+ const contract = getDispenserContract();
+ try {
+ const claimFn = contract.methods.claimOwnerIncentives(unitTypes, unitIds);
+ const estimatedGas = await getEstimatedGasLimit(claimFn, account);
+ const fn = claimFn.send({ from: account, gasLimit: estimatedGas });
+
+ const response = await sendTransaction(fn, account);
+ return response?.transactionHash;
+ } catch (error) {
+ window.console.log('Error occurred on claiming owner incentives');
+ throw error;
+ }
+};
diff --git a/apps/autonolas-registry/components/ListServices/index.jsx b/apps/autonolas-registry/components/ListServices/index.jsx
index 2322b875..07c09f42 100644
--- a/apps/autonolas-registry/components/ListServices/index.jsx
+++ b/apps/autonolas-registry/components/ListServices/index.jsx
@@ -1,25 +1,18 @@
-import { useState, useEffect } from 'react';
import { Tabs } from 'antd';
import { useRouter } from 'next/router';
+import { useEffect, useState } from 'react';
+
import { notifyError } from '@autonolas/frontend-library';
-import { NAV_TYPES } from '../../util/constants';
-import {
- ListTable,
- useExtraTabContent,
- isMyTab,
-} from '../../common-util/List/ListTable';
import { getMyListOnPagination } from '../../common-util/ContractUtils/myList';
+import { ListTable, isMyTab, useExtraTabContent } from '../../common-util/List/ListTable';
import { useHelpers } from '../../common-util/hooks';
-import {
- useAllServices,
- useMyServices,
- useSearchServices,
-} from './hooks/useServicesList';
+import { NAV_TYPES } from '../../util/constants';
+import { useAllServices, useMyServices, useSearchServices } from './hooks/useServicesList';
import { useServiceInfo } from './hooks/useSvmService';
import {
- getServices,
getFilteredServices,
+ getServices,
getTotalForAllServices,
getTotalForMyServices,
} from './utils';
@@ -29,9 +22,7 @@ const MY_SERVICES = 'my-services';
const ListServices = () => {
const router = useRouter();
- const [currentTab, setCurrentTab] = useState(
- isMyTab(router) ? MY_SERVICES : ALL_SERVICES,
- );
+ const [currentTab, setCurrentTab] = useState(isMyTab(router) ? MY_SERVICES : ALL_SERVICES);
const { account, links, isSvm, chainId, isMainnet } = useHelpers();
@@ -58,12 +49,8 @@ const ListServices = () => {
const [currentPage, setCurrentPage] = useState(1);
const [list, setList] = useState([]);
- const {
- getTotalForAllSvmServices,
- getTotalForMySvmServices,
- getSvmServices,
- getMySvmServices,
- } = useServiceInfo();
+ const { getTotalForAllSvmServices, getTotalForMySvmServices, getSvmServices, getMySvmServices } =
+ useServiceInfo();
// fetch total (All services & My services)
useEffect(() => {
@@ -72,9 +59,7 @@ const ListServices = () => {
let totalTemp = null;
if (currentTab === ALL_SERVICES) {
- totalTemp = isSvm
- ? await getTotalForAllSvmServices()
- : await getTotalForAllServices();
+ totalTemp = isSvm ? await getTotalForAllSvmServices() : await getTotalForAllServices();
} else if (currentTab === MY_SERVICES && account) {
totalTemp = isSvm
? await getTotalForMySvmServices(account)
@@ -207,15 +192,7 @@ const ListServices = () => {
setIsLoading(false);
}
})();
- }, [
- account,
- searchValue,
- currentTab,
- currentPage,
- getServicesBySearch,
- isMainnet,
- chainId,
- ]);
+ }, [account, searchValue, currentTab, currentPage, getServicesBySearch, isMainnet, chainId]);
const tableCommonProps = {
type: NAV_TYPES.SERVICE,
@@ -225,16 +202,13 @@ const ListServices = () => {
setCurrentPage,
onViewClick,
searchValue,
- onUpdateClick: (serviceId) =>
- router.push(`${links.UPDATE_SERVICE}/${serviceId}`),
+ onUpdateClick: (serviceId) => router.push(`${links.UPDATE_SERVICE}/${serviceId}`),
};
const getMyServiceList = () => {
if (isMainnet) return list;
- return searchValue
- ? list
- : getMyListOnPagination({ total, nextPage: currentPage, list });
+ return searchValue ? list : getMyListOnPagination({ total, nextPage: currentPage, list });
};
return (
@@ -265,11 +239,7 @@ const ListServices = () => {
label: 'All',
disabled: isLoading,
children: (
-
+
),
},
{
diff --git a/apps/autonolas-registry/tests/components/ListAgents/details.test.jsx b/apps/autonolas-registry/tests/components/ListAgents/details.test.jsx
index 70bd07eb..8e91c154 100644
--- a/apps/autonolas-registry/tests/components/ListAgents/details.test.jsx
+++ b/apps/autonolas-registry/tests/components/ListAgents/details.test.jsx
@@ -37,11 +37,13 @@ jest.mock('wagmi', () => ({
jest.mock('libs/common-contract-functions/src', () => ({
useClaimableIncentives: jest.fn().mockReturnValue({
reward: '0.85',
+ rewardWei: 850000000000000000,
topUp: '222,777.30',
+ topUpWei: 222777300000000000000000000,
}),
getPendingIncentives: jest.fn().mockResolvedValue({
- reward: '0.95555',
- topUp: '200,500.65',
+ pendingReward: '0.95555',
+ pendingTopUp: '200,500.65',
}),
}));
@@ -136,15 +138,15 @@ describe('listAgents/details.jsx', () => {
});
it('should display rewards section', async () => {
- const { getByText } = render(wrapProvider());
+ const { getByText, queryByRole } = render(wrapProvider());
await waitFor(() => {
- expect(getByText('Claimable Reward')).toBeInTheDocument();
- expect(getByText('Claimable Top Up')).toBeInTheDocument();
+ expect(getByText('Claimable')).toBeInTheDocument();
expect(getByText('0.85 ETH')).toBeInTheDocument();
expect(getByText('222,777.30 OLAS')).toBeInTheDocument();
+ const claimButton = queryByRole('button', { name: 'Claim' });
+ expect(claimButton).toBeInTheDocument();
- expect(getByText('Pending Reward')).toBeInTheDocument();
- expect(getByText('Pending Top Up')).toBeInTheDocument();
+ expect(getByText('Pending, next epoch')).toBeInTheDocument();
expect(getByText('0.95555 ETH')).toBeInTheDocument();
expect(getByText('200,500.65 OLAS')).toBeInTheDocument();
});
diff --git a/apps/autonolas-registry/tests/components/ListComponents/details.test.jsx b/apps/autonolas-registry/tests/components/ListComponents/details.test.jsx
index 19c264a7..b076037d 100644
--- a/apps/autonolas-registry/tests/components/ListComponents/details.test.jsx
+++ b/apps/autonolas-registry/tests/components/ListComponents/details.test.jsx
@@ -38,11 +38,13 @@ jest.mock('wagmi', () => ({
jest.mock('libs/common-contract-functions/src', () => ({
useClaimableIncentives: jest.fn().mockReturnValue({
reward: '0.25',
+ rewardWei: 250000000000000000,
topUp: '111,555.70',
+ topUpWei: 111555700000000000000000000,
}),
getPendingIncentives: jest.fn().mockResolvedValue({
- reward: '0.5',
- topUp: '100,800.25',
+ pendingReward: '0.5',
+ pendingTopUp: '100,800.25',
}),
}));
@@ -128,15 +130,15 @@ describe('listComponents/details.jsx', () => {
});
it('should display rewards section', async () => {
- const { getByText } = render(wrapProvider());
+ const { getByText, queryByRole } = render(wrapProvider());
await waitFor(() => {
- expect(getByText('Claimable Reward')).toBeInTheDocument();
- expect(getByText('Claimable Top Up')).toBeInTheDocument();
+ expect(getByText('Claimable')).toBeInTheDocument();
expect(getByText('0.25 ETH')).toBeInTheDocument();
expect(getByText('111,555.70 OLAS')).toBeInTheDocument();
+ const claimButton = queryByRole('button', { name: 'Claim' });
+ expect(claimButton).toBeInTheDocument();
- expect(getByText('Pending Reward')).toBeInTheDocument();
- expect(getByText('Pending Top Up')).toBeInTheDocument();
+ expect(getByText('Pending, next epoch')).toBeInTheDocument();
expect(getByText('0.5 ETH')).toBeInTheDocument();
expect(getByText('100,800.25 OLAS')).toBeInTheDocument();
});
diff --git a/apps/govern/common-util/functions/requests.ts b/apps/govern/common-util/functions/requests.ts
index f4d3de8b..b673fb4d 100644
--- a/apps/govern/common-util/functions/requests.ts
+++ b/apps/govern/common-util/functions/requests.ts
@@ -1,20 +1,28 @@
import { readContract, readContracts } from '@wagmi/core';
import { ethers } from 'ethers';
-import { AbiFunction, TransactionReceipt, parseUnits } from 'viem';
+import { Abi, AbiFunction, parseUnits } from 'viem';
import { Address } from 'viem';
import { mainnet } from 'viem/chains';
-import { sendTransaction } from '@autonolas/frontend-library';
-
-import { STAKING_FACTORY, VE_OLAS } from 'libs/util-contracts/src/lib/abiAndAddresses';
-import { getEstimatedGasLimit } from 'libs/util-functions/src';
+import {
+ SERVICE_REGISTRY,
+ STAKING_FACTORY,
+ VE_OLAS,
+} from 'libs/util-contracts/src/lib/abiAndAddresses';
+import { getEstimatedGasLimit, sendTransaction } from 'libs/util-functions/src';
import { SUPPORTED_CHAINS, wagmiConfig } from 'common-util/config/wagmi';
import { RPC_URLS } from 'common-util/constants/rpcs';
import { getAddressFromBytes32 } from './addresses';
import { getUnixNextWeekStartTimestamp } from './time';
-import { getOlasContract, getVeOlasContract, getVoteWeightingContract } from './web3';
+import {
+ getOlasContract,
+ getTokenomicsContract,
+ getTreasuryContract,
+ getVeOlasContract,
+ getVoteWeightingContract,
+} from './web3';
type VoteForNomineeWeightsParams = {
account: Address | undefined;
@@ -198,7 +206,7 @@ export const createLockRequest = async ({
rpcUrls: RPC_URLS,
});
- return (response as TransactionReceipt)?.transactionHash;
+ return response?.transactionHash;
} catch (error) {
window.console.log('Error occurred on creating lock for veOLAS');
throw error;
@@ -227,7 +235,7 @@ export const updateIncreaseAmount = async ({
rpcUrls: RPC_URLS,
});
- return (response as TransactionReceipt)?.transactionHash;
+ return response?.transactionHash;
} catch (e) {
window.console.log('Error occurred on increasing amount with estimated gas');
throw e;
@@ -259,7 +267,7 @@ export const updateIncreaseUnlockTime = async ({
rpcUrls: RPC_URLS,
});
- return (response as TransactionReceipt)?.transactionHash;
+ return response?.transactionHash;
} catch (error) {
window.console.log('Error occurred on increasing unlock time');
throw error;
@@ -282,9 +290,95 @@ export const withdrawVeolasRequest = async ({ account }: { account: Address }) =
rpcUrls: RPC_URLS,
});
- return (response as TransactionReceipt)?.transactionHash;
+ return response?.transactionHash;
} catch (error) {
window.console.log('Error occurred on withdrawing veOlas');
throw error;
}
};
+
+/**
+ * Start new epoch
+ */
+export const checkpointRequest = async ({ account }: { account: Address }) => {
+ const contract = getTokenomicsContract();
+ try {
+ const checkpointFn = contract.methods.checkpoint();
+ const estimatedGas = await getEstimatedGasLimit(checkpointFn, account);
+ const fn = checkpointFn.send({ from: account, gasLimit: estimatedGas });
+
+ const response = await sendTransaction(fn, account, {
+ supportedChains: SUPPORTED_CHAINS,
+ rpcUrls: RPC_URLS,
+ });
+
+ return response?.transactionHash;
+ } catch (error) {
+ window.console.log('Error occurred on starting new epoch');
+ throw error;
+ }
+};
+
+/**
+ * Check services are eligible for donating
+ */
+export const checkServicesTerminatedOrNotDeployed = async (ids: string[]) => {
+ const invalidServiceIds: string[] = [];
+
+ try {
+ const response = await readContracts(wagmiConfig, {
+ contracts: ids.map((id) => ({
+ abi: SERVICE_REGISTRY.abi as Abi,
+ address: (SERVICE_REGISTRY.addresses as Record)[mainnet.id],
+ chainId: mainnet.id,
+ functionName: 'getService',
+ args: [id],
+ })),
+ });
+
+ response.forEach((service, index) => {
+ const serviceData = service.result as { state: number } | null;
+ if (serviceData && serviceData.state !== 4 && serviceData.state !== 5) {
+ invalidServiceIds.push(ids[index]);
+ }
+ });
+ } catch (error) {
+ window.console.log('Error on checking service status');
+ throw error;
+ }
+
+ return invalidServiceIds;
+};
+
+/**
+ * Donate to services
+ */
+export const depositServiceDonationRequest = async ({
+ account,
+ serviceIds,
+ amounts,
+ totalAmount,
+}: {
+ account: Address;
+ serviceIds: string[];
+ amounts: string[];
+ totalAmount: string;
+}) => {
+ const contract = getTreasuryContract();
+
+ try {
+ const depositFn = contract.methods.depositServiceDonationsETH(serviceIds, amounts);
+ const estimatedGas = await getEstimatedGasLimit(depositFn, account, totalAmount);
+ const fn = depositFn.send({ from: account, value: totalAmount, gasLimit: estimatedGas });
+
+ const response = await sendTransaction(fn, account, {
+ supportedChains: SUPPORTED_CHAINS,
+ rpcUrls: RPC_URLS,
+ });
+
+ return response?.transactionHash;
+ } catch (error) {
+ window.console.log('Error occurred on depositing service donation');
+ throw error;
+ }
+};
diff --git a/apps/govern/common-util/functions/web3.ts b/apps/govern/common-util/functions/web3.ts
index 3362c14a..0640eb5b 100644
--- a/apps/govern/common-util/functions/web3.ts
+++ b/apps/govern/common-util/functions/web3.ts
@@ -2,7 +2,13 @@ import { mainnet } from 'viem/chains';
import Web3 from 'web3';
import { AbiItem } from 'web3-utils';
-import { OLAS, VE_OLAS, VOTE_WEIGHTING } from 'libs/util-contracts/src/lib/abiAndAddresses';
+import {
+ OLAS,
+ TOKENOMICS,
+ TREASURY,
+ VE_OLAS,
+ VOTE_WEIGHTING,
+} from 'libs/util-contracts/src/lib/abiAndAddresses';
import { getChainId, getProvider } from 'common-util/functions/frontend-library';
@@ -46,3 +52,17 @@ export const getVeOlasContract = () => {
const contract = getContract(abi, address);
return contract;
};
+
+export const getTokenomicsContract = () => {
+ const abi = TOKENOMICS.abi as unknown as AbiItem[];
+ const address = TOKENOMICS.addresses[mainnet.id];
+ const contract = getContract(abi, address);
+ return contract;
+};
+
+export const getTreasuryContract = () => {
+ const abi = TREASURY.abi as AbiItem[];
+ const address = TREASURY.addresses[mainnet.id];
+ const contract = getContract(abi, address);
+ return contract;
+};
diff --git a/apps/govern/components/Donate/DonateForm.tsx b/apps/govern/components/Donate/DonateForm.tsx
new file mode 100644
index 00000000..6c1c535f
--- /dev/null
+++ b/apps/govern/components/Donate/DonateForm.tsx
@@ -0,0 +1,132 @@
+import { MinusCircleOutlined, PlusOutlined } from '@ant-design/icons';
+import { Button, Form, Grid, InputNumber, Space, Typography } from 'antd';
+import styled from 'styled-components';
+import { useAccount } from 'wagmi';
+
+const { Text } = Typography;
+const { useBreakpoint } = Grid;
+
+export const DynamicFormContainer = styled.div`
+ max-width: 720px;
+ .ant-input-number {
+ width: 200px;
+ }
+`;
+
+type DonateFormProps = {
+ isLoading: boolean;
+ onSubmit: ({ unitIds, amounts }: { unitIds: number[]; amounts: number[] }) => Promise;
+};
+
+export const DonateForm = ({ isLoading, onSubmit }: DonateFormProps) => {
+ const { address: account } = useAccount();
+ const [form] = Form.useForm();
+
+ const screens = useBreakpoint();
+ const inputStyle = screens.xs ? { width: '140px' } : { width: 'auto' };
+
+ const onFinish = async (values: { units: { unitId: number; amount: number }[] }) => {
+ try {
+ await onSubmit({
+ unitIds: values.units.map((unit) => unit.unitId),
+ amounts: values.units.map((unit) => unit.amount),
+ });
+
+ form.resetFields();
+ } catch (error) {
+ window.console.error(error);
+ }
+ };
+
+ return (
+
+ {
+ if (!units || units?.length === 0) {
+ return Promise.reject(new Error('At least 1 unit is required'));
+ }
+ return Promise.resolve();
+ },
+ },
+ ]}
+ >
+ {(fields, { add, remove }, { errors }) => (
+ <>
+ {fields.map((field) => (
+
+ prevValues.units !== curValues.units}
+ >
+ {() => (
+
+
+
+ )}
+
+
+
+
+
+
+ {fields.length > 1 && remove(field.name)} />}
+
+ ))}
+
+
+
+
+ }>
+ Add row
+
+
+ >
+ )}
+
+
+
+
+
+ {!account && (
+
+ To donate, connect a wallet
+
+ )}
+
+
+
+ );
+};
diff --git a/apps/govern/components/Donate/hooks.ts b/apps/govern/components/Donate/hooks.ts
new file mode 100644
index 00000000..919621fd
--- /dev/null
+++ b/apps/govern/components/Donate/hooks.ts
@@ -0,0 +1,121 @@
+import { ethers } from 'ethers';
+import { useCallback, useMemo } from 'react';
+import { mainnet } from 'viem/chains';
+import { useReadContract } from 'wagmi';
+
+import { TOKENOMICS, TREASURY } from 'libs/util-contracts/src/lib/abiAndAddresses';
+
+const useVeOLASThreshold = () =>
+ useReadContract({
+ address: TOKENOMICS.addresses[mainnet.id],
+ abi: TOKENOMICS.abi,
+ chainId: mainnet.id,
+ functionName: 'veOLASThreshold',
+ query: {
+ select: (data) => ethers.formatEther(`${data}`),
+ },
+ });
+
+const useMinAcceptedETH = () =>
+ useReadContract({
+ address: TREASURY.addresses[mainnet.id],
+ abi: TREASURY.abi,
+ chainId: mainnet.id,
+ functionName: 'minAcceptedETH',
+ });
+
+const useEpochCounter = () =>
+ useReadContract({
+ address: TOKENOMICS.addresses[mainnet.id],
+ abi: TOKENOMICS.abi,
+ chainId: mainnet.id,
+ functionName: 'epochCounter',
+ });
+
+const useEpochTokenomics = (epochCounter: number | undefined) =>
+ useReadContract({
+ address: TOKENOMICS.addresses[mainnet.id],
+ abi: TOKENOMICS.abi,
+ chainId: mainnet.id,
+ functionName: 'mapEpochTokenomics',
+ args: [BigInt(epochCounter || 0)],
+ query: {
+ enabled: epochCounter !== undefined,
+ },
+ });
+
+const useEpochLength = () =>
+ useReadContract({
+ address: TOKENOMICS.addresses[mainnet.id],
+ abi: TOKENOMICS.abi,
+ chainId: mainnet.id,
+ functionName: 'epochLen',
+ });
+
+export const useThresholdData = () => {
+ const {
+ data: veOLASThreshold,
+ isFetching: isVeOLASThresholdFetching,
+ refetch: refetchVeOLASThreshold,
+ } = useVeOLASThreshold();
+ const {
+ data: minAcceptedETH,
+ isFetching: isMinAcceptedETHFetching,
+ refetch: refetchMinAcceptedETH,
+ } = useMinAcceptedETH();
+ const {
+ data: epochCounter,
+ isFetching: isEpochCounterFetching,
+ refetch: refetchEpochCounter,
+ } = useEpochCounter();
+ const {
+ data: prevEpochPoint,
+ isFetching: isPrevEpochPointFetching,
+ refetch: refetchPrevEpochPoint,
+ } = useEpochTokenomics(epochCounter !== undefined ? Number(epochCounter) - 1 : undefined);
+ const {
+ data: epochLength,
+ isFetching: isEpochLengthFetching,
+ refetch: refetchEpochLength,
+ } = useEpochLength();
+
+ const nextEpochEndTime = useMemo(() => {
+ if (prevEpochPoint === undefined) return null;
+ if (epochLength === undefined) return null;
+ return prevEpochPoint.endTime + epochLength;
+ }, [prevEpochPoint, epochLength]);
+
+ const refetchData = useCallback(async () => {
+ const promises = [
+ refetchVeOLASThreshold(),
+ refetchMinAcceptedETH(),
+ refetchEpochCounter(),
+ refetchPrevEpochPoint(),
+ refetchEpochLength(),
+ ];
+
+ return Promise.all(promises);
+ }, [
+ refetchVeOLASThreshold,
+ refetchMinAcceptedETH,
+ refetchEpochCounter,
+ refetchPrevEpochPoint,
+ refetchEpochLength,
+ ]);
+
+ return {
+ veOLASThreshold,
+ minAcceptedETH: minAcceptedETH as bigint | undefined,
+ epochCounter,
+ prevEpochEndTime: prevEpochPoint?.endTime,
+ epochLength,
+ nextEpochEndTime,
+ isDataLoading:
+ isVeOLASThresholdFetching ||
+ isMinAcceptedETHFetching ||
+ isEpochCounterFetching ||
+ isPrevEpochPointFetching ||
+ isEpochLengthFetching,
+ refetchData,
+ };
+};
diff --git a/apps/govern/components/Donate/index.tsx b/apps/govern/components/Donate/index.tsx
new file mode 100644
index 00000000..4269a497
--- /dev/null
+++ b/apps/govern/components/Donate/index.tsx
@@ -0,0 +1,203 @@
+import { Alert, Button, Card, Skeleton, Typography } from 'antd';
+import { ethers } from 'ethers';
+import isNumber from 'lodash/isNumber';
+import Link from 'next/link';
+import { useState } from 'react';
+import { useAccount } from 'wagmi';
+
+import { NA, getFullFormattedDate, notifySuccess } from '@autonolas/frontend-library';
+
+import { notifyError } from 'libs/util-functions/src';
+
+import {
+ checkServicesTerminatedOrNotDeployed,
+ checkpointRequest,
+ depositServiceDonationRequest,
+} from 'common-util/functions';
+
+import { DonateForm } from './DonateForm';
+import { useThresholdData } from './hooks';
+import { DonateContainer, EpochCheckpointRow, EpochStatus } from './styles';
+
+const { Title, Paragraph, Text } = Typography;
+
+const sortUnitIdsAndAmounts = (unitIds: number[], amounts: number[]) => {
+ const sortedUnitIds = [...unitIds].sort((a, b) => a - b);
+ const sortedAmounts = sortedUnitIds.map((e) => amounts[unitIds.indexOf(e)]);
+ return [sortedUnitIds, sortedAmounts];
+};
+
+export const DonatePage = () => {
+ const { address: account } = useAccount();
+
+ const [isDonationLoading, setIsDonationLoading] = useState(false);
+ const [isCheckpointLoading, setIsCheckpointLoading] = useState(false);
+
+ const {
+ isDataLoading,
+ refetchData,
+ veOLASThreshold,
+ minAcceptedETH,
+ epochCounter,
+ prevEpochEndTime,
+ epochLength,
+ nextEpochEndTime,
+ } = useThresholdData();
+
+ const onDepositServiceDonationSubmit = async (values: {
+ unitIds: number[];
+ amounts: number[];
+ }) => {
+ if (!account) return;
+
+ try {
+ setIsDonationLoading(true);
+
+ const [sortedUnitIds, sortedAmounts] = sortUnitIdsAndAmounts(values.unitIds, values.amounts);
+ const serviceIds = sortedUnitIds.map((e) => `${e}`);
+ const invalidServices = await checkServicesTerminatedOrNotDeployed(serviceIds);
+
+ // deposit only if all services are deployed or not terminated
+ if (invalidServices.length > 0) {
+ throw new Error(
+ `Provided service IDs are not deployed or terminated ${invalidServices.join(', ')}`,
+ );
+ } else {
+ const amounts = sortedAmounts.map((e) => ethers.parseUnits(`${e}`, 18).toString());
+ const totalAmount = amounts.reduce(
+ (a, b) => ethers.toBigInt(a) + ethers.toBigInt(b),
+ BigInt(0),
+ );
+
+ if (minAcceptedETH !== undefined && minAcceptedETH > totalAmount) {
+ throw new Error(
+ `At least ${ethers.formatEther(
+ `${minAcceptedETH}`,
+ )} ETH of donations is required to trigger boosts.`,
+ );
+ } else {
+ const params = {
+ account,
+ serviceIds,
+ amounts,
+ totalAmount: totalAmount.toString(),
+ };
+
+ await depositServiceDonationRequest(params);
+ notifySuccess('Deposited service donation successfully');
+ }
+ }
+ } catch (error) {
+ console.error(error);
+ const errorMessage =
+ (error as Error).message || 'Error occurred on depositing service donation';
+ notifyError(errorMessage);
+ throw error;
+ } finally {
+ setIsDonationLoading(false);
+ }
+ };
+
+ const onCheckpoint = async () => {
+ if (!account) return;
+
+ try {
+ setIsCheckpointLoading(true);
+ await checkpointRequest({ account });
+ await refetchData(); // update epoch details after checkpoint
+ notifySuccess('Started new epoch');
+ } catch (error) {
+ console.error(error);
+ notifyError('Error occurred on starting new epoch');
+ } finally {
+ setIsCheckpointLoading(false);
+ }
+ };
+
+ const epochStatusList = [
+ {
+ text: 'Earliest possible expected end time',
+ value: nextEpochEndTime ? getFullFormattedDate(nextEpochEndTime * 1000) : NA,
+ },
+ {
+ text: 'Epoch length',
+ value: isNumber(epochLength) ? `${epochLength / 3600 / 24} days` : NA,
+ },
+ {
+ text: 'Previous epoch end time',
+ value: prevEpochEndTime ? getFullFormattedDate(prevEpochEndTime * 1000) : NA,
+ },
+ {
+ text: 'Epoch counter',
+ value: epochCounter || NA,
+ },
+ ];
+
+ // disable checkpoint button if expected end time is in the future
+ const isExpectedEndTimeInFuture = (nextEpochEndTime || 0) * 1000 > Date.now();
+
+ return (
+
+
+
+ Donate
+
+
+ Show appreciation for the value of an autonomous service by making a donation. The
+ protocol will reward devs who have contributed code for that service.
+
+
+
+ To boost rewards of devs with freshly minted OLAS, you must hold at least
+ {veOLASThreshold || NA}
+ veOLAS. Grab your veOLAS by locking OLAS
+ here. At least
+
+ {minAcceptedETH ? ethers.formatEther(`${minAcceptedETH}`) : NA}
+ ETH
+
+ of donations is required to trigger boosts.
+ >
+ }
+ className="mb-16"
+ />
+
+
+
+
+
+
+ Epoch Status
+
+
+ {epochStatusList.map(({ text, value }, index) => (
+
+ {`${text}:`}
+ {isDataLoading ? (
+
+ ) : (
+ {value}
+ )}
+
+ ))}
+
+
+
+ New epochs must be manually triggered by community members
+
+
+
+ );
+};
diff --git a/apps/govern/components/Donate/styles.tsx b/apps/govern/components/Donate/styles.tsx
new file mode 100644
index 00000000..484d0ba3
--- /dev/null
+++ b/apps/govern/components/Donate/styles.tsx
@@ -0,0 +1,48 @@
+import styled from 'styled-components';
+
+import { MEDIA_QUERY } from 'libs/ui-theme/src';
+
+export const DonateContainer = styled.div`
+ display: flex;
+ gap: 16px;
+
+ .donate-section {
+ width: 720px;
+ }
+ .last-epoch-section {
+ flex: auto;
+ }
+
+ ${MEDIA_QUERY.tabletL} {
+ flex-direction: column;
+ .donate-section {
+ width: 100%;
+ }
+ .last-epoch-section {
+ padding-left: 0;
+ margin-left: 0;
+ border-left: none;
+ }
+ }
+`;
+
+export const EpochStatus = styled.div`
+ display: flex;
+ align-items: center;
+ gap: 0.5rem;
+ margin-bottom: 0.75rem;
+ h5,
+ div {
+ margin: 0;
+ }
+`;
+
+export const EpochCheckpointRow = styled.div`
+ display: flex;
+ flex-direction: column;
+ gap: 4px;
+ margin-top: 1rem;
+ .ant-btn {
+ width: 200px;
+ }
+`;
diff --git a/apps/govern/components/Layout/Menu.tsx b/apps/govern/components/Layout/Menu.tsx
index 6f4cb926..d981bd4a 100644
--- a/apps/govern/components/Layout/Menu.tsx
+++ b/apps/govern/components/Layout/Menu.tsx
@@ -12,6 +12,7 @@ const items: MenuItem[] = [
{ label: 'Staking contracts', key: 'contracts', path: '/contracts' },
{ label: 'Proposals', key: 'proposals', path: '/proposals' },
{ label: 'veOLAS', key: 'veolas', path: '/veolas' },
+ { label: 'Donate', key: 'donate', path: '/donate' },
{ label: 'Docs', key: 'docs', path: '/docs' },
];
diff --git a/apps/govern/components/Proposals/Proposals.spec.tsx b/apps/govern/components/Proposals/index.spec.tsx
similarity index 95%
rename from apps/govern/components/Proposals/Proposals.spec.tsx
rename to apps/govern/components/Proposals/index.spec.tsx
index 63d42e29..591ab2e9 100644
--- a/apps/govern/components/Proposals/Proposals.spec.tsx
+++ b/apps/govern/components/Proposals/index.spec.tsx
@@ -3,7 +3,7 @@ import { render, screen } from '@testing-library/react';
import { UNICODE_SYMBOLS } from 'libs/util-constants/src';
-import { ProposalsPage, proposals } from './Proposals';
+import { ProposalsPage, proposals } from './index';
describe('', () => {
it('should display the page title and description', () => {
diff --git a/apps/govern/components/Proposals/Proposals.tsx b/apps/govern/components/Proposals/index.tsx
similarity index 100%
rename from apps/govern/components/Proposals/Proposals.tsx
rename to apps/govern/components/Proposals/index.tsx
diff --git a/apps/govern/pages/donate.tsx b/apps/govern/pages/donate.tsx
new file mode 100644
index 00000000..a655e295
--- /dev/null
+++ b/apps/govern/pages/donate.tsx
@@ -0,0 +1,3 @@
+import { DonatePage } from 'components/Donate';
+
+export default DonatePage;
diff --git a/apps/govern/pages/proposals.tsx b/apps/govern/pages/proposals.tsx
index 1d63f204..c983755b 100644
--- a/apps/govern/pages/proposals.tsx
+++ b/apps/govern/pages/proposals.tsx
@@ -1,3 +1,3 @@
-import { ProposalsPage } from 'components/Proposals/Proposals';
+import { ProposalsPage } from 'components/Proposals';
export default ProposalsPage;
diff --git a/apps/launch/common-util/functions/web3.ts b/apps/launch/common-util/functions/web3.ts
index a8fc49c8..b44fee87 100644
--- a/apps/launch/common-util/functions/web3.ts
+++ b/apps/launch/common-util/functions/web3.ts
@@ -1,13 +1,14 @@
-import { ContractTransactionReceipt, EventLog } from 'ethers';
+import { BaseContract, EventLog } from 'ethers';
import { Address } from 'viem';
import Web3 from 'web3';
import { AbiItem } from 'web3-utils';
-import { sendTransaction as sendTransactionFn } from '@autonolas/frontend-library';
-
import { RPC_URLS } from 'libs/util-constants/src';
import { STAKING_FACTORY, VOTE_WEIGHTING } from 'libs/util-contracts/src/lib/abiAndAddresses';
-import { getEstimatedGasLimit } from 'libs/util-functions/src';
+import {
+ getEstimatedGasLimit,
+ sendTransaction as sendTransactionFn,
+} from 'libs/util-functions/src';
import { SUPPORTED_CHAINS } from 'common-util/config/wagmi';
import { getChainId, getProvider } from 'common-util/functions/frontend-library';
@@ -82,7 +83,7 @@ export const createStakingContract = async ({
const createFn = contract.methods.createStakingInstance(implementation, initPayload);
const result = await sendTransaction(createFn, account);
- return result as ContractTransactionReceipt & {
+ return result as BaseContract & {
events?: { InstanceCreated: InstanceCreatedEvent };
};
};
diff --git a/apps/tokenomics/components/Layout/index.jsx b/apps/tokenomics/components/Layout/index.jsx
index b8bf8ead..e19f7143 100644
--- a/apps/tokenomics/components/Layout/index.jsx
+++ b/apps/tokenomics/components/Layout/index.jsx
@@ -45,12 +45,14 @@ const Layout = ({ children }) => {
useEffect(() => {
if (router.pathname) {
const name = router.pathname.split('/')[1];
- setSelectedMenu(name || 'veolas');
+ setSelectedMenu(name || 'dev-incentives');
}
}, [router.pathname]);
const handleMenuItemClick = ({ key }) => {
- if (key === 'docs') {
+ if (key === 'donate') {
+ window.open('https://govern.olas.network/donate', '_blank');
+ } else if (key === 'docs') {
window.open('https://docs.autonolas.network/protocol/tokenomics/', '_blank');
} else if (key === 'bonding-products') {
window.open('https://bond.olas.network/bonding-products', '_blank');
@@ -80,8 +82,8 @@ const Layout = ({ children }) => {
selectedKeys={[selectedMenu]}
onClick={handleMenuItemClick}
items={[
- { key: 'donate', label: 'Donate' },
{ key: 'dev-incentives', label: 'Dev Rewards' },
+ { key: 'donate', label: },
{ key: 'bonding-products', label: },
{ key: 'my-bonds', label: },
{
diff --git a/apps/tokenomics/next.config.js b/apps/tokenomics/next.config.js
index d05da172..7603316d 100644
--- a/apps/tokenomics/next.config.js
+++ b/apps/tokenomics/next.config.js
@@ -30,8 +30,13 @@ const nextConfig = {
return [
{
source: '/',
- destination: '/donate',
+ destination: '/dev-incentives',
permanent: false,
+ },
+ {
+ source: '/donate',
+ destination: 'https://govern.olas.network/donate',
+ permanent: true,
},
{
source: '/bonding-products',
diff --git a/libs/common-contract-functions/src/lib/rewards.ts b/libs/common-contract-functions/src/lib/rewards.ts
index 2323e2fc..ea691f2e 100644
--- a/libs/common-contract-functions/src/lib/rewards.ts
+++ b/libs/common-contract-functions/src/lib/rewards.ts
@@ -1,7 +1,5 @@
import { Contract, Provider, ethers } from 'ethers';
-import { TOKENOMICS_UNIT_TYPES } from 'libs/util-constants/src';
-
import { rewardsFormatter } from './utils';
const BIG_INT_0 = BigInt(0);
@@ -23,10 +21,8 @@ const getEpochDetails = async (provider: Provider, contract: Contract) => {
// epoch length
const epochLen: bigint = await contract.epochLen();
- // get the block number and timestamp
- const blockNumber = await provider.getBlockNumber();
-
- const blockTimestamp = (await provider.getBlock(blockNumber))?.timestamp || 0;
+ // get the block timestamp
+ const blockTimestamp = (await provider.getBlock('latest'))?.timestamp || 0;
const timeDiff = ethers.toBigInt(blockTimestamp) - epTokenomics.endTime;
return { timeDiff, epochLen };
@@ -39,7 +35,7 @@ type MapUnitIncentivesRequestArgs = {
unitId: string;
};
-const getMapUnitIncentivesRequest = async ({
+export const getPendingIncentives = async ({
provider,
contract,
unitType,
@@ -47,71 +43,44 @@ const getMapUnitIncentivesRequest = async ({
}: MapUnitIncentivesRequestArgs) => {
const currentEpochCounter = await getEpochCounter(contract);
- // Get the unit points of the last epoch
- const componentInfo: {
- rewardUnitFraction: bigint;
- topUpUnitFraction: bigint;
- sumUnitTopUpsOLAS: bigint;
- } = await contract.getUnitPoint(currentEpochCounter, 0);
- const agentInfo: {
- rewardUnitFraction: bigint;
- topUpUnitFraction: bigint;
- sumUnitTopUpsOLAS: bigint;
- } = await contract.getUnitPoint(currentEpochCounter, 1);
-
const {
pendingRelativeReward,
pendingRelativeTopUp,
lastEpoch,
}: {
- pendingRelativeReward: bigint;
- pendingRelativeTopUp: bigint;
+ pendingRelativeReward: bigint; // Pending relative reward in ETH
+ pendingRelativeTopUp: bigint; // Pending relative top-up
lastEpoch: bigint;
} = await contract.mapUnitIncentives(unitType, unitId);
- // Struct for component / agent incentive balances
- // struct IncentiveBalances {
- // uint96 reward; // Reward in ETH [0]
- // uint96 pendingRelativeReward; // Pending relative reward in ETH [1]
- // uint96 topUp; // Top-up in OLAS [2]
- // uint96 pendingRelativeTopUp; // Pending relative top-up [3]
- // uint32 lastEpoch; // Last epoch number the information was updated [4]
- // }
const isCurrentEpochWithReward =
currentEpochCounter === lastEpoch && pendingRelativeReward > BIG_INT_0;
- // if the current epoch is not the last epoch, return 0
+ // if already received rewards this epoch, return zeroes
if (!isCurrentEpochWithReward) {
return {
- pendingRelativeReward: '0.0000',
- pendingRelativeTopUp: '0.00',
+ pendingReward: rewardsFormatter(BigInt(0), 4),
+ pendingTopUp: rewardsFormatter(BigInt(0), 2),
};
}
- // if the current epoch is the last epoch, calculate the incentives
- const {
- rewardUnitFraction: cRewardFraction,
- topUpUnitFraction: cTopupFraction,
- sumUnitTopUpsOLAS: cSumUnitTopUpsOLAS,
- } = componentInfo;
- const {
- rewardUnitFraction: aRewardFraction,
- topUpUnitFraction: aTopupFraction,
- sumUnitTopUpsOLAS: aSumUnitTopUpsOLAS,
- } = agentInfo;
+ // Get the unit points of the current epoch
+ const unitInfo: {
+ rewardUnitFraction: bigint;
+ topUpUnitFraction: bigint;
+ sumUnitTopUpsOLAS: bigint;
+ } = await contract.getUnitPoint(currentEpochCounter, unitType);
- /**
- * for unitType agent(0) & component(1),
- * the below calculation is done to get the reward and topup
- */
- const componentReward = (pendingRelativeReward * cRewardFraction) / BIG_INT_100;
- const agentReward = (pendingRelativeReward * aRewardFraction) / BIG_INT_100;
+ const pendingReward = (pendingRelativeReward * unitInfo.rewardUnitFraction) / BIG_INT_100;
let totalIncentives = pendingRelativeTopUp;
- let componentTopUp = BigInt(0);
- let agentPendingTopUp = BigInt(0);
+ let pendingTopUp = BigInt(0);
- if (pendingRelativeTopUp > BIG_INT_0) {
+ /**
+ * the below calculation is done to get the reward and topup
+ * based on current epoch length
+ */
+ if (totalIncentives > BIG_INT_0) {
const inflationPerSecond: bigint = await contract.inflationPerSecond();
const { timeDiff, epochLen } = await getEpochDetails(provider, contract);
@@ -120,37 +89,12 @@ const getMapUnitIncentivesRequest = async ({
const totalTopUps = inflationPerSecond * epochLength;
totalIncentives = totalIncentives * totalTopUps;
- componentTopUp = (totalIncentives * cTopupFraction) / (cSumUnitTopUpsOLAS * BIG_INT_100);
- agentPendingTopUp = (totalIncentives * aTopupFraction) / (aSumUnitTopUpsOLAS * BIG_INT_100);
+ pendingTopUp =
+ (totalIncentives * unitInfo.topUpUnitFraction) / (unitInfo.sumUnitTopUpsOLAS * BIG_INT_100);
}
- const isComponent = unitType === TOKENOMICS_UNIT_TYPES.COMPONENT;
- const pendingRelativeTopUpInWei = isComponent ? componentReward : agentReward;
- const componentTopUpInWei = isComponent ? componentTopUp : agentPendingTopUp;
-
return {
- pendingRelativeReward: rewardsFormatter(pendingRelativeTopUpInWei, 4),
- pendingRelativeTopUp: rewardsFormatter(componentTopUpInWei, 2),
+ pendingReward: rewardsFormatter(pendingReward, 4),
+ pendingTopUp: rewardsFormatter(pendingTopUp, 2),
};
-};
-
-// TODO: unable to import TOKENOMICS from util-contracts as of now
-// once the import is fixed, remove provider and contract from the function signature
-export const getPendingIncentives = async (
- provider: Provider,
- contract: Contract,
- unitType: string,
- unitId: string,
-): Promise<{ reward: string; topUp: string }> => {
- const { pendingRelativeReward, pendingRelativeTopUp } = await getMapUnitIncentivesRequest({
- provider,
- contract,
- unitType,
- unitId,
- });
-
- return {
- reward: pendingRelativeReward,
- topUp: pendingRelativeTopUp,
- };
-};
+};
\ No newline at end of file
diff --git a/libs/common-contract-functions/src/lib/useRewards.ts b/libs/common-contract-functions/src/lib/useRewards.ts
index 211cb26f..76f35699 100644
--- a/libs/common-contract-functions/src/lib/useRewards.ts
+++ b/libs/common-contract-functions/src/lib/useRewards.ts
@@ -29,7 +29,12 @@ export const useClaimableIncentives = (
enabled: !!ownerAddress && !!id && isNumber(tokenomicsUnitType),
select: (data) => {
const [reward, topup] = data as [bigint, bigint];
- return { reward: rewardsFormatter(reward, 4), topUp: rewardsFormatter(topup, 2) };
+ return {
+ reward: rewardsFormatter(reward, 4),
+ rewardWei: reward,
+ topUp: rewardsFormatter(topup, 2),
+ topUpWei: topup,
+ };
},
refetchOnWindowFocus: false,
},
diff --git a/libs/util-contracts/src/lib/abiAndAddresses/treasury.js b/libs/util-contracts/src/lib/abiAndAddresses/treasury.ts
similarity index 99%
rename from libs/util-contracts/src/lib/abiAndAddresses/treasury.js
rename to libs/util-contracts/src/lib/abiAndAddresses/treasury.ts
index aeb306c3..f8766fb7 100644
--- a/libs/util-contracts/src/lib/abiAndAddresses/treasury.js
+++ b/libs/util-contracts/src/lib/abiAndAddresses/treasury.ts
@@ -1,4 +1,6 @@
-export const TREASURY = {
+import { Contract } from "./types";
+
+export const TREASURY: Contract = {
contractName: 'Treasury',
addresses: {
1: '0xa0DA53447C0f6C4987964d8463da7e6628B30f82',
diff --git a/libs/util-functions/src/lib/ethers.ts b/libs/util-functions/src/lib/ethers.ts
index f8c97b8d..ab942e5a 100644
--- a/libs/util-functions/src/lib/ethers.ts
+++ b/libs/util-functions/src/lib/ethers.ts
@@ -1,4 +1,5 @@
import { ethers } from 'ethers';
+import toLower from 'lodash/toLower';
import { Address } from 'viem';
export const getAddressFromBytes32 = (address: Address | string) => {
@@ -10,3 +11,6 @@ export const getBytes32FromAddress = (address: Address | string) => {
};
export const isValidAddress = (address: string) => ethers.isAddress(address);
+
+export const areAddressesEqual = (a1: string | Address, a2: string | Address) =>
+ toLower(a1) === toLower(a2);
diff --git a/libs/util-functions/src/lib/requests.ts b/libs/util-functions/src/lib/requests.ts
index 36533382..3ac16366 100644
--- a/libs/util-functions/src/lib/requests.ts
+++ b/libs/util-functions/src/lib/requests.ts
@@ -1,5 +1,6 @@
import { Contract } from 'ethers';
+
const ESTIMATED_GAS_LIMIT = 500_000;
/**
@@ -8,17 +9,18 @@ const ESTIMATED_GAS_LIMIT = 500_000;
export const getEstimatedGasLimit = async (
fn: Contract['methods'],
account: `0x${string}` | string | undefined,
+ value?: string,
) => {
if (!account) {
throw new Error('Invalid account passed to estimate gas limit');
}
try {
- const estimatedGas = await fn.estimateGas({ from: account });
+ const estimatedGas = await fn.estimateGas({ from: account, value });
return Math.ceil(Number(estimatedGas) * 1.2);
} catch (error) {
window.console.warn(`Error occurred on estimating gas, defaulting to ${ESTIMATED_GAS_LIMIT}`);
}
return ESTIMATED_GAS_LIMIT;
-};
+};
\ No newline at end of file
diff --git a/libs/util-functions/src/lib/sendTransaction/helpers.ts b/libs/util-functions/src/lib/sendTransaction/helpers.ts
index d11145c2..d4c60df6 100644
--- a/libs/util-functions/src/lib/sendTransaction/helpers.ts
+++ b/libs/util-functions/src/lib/sendTransaction/helpers.ts
@@ -1,5 +1,6 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { ethers } from 'ethers';
+
import { getUrl } from './index';
import { Chain, RpcUrl } from './types';
@@ -37,10 +38,7 @@ export const pollTransactionDetails = async (hash: string, chainId: number) => {
* i/p: getIsValidChainId(1);
* o/p: true
*/
-export const getIsValidChainId = (
- SUPPORTED_CHAINS: Chain[],
- chainId: number | string
-) => {
+export const getIsValidChainId = (SUPPORTED_CHAINS: Chain[], chainId: number | string) => {
if (!chainId) return false;
return SUPPORTED_CHAINS.some((e) => e.id === Number(chainId));
@@ -69,9 +67,7 @@ export const getProvider = (supportedChains: Chain[], rpcUrls: RpcUrl) => {
}
if (typeof window === 'undefined') {
- console.warn(
- 'No provider found, fetching RPC URL from first supported chain'
- );
+ console.warn('No provider found, fetching RPC URL from first supported chain');
return rpcUrl;
}
@@ -82,9 +78,7 @@ export const getProvider = (supportedChains: Chain[], rpcUrls: RpcUrl) => {
// if logged in via wallet-connect but chainId is not supported,
// default to mainnet (ie. Use JSON-RPC provider)
- return getIsValidChainId(supportedChains, walletConnectChainId)
- ? walletProvider
- : rpcUrl;
+ return getIsValidChainId(supportedChains, walletConnectChainId) ? walletProvider : rpcUrl;
}
// NOT logged in but has wallet installed (eg. Metamask).
@@ -92,9 +86,7 @@ export const getProvider = (supportedChains: Chain[], rpcUrls: RpcUrl) => {
const windowEthereum = getWindowEthereum();
if (windowEthereum?.chainId) {
const walletChainId = Number(windowEthereum.chainId);
- return getIsValidChainId(supportedChains, walletChainId)
- ? windowEthereum
- : rpcUrl;
+ return getIsValidChainId(supportedChains, walletChainId) ? windowEthereum : rpcUrl;
}
// fallback to mainnet JSON RPC provider
@@ -105,10 +97,7 @@ export const getProvider = (supportedChains: Chain[], rpcUrls: RpcUrl) => {
* gets ethers provider from the connected wallet or
* installed wallet or fallback to mainnet
*/
-export const getEthersProvider = (
- supportedChains: Chain[],
- rpcUrls: RpcUrl
-) => {
+export const getEthersProvider = (supportedChains: Chain[], rpcUrls: RpcUrl) => {
const provider = getProvider(supportedChains, rpcUrls);
// if provider is a string, it is a JSON-RPC provider
@@ -116,7 +105,7 @@ export const getEthersProvider = (
return new ethers.JsonRpcProvider(provider);
}
- return new ethers.FallbackProvider([provider]);
+ return new ethers.BrowserProvider(provider);
};
/**
@@ -126,16 +115,14 @@ export const getEthersProvider = (
*/
export const getChainIdOrDefaultToMainnet = (
SUPPORTED_CHAINS: Chain[],
- chainIdPassed: string | number
+ chainIdPassed: string | number,
) => {
if (!chainIdPassed) {
throw new Error('chainId is not provided');
}
const chain = Number(chainIdPassed);
- return getIsValidChainId(SUPPORTED_CHAINS, chain)
- ? chain
- : SUPPORTED_CHAINS[0].id;
+ return getIsValidChainId(SUPPORTED_CHAINS, chain) ? chain : SUPPORTED_CHAINS[0].id;
};
/**
@@ -143,7 +130,7 @@ export const getChainIdOrDefaultToMainnet = (
*/
export const getChainIdOrDefaultToFirstSupportedChain = (
SUPPORTED_CHAINS: Chain[],
- chainIdPassed: string | number
+ chainIdPassed: string | number,
) => {
return getChainIdOrDefaultToMainnet(SUPPORTED_CHAINS, chainIdPassed);
};
@@ -152,10 +139,7 @@ export const getChainIdOrDefaultToFirstSupportedChain = (
* get chainId from the providers or fallback to default chainId (mainnet)
* first element of supportedChains is the default chainId
*/
-export const getChainId = (
- supportedChains: Chain[],
- chainId?: string | number | null
-) => {
+export const getChainId = (supportedChains: Chain[], chainId?: string | number | null) => {
// if window is undefined, we are in server side
// return undefined
if (typeof window === 'undefined') {
@@ -172,10 +156,7 @@ export const getChainId = (
const walletProvider = getModalProvider();
if (walletProvider?.chainId) {
const walletConnectChainId = walletProvider.chainId;
- return getChainIdOrDefaultToFirstSupportedChain(
- supportedChains,
- walletConnectChainId
- );
+ return getChainIdOrDefaultToFirstSupportedChain(supportedChains, walletConnectChainId);
}
// NOT logged in but has wallet installed (eg. metamask).
@@ -183,10 +164,7 @@ export const getChainId = (
const windowEthereum = getWindowEthereum();
if (windowEthereum?.chainId) {
const walletChainId = windowEthereum.chainId;
- return getChainIdOrDefaultToFirstSupportedChain(
- supportedChains,
- walletChainId
- );
+ return getChainIdOrDefaultToFirstSupportedChain(supportedChains, walletChainId);
}
// has no wallet (eg. incognito mode or no wallet installed)
diff --git a/libs/util-functions/src/lib/sendTransaction/index.ts b/libs/util-functions/src/lib/sendTransaction/index.ts
index f297f09b..f087f72f 100644
--- a/libs/util-functions/src/lib/sendTransaction/index.ts
+++ b/libs/util-functions/src/lib/sendTransaction/index.ts
@@ -1,7 +1,8 @@
-/* eslint-disable @typescript-eslint/no-explicit-any */
+import { Contract } from 'ethers';
+
import { notifyError, notifyWarning } from '../notifications';
import { getChainId, getEthersProvider, pollTransactionDetails } from './helpers';
-import { Chain, RpcUrl, Web3ReceiptType } from './types';
+import { Chain, RpcUrl } from './types';
export const SAFE_API_MAINNET =
'https://safe-transaction-mainnet.safe.global/api/v1/multisig-transactions';
@@ -36,7 +37,7 @@ export const getUrl = (hash: string, chainId: number) => {
* poll until the hash has been approved
*/
export const sendTransaction = async (
- sendFn: any,
+ sendFn: Contract,
account = (window as any)?.MODAL_PROVIDER?.accounts[0],
{ supportedChains, rpcUrls }: { supportedChains: Chain[]; rpcUrls: RpcUrl },
) => {
@@ -87,8 +88,7 @@ export const sendTransaction = async (
});
} else {
// usual send function
- const receipt: Web3ReceiptType = await sendFn();
- return receipt;
+ return sendFn;
}
} catch (e) {
notifyError('Error occurred while sending transaction');