From f717f89d478fb83d133b99b40dbe4ccc04ae9fd8 Mon Sep 17 00:00:00 2001 From: ben888 Date: Mon, 20 Oct 2025 23:43:06 +0700 Subject: [PATCH 1/6] Add useAllowance and useSend hooks; update tailwind config with safelist --- src/lib/massa-react/hooks/index.ts | 2 + src/lib/massa-react/hooks/useAllowance.tsx | 80 +++++++++++ src/lib/massa-react/hooks/useSend.ts | 133 ++++++++++++++++++ src/lib/massa-react/utils/operationHandler.ts | 1 + src/lib/massa-react/utils/sendTransaction.ts | 66 +++++++++ tailwind.config.js | 14 ++ 6 files changed, 296 insertions(+) create mode 100644 src/lib/massa-react/hooks/useAllowance.tsx create mode 100644 src/lib/massa-react/hooks/useSend.ts create mode 100644 src/lib/massa-react/utils/sendTransaction.ts diff --git a/src/lib/massa-react/hooks/index.ts b/src/lib/massa-react/hooks/index.ts index 300d7fad..428a8c72 100644 --- a/src/lib/massa-react/hooks/index.ts +++ b/src/lib/massa-react/hooks/index.ts @@ -4,3 +4,5 @@ export * from './useDisclaimer'; export * from './useResolveDeweb'; export * from './types'; export * from './const'; +export * from './useSend'; +export * from './useAllowance'; diff --git a/src/lib/massa-react/hooks/useAllowance.tsx b/src/lib/massa-react/hooks/useAllowance.tsx new file mode 100644 index 00000000..a5215e0b --- /dev/null +++ b/src/lib/massa-react/hooks/useAllowance.tsx @@ -0,0 +1,80 @@ +import { useCallback, useMemo, useState } from 'react'; +import { MRC20, Provider } from '@massalabs/massa-web3'; +import { useHandleOperation } from './useHandleOperation'; +import toast from 'react-hot-toast'; +import { Asset } from './useSend'; + +export interface AllowanceParams { + spender: string; + amount: bigint; + token: Asset; +} + +export interface UseAllowanceOptions { + provider: Provider | null; +} + +export function useAllowance(options: UseAllowanceOptions) { + const { provider } = options; + const [isProcessing, setIsProcessing] = useState(false); + const { handleOperation } = useHandleOperation(); + + const increaseAllowance = useCallback( + async ({ spender, amount, token }: AllowanceParams): Promise => { + if (!provider) throw new Error('No provider'); + setIsProcessing(true); + if (!token.address) throw new Error('Token address required'); + const mrc20 = new MRC20(provider, token.address); + try { + const current = await mrc20.allowance(provider.address, spender); + if (current >= amount) { + toast.error('Already sufficient allowance'); + setIsProcessing(false); + return; + } + const op = await mrc20.increaseAllowance(spender, amount - current); + await handleOperation(op, { + pending: `Increasing allowance ${amount} ${token.symbol}`, + success: `Increased allowance ${amount} ${token.symbol}`, + error: `Error increasing allowance`, + timeout: `Timeout increasing allowance`, + }); + } finally { + setIsProcessing(false); + } + }, + [provider, handleOperation], + ); + + const decreaseAllowance = useCallback( + async ({ spender, amount, token }: AllowanceParams): Promise => { + if (!provider) throw new Error('No provider'); + setIsProcessing(true); + if (!token.address) throw new Error('Token address required'); + const mrc20 = new MRC20(provider, token.address); + try { + const current = await mrc20.allowance(provider.address, spender); + if (current < amount) { + toast.error('Cannot decrease below current allowance'); + setIsProcessing(false); + return; + } + const op = await mrc20.decreaseAllowance(spender, amount); + await handleOperation(op, { + pending: `Decreasing allowance ${amount} ${token.symbol}`, + success: `Decreased allowance ${amount} ${token.symbol}`, + error: `Error decreasing allowance`, + timeout: `Timeout decreasing allowance`, + }); + } finally { + setIsProcessing(false); + } + }, + [provider, handleOperation], + ); + + return useMemo( + () => ({ isProcessing, increaseAllowance, decreaseAllowance }), + [isProcessing, increaseAllowance, decreaseAllowance], + ); +} diff --git a/src/lib/massa-react/hooks/useSend.ts b/src/lib/massa-react/hooks/useSend.ts new file mode 100644 index 00000000..037d3be1 --- /dev/null +++ b/src/lib/massa-react/hooks/useSend.ts @@ -0,0 +1,133 @@ +import { useCallback, useMemo, useState } from 'react'; +import { Address, MRC20, Operation, Provider } from '@massalabs/massa-web3'; +import { validateAmount } from '../utils/sendTransaction'; +import { useHandleOperation } from './useHandleOperation'; +import toast from 'react-hot-toast'; + +export interface Asset { + decimals: number; + balance: bigint; + symbol: string; + address?: string; + isNative?: boolean; + allowance?: bigint; +} + +export interface SendParams { + recipient: string; + amount: bigint; + asset: Asset; +} + +export interface UseSendOptions { + provider: Provider | null; +} + +export function useSend(options: UseSendOptions) { + const { provider } = options; + const [isProcessing, setIsProcessing] = useState(false); + const { handleOperation } = useHandleOperation(); + + const execute = useCallback( + async ( + sendFn: () => Promise, + asset: Asset, + amount: bigint, + recipient: string, + ): Promise => { + console.log('execute', sendFn, asset, amount, recipient); + console.log('provider', provider); + if (!provider) throw new Error('No provider'); + console.log('provider', provider); + setIsProcessing(true); + + const validation = validateAmount(amount, asset.balance, asset.decimals); + console.log('validation', validation); + if (!validation.valid) { + toast.error(validation.error ?? 'Invalid amount'); + setIsProcessing(false); + return; + } + + console.log('validation passed'); + + try { + Address.fromString(recipient); + } catch { + toast.error('Invalid address'); + setIsProcessing(false); + return; + } + + console.log('recipient', recipient); + try { + const op = await sendFn(); + console.log('op', op); + await handleOperation(op, { + pending: `Sending ${amount} ${asset.symbol}`, + success: `Sent ${amount} ${asset.symbol}`, + error: `Error sending`, + timeout: `Timeout sending`, + }); + } finally { + setIsProcessing(false); + } + }, + [provider, handleOperation], + ); + + const sendMassa = useCallback( + async ({ recipient, amount, asset }: SendParams): Promise => { + if (!provider) throw new Error('No provider'); + await execute( + () => provider.transfer(recipient, amount), + asset, + amount, + recipient, + ); + }, + [provider, execute], + ); + + const sendToken = useCallback( + async ({ recipient, amount, asset }: SendParams): Promise => { + if (!provider) throw new Error('No provider'); + if (!asset.address) throw new Error('Token address required'); + const mrc20 = new MRC20(provider, asset.address); + + const allowance = await mrc20.allowance(provider.address, recipient); + if (allowance < amount) { + toast.error('Insufficient allowance'); + return; + } + + await execute( + () => mrc20.transfer(recipient, amount), + asset, + amount, + recipient, + ); + }, + [provider, execute], + ); + + const sendAsset = useCallback( + async ({ recipient, amount, asset }: SendParams): Promise => { + if (asset.isNative) { + console.log('sendMassa', recipient, amount, asset); + return sendMassa({ recipient, amount, asset }); + } + + return sendToken({ recipient, amount, asset }); + }, + [sendMassa, sendToken], + ); + + return useMemo( + () => ({ + isProcessing, + sendAsset, + }), + [isProcessing, sendAsset], + ); +} diff --git a/src/lib/massa-react/utils/operationHandler.ts b/src/lib/massa-react/utils/operationHandler.ts index 449a3fd3..d3523383 100644 --- a/src/lib/massa-react/utils/operationHandler.ts +++ b/src/lib/massa-react/utils/operationHandler.ts @@ -20,6 +20,7 @@ export async function processOperation( setState: React.Dispatch>, ): Promise { try { + console.log('processOperation', operation.id); updateOpState(setState, { opId: operation.id }); const loadingToastId = showToast( diff --git a/src/lib/massa-react/utils/sendTransaction.ts b/src/lib/massa-react/utils/sendTransaction.ts new file mode 100644 index 00000000..88d3ca8e --- /dev/null +++ b/src/lib/massa-react/utils/sendTransaction.ts @@ -0,0 +1,66 @@ +export interface TransactionValidationResult { + valid: boolean; + error?: string; +} + +export interface AmountValidationResult { + valid: boolean; + error?: string; + amount?: bigint; +} + +/** + * Validates and parses an amount string + */ +export function validateAmount( + amount: bigint, + availableBalance: bigint, + decimals: number, + minAmount = 0n, +): AmountValidationResult { + console.log('validateAmount', amount, availableBalance, decimals, minAmount); + if (!amount || amount === 0n) { + return { valid: false, error: 'Amount is required' }; + } + + if (amount <= 0) { + return { valid: false, error: 'Amount must be greater than 0' }; + } + + if (amount < minAmount) { + const minAmountFormatted = (Number(minAmount) / 10 ** decimals).toFixed( + decimals, + ); + return { + valid: false, + error: `Minimum amount is ${minAmountFormatted}`, + }; + } + + if (amount > availableBalance) { + return { valid: false, error: 'Insufficient balance' }; + } + + return { + valid: true, + amount, + }; +} + +/** + * Calculates the total cost of a transaction (amount + fees) + */ +export function calculateTotalCost(amount: bigint, fee: bigint): bigint { + return amount + fee; +} + +/** + * Checks if the user has sufficient balance for the transaction + */ +export function hasSufficientBalance( + availableBalance: bigint, + amount: bigint, + fee: bigint, +): boolean { + return availableBalance >= amount + fee; +} diff --git a/tailwind.config.js b/tailwind.config.js index db33731b..6d6613c7 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -1,4 +1,18 @@ /** @type {import('tailwindcss').Config} */ export default { presets: [require('./presets/massa-station-preset.js')], + safelist: [ + 'mas-banner', + 'mas-title', + 'mas-subtitle', + 'mas-h2', + 'mas-h3', + 'mas-buttons', + 'mas-menu-active', + 'mas-menu-default', + 'mas-menu-underline', + 'mas-body', + 'mas-body2', + 'mas-caption', + ], }; From f04aeae5eaa0447d433c2d9403d0315cbdf07138 Mon Sep 17 00:00:00 2001 From: ben888 Date: Tue, 21 Oct 2025 00:00:08 +0700 Subject: [PATCH 2/6] Refactor error messages and remove debug logs in useAllowance and useSend hooks; streamline validation in sendTransaction --- src/lib/massa-react/hooks/useAllowance.tsx | 2 +- src/lib/massa-react/hooks/useSend.ts | 17 +---------------- src/lib/massa-react/utils/operationHandler.ts | 1 - src/lib/massa-react/utils/sendTransaction.ts | 7 +------ 4 files changed, 3 insertions(+), 24 deletions(-) diff --git a/src/lib/massa-react/hooks/useAllowance.tsx b/src/lib/massa-react/hooks/useAllowance.tsx index a5215e0b..ecc14aaa 100644 --- a/src/lib/massa-react/hooks/useAllowance.tsx +++ b/src/lib/massa-react/hooks/useAllowance.tsx @@ -55,7 +55,7 @@ export function useAllowance(options: UseAllowanceOptions) { try { const current = await mrc20.allowance(provider.address, spender); if (current < amount) { - toast.error('Cannot decrease below current allowance'); + toast.error('Insufficient allowance to decrease by requested amount'); setIsProcessing(false); return; } diff --git a/src/lib/massa-react/hooks/useSend.ts b/src/lib/massa-react/hooks/useSend.ts index 037d3be1..c3308bbb 100644 --- a/src/lib/massa-react/hooks/useSend.ts +++ b/src/lib/massa-react/hooks/useSend.ts @@ -10,7 +10,6 @@ export interface Asset { symbol: string; address?: string; isNative?: boolean; - allowance?: bigint; } export interface SendParams { @@ -35,22 +34,17 @@ export function useSend(options: UseSendOptions) { amount: bigint, recipient: string, ): Promise => { - console.log('execute', sendFn, asset, amount, recipient); - console.log('provider', provider); if (!provider) throw new Error('No provider'); - console.log('provider', provider); setIsProcessing(true); const validation = validateAmount(amount, asset.balance, asset.decimals); - console.log('validation', validation); + if (!validation.valid) { toast.error(validation.error ?? 'Invalid amount'); setIsProcessing(false); return; } - console.log('validation passed'); - try { Address.fromString(recipient); } catch { @@ -59,10 +53,8 @@ export function useSend(options: UseSendOptions) { return; } - console.log('recipient', recipient); try { const op = await sendFn(); - console.log('op', op); await handleOperation(op, { pending: `Sending ${amount} ${asset.symbol}`, success: `Sent ${amount} ${asset.symbol}`, @@ -95,12 +87,6 @@ export function useSend(options: UseSendOptions) { if (!asset.address) throw new Error('Token address required'); const mrc20 = new MRC20(provider, asset.address); - const allowance = await mrc20.allowance(provider.address, recipient); - if (allowance < amount) { - toast.error('Insufficient allowance'); - return; - } - await execute( () => mrc20.transfer(recipient, amount), asset, @@ -114,7 +100,6 @@ export function useSend(options: UseSendOptions) { const sendAsset = useCallback( async ({ recipient, amount, asset }: SendParams): Promise => { if (asset.isNative) { - console.log('sendMassa', recipient, amount, asset); return sendMassa({ recipient, amount, asset }); } diff --git a/src/lib/massa-react/utils/operationHandler.ts b/src/lib/massa-react/utils/operationHandler.ts index d3523383..449a3fd3 100644 --- a/src/lib/massa-react/utils/operationHandler.ts +++ b/src/lib/massa-react/utils/operationHandler.ts @@ -20,7 +20,6 @@ export async function processOperation( setState: React.Dispatch>, ): Promise { try { - console.log('processOperation', operation.id); updateOpState(setState, { opId: operation.id }); const loadingToastId = showToast( diff --git a/src/lib/massa-react/utils/sendTransaction.ts b/src/lib/massa-react/utils/sendTransaction.ts index 88d3ca8e..0891d1f3 100644 --- a/src/lib/massa-react/utils/sendTransaction.ts +++ b/src/lib/massa-react/utils/sendTransaction.ts @@ -18,15 +18,10 @@ export function validateAmount( decimals: number, minAmount = 0n, ): AmountValidationResult { - console.log('validateAmount', amount, availableBalance, decimals, minAmount); - if (!amount || amount === 0n) { + if (!amount) { return { valid: false, error: 'Amount is required' }; } - if (amount <= 0) { - return { valid: false, error: 'Amount must be greater than 0' }; - } - if (amount < minAmount) { const minAmountFormatted = (Number(minAmount) / 10 ** decimals).toFixed( decimals, From f5e42816ea3dd251ba007214307b5c8701072a28 Mon Sep 17 00:00:00 2001 From: ben888 Date: Tue, 21 Oct 2025 08:27:06 +0700 Subject: [PATCH 3/6] Refactor useAllowance and useSend hooks to format amount display; remove safelist from tailwind config --- src/lib/massa-react/hooks/useAllowance.tsx | 21 +++++++++++++++++---- src/lib/massa-react/hooks/useSend.ts | 9 +++++++-- tailwind.config.js | 14 -------------- 3 files changed, 24 insertions(+), 20 deletions(-) diff --git a/src/lib/massa-react/hooks/useAllowance.tsx b/src/lib/massa-react/hooks/useAllowance.tsx index ecc14aaa..f5281974 100644 --- a/src/lib/massa-react/hooks/useAllowance.tsx +++ b/src/lib/massa-react/hooks/useAllowance.tsx @@ -3,6 +3,7 @@ import { MRC20, Provider } from '@massalabs/massa-web3'; import { useHandleOperation } from './useHandleOperation'; import toast from 'react-hot-toast'; import { Asset } from './useSend'; +import { formatAmount } from '../../util'; export interface AllowanceParams { spender: string; @@ -34,8 +35,14 @@ export function useAllowance(options: UseAllowanceOptions) { } const op = await mrc20.increaseAllowance(spender, amount - current); await handleOperation(op, { - pending: `Increasing allowance ${amount} ${token.symbol}`, - success: `Increased allowance ${amount} ${token.symbol}`, + pending: `Increasing allowance ${formatAmount( + amount.toString(), + token.decimals, + )} ${token.symbol}`, + success: `Increased allowance ${formatAmount( + amount.toString(), + token.decimals, + )} ${token.symbol}`, error: `Error increasing allowance`, timeout: `Timeout increasing allowance`, }); @@ -61,8 +68,14 @@ export function useAllowance(options: UseAllowanceOptions) { } const op = await mrc20.decreaseAllowance(spender, amount); await handleOperation(op, { - pending: `Decreasing allowance ${amount} ${token.symbol}`, - success: `Decreased allowance ${amount} ${token.symbol}`, + pending: `Decreasing allowance ${formatAmount( + amount.toString(), + token.decimals, + )} ${token.symbol}`, + success: `Decreased allowance ${formatAmount( + amount.toString(), + token.decimals, + )} ${token.symbol}`, error: `Error decreasing allowance`, timeout: `Timeout decreasing allowance`, }); diff --git a/src/lib/massa-react/hooks/useSend.ts b/src/lib/massa-react/hooks/useSend.ts index c3308bbb..94be41b2 100644 --- a/src/lib/massa-react/hooks/useSend.ts +++ b/src/lib/massa-react/hooks/useSend.ts @@ -3,6 +3,7 @@ import { Address, MRC20, Operation, Provider } from '@massalabs/massa-web3'; import { validateAmount } from '../utils/sendTransaction'; import { useHandleOperation } from './useHandleOperation'; import toast from 'react-hot-toast'; +import { formatAmount } from '../../util'; export interface Asset { decimals: number; @@ -56,8 +57,12 @@ export function useSend(options: UseSendOptions) { try { const op = await sendFn(); await handleOperation(op, { - pending: `Sending ${amount} ${asset.symbol}`, - success: `Sent ${amount} ${asset.symbol}`, + pending: `Sending ${ + formatAmount(amount.toString(), asset.decimals).preview + } ${asset.symbol}`, + success: `Sent ${ + formatAmount(amount.toString(), asset.decimals).preview + } ${asset.symbol}`, error: `Error sending`, timeout: `Timeout sending`, }); diff --git a/tailwind.config.js b/tailwind.config.js index 6d6613c7..db33731b 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -1,18 +1,4 @@ /** @type {import('tailwindcss').Config} */ export default { presets: [require('./presets/massa-station-preset.js')], - safelist: [ - 'mas-banner', - 'mas-title', - 'mas-subtitle', - 'mas-h2', - 'mas-h3', - 'mas-buttons', - 'mas-menu-active', - 'mas-menu-default', - 'mas-menu-underline', - 'mas-body', - 'mas-body2', - 'mas-caption', - ], }; From c1bc0ec1379215f8c31d55da898400b14085133f Mon Sep 17 00:00:00 2001 From: ben888 Date: Tue, 21 Oct 2025 08:39:37 +0700 Subject: [PATCH 4/6] Refactor allowance messages in useAllowance hook to utilize formatted amount previews for better clarity --- src/lib/massa-react/hooks/useAllowance.tsx | 28 ++++++++++------------ 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/src/lib/massa-react/hooks/useAllowance.tsx b/src/lib/massa-react/hooks/useAllowance.tsx index f5281974..9fbd3c67 100644 --- a/src/lib/massa-react/hooks/useAllowance.tsx +++ b/src/lib/massa-react/hooks/useAllowance.tsx @@ -35,14 +35,12 @@ export function useAllowance(options: UseAllowanceOptions) { } const op = await mrc20.increaseAllowance(spender, amount - current); await handleOperation(op, { - pending: `Increasing allowance ${formatAmount( - amount.toString(), - token.decimals, - )} ${token.symbol}`, - success: `Increased allowance ${formatAmount( - amount.toString(), - token.decimals, - )} ${token.symbol}`, + pending: `Increasing allowance ${ + formatAmount(amount.toString(), token.decimals).preview + } ${token.symbol}`, + success: `Increased allowance ${ + formatAmount(amount.toString(), token.decimals).preview + } ${token.symbol}`, error: `Error increasing allowance`, timeout: `Timeout increasing allowance`, }); @@ -68,14 +66,12 @@ export function useAllowance(options: UseAllowanceOptions) { } const op = await mrc20.decreaseAllowance(spender, amount); await handleOperation(op, { - pending: `Decreasing allowance ${formatAmount( - amount.toString(), - token.decimals, - )} ${token.symbol}`, - success: `Decreased allowance ${formatAmount( - amount.toString(), - token.decimals, - )} ${token.symbol}`, + pending: `Decreasing allowance ${ + formatAmount(amount.toString(), token.decimals).preview + } ${token.symbol}`, + success: `Decreased allowance ${ + formatAmount(amount.toString(), token.decimals).preview + } ${token.symbol}`, error: `Error decreasing allowance`, timeout: `Timeout decreasing allowance`, }); From de8d84cae450d71e382123d1b73edda8172e9ba7 Mon Sep 17 00:00:00 2001 From: ben888 Date: Tue, 21 Oct 2025 10:02:43 +0700 Subject: [PATCH 5/6] Update toast messages in useAllowance hook for improved clarity and accuracy --- src/lib/massa-react/hooks/useAllowance.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/massa-react/hooks/useAllowance.tsx b/src/lib/massa-react/hooks/useAllowance.tsx index 9fbd3c67..8d645b31 100644 --- a/src/lib/massa-react/hooks/useAllowance.tsx +++ b/src/lib/massa-react/hooks/useAllowance.tsx @@ -29,7 +29,7 @@ export function useAllowance(options: UseAllowanceOptions) { try { const current = await mrc20.allowance(provider.address, spender); if (current >= amount) { - toast.error('Already sufficient allowance'); + toast('Already sufficient allowance'); setIsProcessing(false); return; } @@ -60,7 +60,7 @@ export function useAllowance(options: UseAllowanceOptions) { try { const current = await mrc20.allowance(provider.address, spender); if (current < amount) { - toast.error('Insufficient allowance to decrease by requested amount'); + toast.error('Decrease amount is greater than current allowance'); setIsProcessing(false); return; } From 6fe813a5175fdd824e2f618f80d1c40de2167969 Mon Sep 17 00:00:00 2001 From: ben888 Date: Tue, 21 Oct 2025 15:00:48 +0700 Subject: [PATCH 6/6] Refactor asset type definition and update useAllowance and useSend hooks to improve clarity and remove unused validation logic --- src/lib/massa-react/hooks/types.ts | 8 +++ src/lib/massa-react/hooks/useAllowance.tsx | 36 +++--------- src/lib/massa-react/hooks/useSend.ts | 60 +++++++++++++------ src/lib/massa-react/utils/sendTransaction.ts | 61 -------------------- 4 files changed, 57 insertions(+), 108 deletions(-) delete mode 100644 src/lib/massa-react/utils/sendTransaction.ts diff --git a/src/lib/massa-react/hooks/types.ts b/src/lib/massa-react/hooks/types.ts index 51f021ea..cb7cdbd9 100644 --- a/src/lib/massa-react/hooks/types.ts +++ b/src/lib/massa-react/hooks/types.ts @@ -4,3 +4,11 @@ export type ToasterMessage = { error: string; timeout?: string; }; + +export interface Asset { + decimals: number; + balance: bigint; + symbol: string; + address?: string; + isNative?: boolean; +} diff --git a/src/lib/massa-react/hooks/useAllowance.tsx b/src/lib/massa-react/hooks/useAllowance.tsx index 8d645b31..7cd3e62f 100644 --- a/src/lib/massa-react/hooks/useAllowance.tsx +++ b/src/lib/massa-react/hooks/useAllowance.tsx @@ -1,9 +1,7 @@ import { useCallback, useMemo, useState } from 'react'; import { MRC20, Provider } from '@massalabs/massa-web3'; import { useHandleOperation } from './useHandleOperation'; -import toast from 'react-hot-toast'; -import { Asset } from './useSend'; -import { formatAmount } from '../../util'; +import { Asset } from './types'; export interface AllowanceParams { spender: string; @@ -24,23 +22,15 @@ export function useAllowance(options: UseAllowanceOptions) { async ({ spender, amount, token }: AllowanceParams): Promise => { if (!provider) throw new Error('No provider'); setIsProcessing(true); + if (!token.address) throw new Error('Token address required'); const mrc20 = new MRC20(provider, token.address); try { - const current = await mrc20.allowance(provider.address, spender); - if (current >= amount) { - toast('Already sufficient allowance'); - setIsProcessing(false); - return; - } - const op = await mrc20.increaseAllowance(spender, amount - current); + const op = await mrc20.increaseAllowance(spender, amount); + await handleOperation(op, { - pending: `Increasing allowance ${ - formatAmount(amount.toString(), token.decimals).preview - } ${token.symbol}`, - success: `Increased allowance ${ - formatAmount(amount.toString(), token.decimals).preview - } ${token.symbol}`, + pending: `Increasing allowance ${amount} ${token.symbol}`, + success: `Increased allowance ${amount} ${token.symbol}`, error: `Error increasing allowance`, timeout: `Timeout increasing allowance`, }); @@ -58,20 +48,10 @@ export function useAllowance(options: UseAllowanceOptions) { if (!token.address) throw new Error('Token address required'); const mrc20 = new MRC20(provider, token.address); try { - const current = await mrc20.allowance(provider.address, spender); - if (current < amount) { - toast.error('Decrease amount is greater than current allowance'); - setIsProcessing(false); - return; - } const op = await mrc20.decreaseAllowance(spender, amount); await handleOperation(op, { - pending: `Decreasing allowance ${ - formatAmount(amount.toString(), token.decimals).preview - } ${token.symbol}`, - success: `Decreased allowance ${ - formatAmount(amount.toString(), token.decimals).preview - } ${token.symbol}`, + pending: `Decreasing allowance ${amount} ${token.symbol}`, + success: `Decreased allowance ${amount} ${token.symbol}`, error: `Error decreasing allowance`, timeout: `Timeout decreasing allowance`, }); diff --git a/src/lib/massa-react/hooks/useSend.ts b/src/lib/massa-react/hooks/useSend.ts index 94be41b2..f8a46a01 100644 --- a/src/lib/massa-react/hooks/useSend.ts +++ b/src/lib/massa-react/hooks/useSend.ts @@ -1,17 +1,9 @@ import { useCallback, useMemo, useState } from 'react'; import { Address, MRC20, Operation, Provider } from '@massalabs/massa-web3'; -import { validateAmount } from '../utils/sendTransaction'; import { useHandleOperation } from './useHandleOperation'; import toast from 'react-hot-toast'; import { formatAmount } from '../../util'; - -export interface Asset { - decimals: number; - balance: bigint; - symbol: string; - address?: string; - isNative?: boolean; -} +import { Asset } from './types'; export interface SendParams { recipient: string; @@ -28,6 +20,14 @@ export function useSend(options: UseSendOptions) { const [isProcessing, setIsProcessing] = useState(false); const { handleOperation } = useHandleOperation(); + /** + * Executes a send operation + * @param sendFn - The function to send the asset + * @param asset - The asset to send + * @param amount - The amount to send + * @param recipient - The recipient address + * @returns void + */ const execute = useCallback( async ( sendFn: () => Promise, @@ -35,27 +35,25 @@ export function useSend(options: UseSendOptions) { amount: bigint, recipient: string, ): Promise => { - if (!provider) throw new Error('No provider'); setIsProcessing(true); - const validation = validateAmount(amount, asset.balance, asset.decimals); - - if (!validation.valid) { - toast.error(validation.error ?? 'Invalid amount'); - setIsProcessing(false); - return; - } + if (!provider) throw new Error('No provider'); try { Address.fromString(recipient); } catch { toast.error('Invalid address'); - setIsProcessing(false); + return; + } + + if (amount > asset.balance) { + toast.error('Insufficient balance'); return; } try { const op = await sendFn(); + await handleOperation(op, { pending: `Sending ${ formatAmount(amount.toString(), asset.decimals).preview @@ -73,6 +71,13 @@ export function useSend(options: UseSendOptions) { [provider, handleOperation], ); + /** + * Sends a native Massa coin to a recipient + * @param recipient - The recipient address + * @param amount - The amount to send + * @param asset - The asset to send + * @returns void + */ const sendMassa = useCallback( async ({ recipient, amount, asset }: SendParams): Promise => { if (!provider) throw new Error('No provider'); @@ -86,6 +91,13 @@ export function useSend(options: UseSendOptions) { [provider, execute], ); + /** + * Sends a mrc20 token to a recipient + * @param recipient - The recipient address + * @param amount - The amount to send + * @param asset - The token to send + * @returns void + */ const sendToken = useCallback( async ({ recipient, amount, asset }: SendParams): Promise => { if (!provider) throw new Error('No provider'); @@ -102,6 +114,14 @@ export function useSend(options: UseSendOptions) { [provider, execute], ); + /** + * Sends an asset to a recipient + * The Asset can be a native Massa coin or a mrc20 token + * @param recipient - The recipient address + * @param amount - The amount to send + * @param asset - The asset to send + * @returns void + */ const sendAsset = useCallback( async ({ recipient, amount, asset }: SendParams): Promise => { if (asset.isNative) { @@ -117,7 +137,9 @@ export function useSend(options: UseSendOptions) { () => ({ isProcessing, sendAsset, + sendMassa, + sendToken, }), - [isProcessing, sendAsset], + [isProcessing, sendAsset, sendMassa, sendToken], ); } diff --git a/src/lib/massa-react/utils/sendTransaction.ts b/src/lib/massa-react/utils/sendTransaction.ts deleted file mode 100644 index 0891d1f3..00000000 --- a/src/lib/massa-react/utils/sendTransaction.ts +++ /dev/null @@ -1,61 +0,0 @@ -export interface TransactionValidationResult { - valid: boolean; - error?: string; -} - -export interface AmountValidationResult { - valid: boolean; - error?: string; - amount?: bigint; -} - -/** - * Validates and parses an amount string - */ -export function validateAmount( - amount: bigint, - availableBalance: bigint, - decimals: number, - minAmount = 0n, -): AmountValidationResult { - if (!amount) { - return { valid: false, error: 'Amount is required' }; - } - - if (amount < minAmount) { - const minAmountFormatted = (Number(minAmount) / 10 ** decimals).toFixed( - decimals, - ); - return { - valid: false, - error: `Minimum amount is ${minAmountFormatted}`, - }; - } - - if (amount > availableBalance) { - return { valid: false, error: 'Insufficient balance' }; - } - - return { - valid: true, - amount, - }; -} - -/** - * Calculates the total cost of a transaction (amount + fees) - */ -export function calculateTotalCost(amount: bigint, fee: bigint): bigint { - return amount + fee; -} - -/** - * Checks if the user has sufficient balance for the transaction - */ -export function hasSufficientBalance( - availableBalance: bigint, - amount: bigint, - fee: bigint, -): boolean { - return availableBalance >= amount + fee; -}