Skip to content

Commit

Permalink
Balance refactor (#3069)
Browse files Browse the repository at this point in the history
* job launcher balance refactor

* balance refactor

* use utils operators to properly handle small decimal amounts

* fix fee calculation

* fix payment amount

* job launcher balance refactor backend

* set fund token for the job

* fix rate direction

* fix payment logic

* update frontend and tests

* refactor job service tests

* Solve null issue with stringify

* fix typo

* fix pr comments

---------

Co-authored-by: Francisco López <[email protected]>
  • Loading branch information
portuu3 and flopez7 authored Feb 14, 2025
1 parent 484c530 commit 69ff9a4
Show file tree
Hide file tree
Showing 38 changed files with 3,595 additions and 6,450 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,7 @@ export const TESTNET_CHAIN_IDS = [
ChainId.BSC_TESTNET,
ChainId.SEPOLIA,
];
export const MAINNET_CHAIN_IDS = [
ChainId.POLYGON,
ChainId.BSC_MAINNET,
];
export const MAINNET_CHAIN_IDS = [ChainId.POLYGON, ChainId.BSC_MAINNET];

export const JWT_KVSTORE_KEY = 'jwt_public_key';
export const KYC_APPROVED = 'approved';
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import {
usePublicClient,
} from 'wagmi';
import { TokenSelect } from '../../../components/TokenSelect';
import { CURRENCY } from '../../../constants/payment';
import { NETWORK_TOKENS } from '../../../constants/chains';
import { useTokenRate } from '../../../hooks/useTokenRate';
import { useCreateJobPageUI } from '../../../providers/CreateJobPageUIProvider';
import * as jobService from '../../../services/job';
Expand All @@ -46,6 +46,7 @@ export const CryptoPayForm = ({
const { chain } = useAccount();
const { jobRequest, goToPrevStep } = useCreateJobPageUI();
const [tokenAddress, setTokenAddress] = useState<string>();
const [tokenSymbol, setTokenSymbol] = useState<string>();
const [payWithAccountBalance, setPayWithAccountBalance] = useState(false);
const [amount, setAmount] = useState<string>();
const [isLoading, setIsLoading] = useState(false);
Expand Down Expand Up @@ -103,7 +104,7 @@ export const CryptoPayForm = ({
}, [payWithAccountBalance, totalAmount, accountAmount]);

const handlePay = async () => {
if (signer && tokenAddress && amount && jobRequest.chainId) {
if (signer && tokenAddress && amount && jobRequest.chainId && tokenSymbol) {
setIsLoading(true);
try {
if (walletPayAmount > 0) {
Expand Down Expand Up @@ -145,15 +146,17 @@ export const CryptoPayForm = ({
await jobService.createFortuneJob(
chainId,
fortuneRequest,
tokenSymbol,
Number(amount),
CURRENCY.hmt,
tokenSymbol,
);
} else if (jobType === JobType.CVAT && cvatRequest) {
await jobService.createCvatJob(
chainId,
cvatRequest,
tokenSymbol,
Number(amount),
CURRENCY.hmt,
tokenSymbol,
);
} else if (jobType === JobType.HCAPTCHA && hCaptchaRequest) {
await jobService.createHCaptchaJob(chainId, hCaptchaRequest);
Expand Down Expand Up @@ -223,7 +226,15 @@ export const CryptoPayForm = ({
<TokenSelect
chainId={chain?.id}
value={tokenAddress}
onChange={(e) => setTokenAddress(e.target.value as string)}
onChange={(e) => {
const symbol = e.target.value as string;
setTokenSymbol(symbol);
setTokenAddress(
NETWORK_TOKENS[
jobRequest.chainId! as keyof typeof NETWORK_TOKENS
]?.[symbol.toLowerCase()],
);
}}
/>
<FormControl fullWidth>
<TextField
Expand All @@ -250,7 +261,7 @@ export const CryptoPayForm = ({
>
<Typography>Account Balance</Typography>
<Typography color="text.secondary">
{user?.balance?.amount?.toFixed(2) ?? '0'}{' '}
~ {user?.balance?.amount?.toFixed(2) ?? '0'}{' '}
{user?.balance?.currency?.toUpperCase() ?? 'USD'}
</Typography>
</Box>
Expand All @@ -265,7 +276,7 @@ export const CryptoPayForm = ({
>
<Typography>Fund Amount</Typography>
<Typography color="text.secondary">
{fundAmount?.toFixed(2)} USD
~ {fundAmount?.toFixed(2)} USD
</Typography>
</Box>
<Box
Expand Down Expand Up @@ -344,6 +355,7 @@ export const CryptoPayForm = ({
disabled={
!isConnected ||
!tokenAddress ||
!tokenSymbol ||
!amount ||
jobRequest.chainId !== chain?.id
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import AddCardModal from '../../../components/CreditCard/AddCardModal';
import SelectCardModal from '../../../components/CreditCard/SelectCardModal';
import { CardIcon } from '../../../components/Icons/CardIcon';
import SuccessModal from '../../../components/SuccessModal';
import { TokenSelect } from '../../../components/TokenSelect';
import { CURRENCY } from '../../../constants/payment';
import { useCreateJobPageUI } from '../../../providers/CreateJobPageUIProvider';
import { useSnackbar } from '../../../providers/SnackProvider';
Expand Down Expand Up @@ -83,6 +84,7 @@ export const FiatPayForm = ({
const [accountAmount] = useState(
user?.balance ? Number(user?.balance?.amount) : 0,
);
const [tokenAddress, setTokenAddress] = useState<string>();

useEffect(() => {
const fetchJobLauncherData = async () => {
Expand Down Expand Up @@ -181,6 +183,11 @@ export const FiatPayForm = ({
return;
}

if (!tokenAddress) {
onError('Please select a token.');
return;
}

onStart();
setIsLoading(true);

Expand Down Expand Up @@ -215,11 +222,18 @@ export const FiatPayForm = ({
await createFortuneJob(
chainId,
fortuneRequest,
fundAmount,
CURRENCY.usd,
fundAmount,
tokenAddress,
);
} else if (jobType === JobType.CVAT && cvatRequest) {
await createCvatJob(chainId, cvatRequest, fundAmount, CURRENCY.usd);
await createCvatJob(
chainId,
cvatRequest,
CURRENCY.usd,
fundAmount,
tokenAddress,
);
} else if (jobType === JobType.HCAPTCHA && hCaptchaRequest) {
await createHCaptchaJob(chainId, hCaptchaRequest);
}
Expand Down Expand Up @@ -321,6 +335,13 @@ export const FiatPayForm = ({
Add Payment Method
</Button>
)}
<TokenSelect
chainId={jobRequest.chainId!}
value={tokenAddress}
onChange={(e) =>
setTokenAddress(e.target.value as string)
}
/>
</FormControl>
</Grid>
</Grid>
Expand Down Expand Up @@ -348,7 +369,7 @@ export const FiatPayForm = ({
<Typography>Account Balance</Typography>
{user?.balance && (
<Typography color="text.secondary">
{user?.balance?.amount?.toFixed(2)} USD
~ {user?.balance?.amount?.toFixed(2)} USD
</Typography>
)}
</Box>
Expand Down Expand Up @@ -427,7 +448,8 @@ export const FiatPayForm = ({
disabled={
!amount ||
(!payWithAccountBalance && !selectedCard) ||
hasError
hasError ||
!tokenAddress
}
>
Pay now
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,11 @@ export const TokenSelect: FC<TokenSelectProps> = (props) => {

return (
<FormControl fullWidth>
<InputLabel id="token-select-label">Token</InputLabel>
<InputLabel id="token-select-label">Funding token</InputLabel>
<Select
labelId="token-select-label"
id="token-select"
label={props?.label ?? 'Token'}
label={props?.label ?? 'Funding token'}
sx={{
'.MuiSelect-select': {
display: 'flex',
Expand All @@ -47,12 +47,8 @@ export const TokenSelect: FC<TokenSelectProps> = (props) => {
>
{availableTokens.map((symbol) => {
const IconComponent = TOKEN_ICONS[symbol];
const tokenAddress =
NETWORK_TOKENS[props.chainId as keyof typeof NETWORK_TOKENS]?.[
symbol.toLowerCase()
];
return (
<MenuItem value={tokenAddress} key={symbol}>
<MenuItem value={symbol} key={symbol}>
{IconComponent && (
<ListItemIcon sx={{ color: '#320a8d' }}>
{IconComponent}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import React, { useMemo, useState } from 'react';
import { Address } from 'viem';
import { useAccount, useWalletClient, usePublicClient } from 'wagmi';
import { TokenSelect } from '../../components/TokenSelect';
import { SUPPORTED_CHAIN_IDS } from '../../constants/chains';
import { NETWORK_TOKENS, SUPPORTED_CHAIN_IDS } from '../../constants/chains';
import { useTokenRate } from '../../hooks/useTokenRate';
import { useSnackbar } from '../../providers/SnackProvider';
import * as paymentService from '../../services/payment';
Expand Down Expand Up @@ -116,7 +116,14 @@ export const CryptoTopUpForm = () => {
<TokenSelect
chainId={chain?.id}
value={tokenAddress}
onChange={(e) => setTokenAddress(e.target.value as string)}
onChange={(e) => {
const symbol = e.target.value as string;
setTokenAddress(
NETWORK_TOKENS[chain?.id as keyof typeof NETWORK_TOKENS]?.[
symbol.toLowerCase()
],
);
}}
/>
<FormControl fullWidth>
<TextField
Expand Down
2 changes: 1 addition & 1 deletion packages/apps/job-launcher/client/src/constants/chains.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ export const NETWORK_TOKENS: Record<
},
[ChainId.SEPOLIA]: {
hmt: NETWORKS[ChainId.SEPOLIA]?.hmtAddress,
usdc: '0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238',
// usdc: '0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238',
},
[ChainId.POLYGON_AMOY]: {
hmt: NETWORKS[ChainId.POLYGON_AMOY]?.hmtAddress,
Expand Down
20 changes: 12 additions & 8 deletions packages/apps/job-launcher/client/src/services/job.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,18 @@ import { getFilenameFromContentDisposition } from '../utils/string';
export const createFortuneJob = async (
chainId: number,
data: FortuneRequest,
amount: number | string,
currency: string,
paymentCurrency: string,
paymentAmount: number | string,
escrowFundToken: string,
) => {
const body: CreateFortuneJobRequest = {
chainId,
submissionsRequired: Number(data.fortunesRequested),
requesterTitle: data.title,
requesterDescription: data.description,
currency: currency,
fundAmount: Number(amount),
paymentCurrency,
paymentAmount: Number(paymentAmount),
escrowFundToken,
qualifications: data.qualifications,
};
await api.post('/job/fortune', body);
Expand All @@ -33,14 +35,16 @@ export const createFortuneJob = async (
export const createCvatJob = async (
chainId: number,
data: CvatRequest,
amount: number | string,
currency: string,
paymentCurrency: string,
paymentAmount: number | string,
escrowFundToken: string,
) => {
const body: CreateCvatJobRequest = {
chainId,
requesterDescription: data.description,
fundAmount: Number(amount),
currency: currency,
paymentCurrency,
paymentAmount: Number(paymentAmount),
escrowFundToken,
data: data.data,
labels: data.labels,
minQuality: Number(data.accuracyTarget) / 100,
Expand Down
10 changes: 6 additions & 4 deletions packages/apps/job-launcher/client/src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,17 +39,19 @@ export type CreateFortuneJobRequest = {
submissionsRequired: number;
requesterTitle: string;
requesterDescription: string;
currency: string;
fundAmount: number;
paymentCurrency: string;
paymentAmount: number;
escrowFundToken: string;
qualifications?: string[];
};

export type CreateCvatJobRequest = {
chainId: number;
requesterDescription: string;
qualifications?: string[];
fundAmount: number;
currency: string;
paymentCurrency: string;
paymentAmount: number;
escrowFundToken: string;
data: CvatDataSource;
labels: string[];
minQuality: number;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,5 @@ export const envValidator = Joi.object({
APIKEY_KEY_LENGTH: Joi.number(),
//COIN API KEYS
RATE_CACHE_TIME: Joi.number().optional(),
COINMARKETCAP_API_KEY: Joi.string().optional(),
COINGECKO_API_KEY: Joi.string().optional(),
});
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ export class ServerConfigService {
* The minimum transaction fee in USD.
* Default: 0.01
*/
get minimunFeeUsd(): number {
get minimumFeeUsd(): number {
return +this.configService.get<number>('MINIMUM_FEE_USD', 0.01);
}

Expand All @@ -61,13 +61,6 @@ export class ServerConfigService {
return +this.configService.get<number>('RATE_CACHE_TIME', 30);
}

/**
* The API key for accessing CoinMarketCap data.
*/
get coinmarketcapApiKey(): string | undefined {
return this.configService.get<string>('COINMARKETCAP_API_KEY');
}

/**
* The API key for accessing CoinGecko data.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@ export const SERVICE_NAME = 'Job Launcher';
export const NS = 'hmt';
export const COINGECKO_API_URL =
'https://api.coingecko.com/api/v3/simple/price';
export const COINMARKETCAP_API_URL =
'https://pro-api.coinmarketcap.com/v1/cryptocurrency/quotes/latest';
export const DEFAULT_MAX_RETRY_COUNT = 3;
export const TX_CONFIRMATION_TRESHOLD = 1;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,3 @@ export const CoingeckoTokenId: ITokenId = {
hmt: 'human-protocol',
usdt: 'tether',
};
export const CoinMarketCupTokenId: ITokenId = {
hmt: 'human',
};
4 changes: 2 additions & 2 deletions packages/apps/job-launcher/server/src/common/enums/job.ts
Original file line number Diff line number Diff line change
Expand Up @@ -426,7 +426,7 @@ export enum WorkerBrowser {
MODERN_BROWSER = 'modern_browser',
}

export enum JobCurrency {
export enum EscrowFundToken {
HMT = 'hmt',
USD = 'usd',
USDT = 'usdt',
}
Loading

0 comments on commit 69ff9a4

Please sign in to comment.