Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1932,6 +1932,7 @@ Codes follow `category/specific_noun_suffix`. Suffixes: `_failed`, `_timeout`, `
| `user_action/ephemeral_key_denied` | User rejected the ephemeral-key derivation signature | None — user cancelled |
| **validation/*** | | |
| `validation/insufficient_balance` | Not enough tokens for operation | Show balance, suggest deposit |
| `validation/amount_too_low` | Amount cannot cover bridge fees | Increase the source amount |
| `validation/no_balance_for_address` | No balance found for address | Verify address |
| `validation/invalid_input` | Invalid parameters provided | Check input values |
| `validation/invalid_address_length` | Address has wrong length | Verify address format |
Expand Down
1 change: 1 addition & 0 deletions skills/nexus-core/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -648,6 +648,7 @@ Codes follow `category/specific_noun_suffix`. Suffixes: `_failed`, `_timeout`, `

**Validation (`ValidationError`, no service):**
- `validation/insufficient_balance`
- `validation/amount_too_low` — source amount cannot cover bridge fees
- `validation/no_balance_for_address`
- `validation/invalid_input`, `validation/invalid_address_length`, `validation/invalid_allowance_hook`
- `validation/token_not_supported`, `validation/chain_not_found`, `validation/chain_data_not_found`, `validation/asset_not_found`
Expand Down
3 changes: 3 additions & 0 deletions src/domain/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ export const ERROR_CODES = {
ENVIRONMENT_NOT_KNOWN: 'validation/environment_not_known',
INSUFFICIENT_BALANCE: 'validation/insufficient_balance',
NO_BALANCE_FOR_ADDRESS: 'validation/no_balance_for_address',
AMOUNT_TOO_LOW: 'validation/amount_too_low',
SDK_NOT_INITIALIZED: 'validation/sdk_not_initialized',
SDK_INIT_STATE_NOT_EXPECTED: 'validation/sdk_init_state_unexpected',
WALLET_NOT_CONNECTED: 'validation/wallet_not_connected',
Expand Down Expand Up @@ -460,6 +461,8 @@ export const Errors = {
`Insufficient balance to proceed. ${msg ?? ''}`.trim(),
{ context: {} }
),
amountTooLow: (msg: string): ValidationError =>
new ValidationError(ERROR_CODES.AMOUNT_TOO_LOW, msg, { context: {} }),

walletNotConnected: (walletType: string): ValidationError =>
new ValidationError(
Expand Down
5 changes: 3 additions & 2 deletions src/swap/routing/exact-in.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import Decimal from 'decimal.js';
import { formatUnits, type Hex } from 'viem';
import { Errors } from '../../domain/errors';
import { formatTokenBalance } from '../../domain/utils/format';
import { logger } from '../../domain/utils/logger';
import { divDecimals, mulDecimals } from '../../services/math';
import { MAYAN_MIN_USD_PER_LEG, selectMayanQuoteOutput } from '../../services/mayan';
Expand Down Expand Up @@ -220,8 +221,8 @@ const buildExactInBridge = async (input: {
nexusFeeModel,
} = feeSummary;
if (effectiveBridgedToDestination.lte(0)) {
throw Errors.insufficientBalance(
`Bridge fees (${totalFeeAmount.toString()}) exceed bridged COT (${bridgedCOT.toString()})`
throw Errors.amountTooLow(
`Bridge fees (${formatTokenBalance(totalFeeAmount.toFixed())}) exceed bridged amount (${formatTokenBalance(bridgedCOT.toFixed())})`
);
}

Expand Down
5 changes: 3 additions & 2 deletions src/swap/routing/fast-paths.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { formatUnits, type Hex } from 'viem';
import type { ChainListType } from '../../domain';
import { ZERO_ADDRESS } from '../../domain/constants/addresses';
import { Errors } from '../../domain/errors';
import { formatTokenBalance } from '../../domain/utils/format';
import { logger } from '../../domain/utils/logger';
import { isNativeAddress } from '../../services/addresses';
import { divDecimals, mulDecimals } from '../../services/math';
Expand Down Expand Up @@ -611,8 +612,8 @@ export async function buildSameTokenBridgeRoute(
});
deliveredFromBridge = deliveredAmount;
if (deliveredFromBridge.lte(0)) {
throw Errors.insufficientBalance(
`Bridge fees (${totalFee.toString()}) exceed bridged amount (${bridgedToken.toString()})`
throw Errors.amountTooLow(
`Bridge fees (${formatTokenBalance(totalFee.toFixed())}) exceed bridged amount (${formatTokenBalance(bridgedToken.toFixed())})`
);
}
// The fast path participates in provider selection too, querying the server with the actual
Expand Down
12 changes: 9 additions & 3 deletions src/swap/routing/holdings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ import Decimal from 'decimal.js';
import { type Hex, parseUnits } from 'viem';
import type { ChainListType } from '../../domain';
import { Errors } from '../../domain/errors';
import { formatTokenBalance } from '../../domain/utils/format';
import { logger } from '../../domain/utils/logger';
import { isNativeAddress } from '../../services/addresses';
import { divDecimals } from '../../services/math';
import { equalFold } from '../../services/strings';
import { filterMayanSourcesByChain } from '../algorithms/mayan-floor';
Expand Down Expand Up @@ -269,14 +271,18 @@ export function resolveExactInHoldings(
entry.chainID === source.chainId && equalFold(entry.tokenAddress, source.tokenAddress)
);
if (!balance || new Decimal(balance.amount).lte(0)) {
throw Errors.insufficientBalance('Requested source has no usable balance');
throw Errors.insufficientBalance(
`Requested source ${source.tokenAddress} on chain ${source.chainId} has no usable balance`
);
}

const availableRaw = parseUnits(balance.amount, balance.decimals);
const amountRaw = source.amountRaw ?? availableRaw;

if (amountRaw > availableRaw) {
throw Errors.insufficientBalance('Requested source amount exceeds available balance');
if (!isNativeAddress(source.tokenAddress) && amountRaw > availableRaw) {
throw Errors.insufficientBalance(
`Requested source (${balance.symbol}, ${source.chainId}) amount (${formatTokenBalance(divDecimals(amountRaw, balance.decimals).toFixed())}) exceeds available balance (${formatTokenBalance(divDecimals(availableRaw, balance.decimals).toFixed())})`
);
}

return amountRaw > 0n
Expand Down