0
# Currency and Transaction Errors
1
2
Error classes for cryptocurrency operations including address validation, transaction processing, fee estimation, and currency-specific issues.
3
4
## Types
5
6
```typescript { .api }
7
interface Transaction {
8
from: string;
9
to: string;
10
amount: number;
11
currency: string;
12
destinationTag?: number;
13
}
14
15
interface FeeData {
16
fee: number;
17
gasLimit?: number;
18
gasPrice?: number;
19
estimatedGas?: number;
20
}
21
22
interface TransactionInput {
23
amount: number;
24
recipient: string;
25
currency: string;
26
senderBalance: number;
27
}
28
29
interface TezosAccount {
30
type: "originated" | "implicit";
31
canReceive: boolean;
32
canSend: boolean;
33
address: string;
34
}
35
```
36
37
## Capabilities
38
39
### Currency Support Errors
40
41
Errors related to currency support and compatibility.
42
43
```typescript { .api }
44
const CurrencyNotSupported: CustomErrorFunc;
45
const CashAddrNotSupported: CustomErrorFunc;
46
const NotSupportedLegacyAddress: CustomErrorFunc;
47
const ETHAddressNonEIP: CustomErrorFunc;
48
const EthAppPleaseEnableContractData: CustomErrorFunc;
49
```
50
51
**Usage Examples:**
52
53
```typescript
54
import {
55
CurrencyNotSupported,
56
CashAddrNotSupported,
57
NotSupportedLegacyAddress,
58
ETHAddressNonEIP
59
} from "@ledgerhq/errors";
60
61
// Validate currency support
62
function validateCurrency(currency: string) {
63
const supportedCurrencies = ["BTC", "ETH", "LTC", "XRP"];
64
if (!supportedCurrencies.includes(currency)) {
65
throw new CurrencyNotSupported(`Currency ${currency} is not supported`);
66
}
67
}
68
69
// Handle Bitcoin Cash address format
70
function validateBitcoinAddress(address: string, allowCashAddr: boolean = false) {
71
if (address.startsWith("bitcoincash:") && !allowCashAddr) {
72
throw new CashAddrNotSupported("Cash address format is not supported");
73
}
74
}
75
76
// Handle legacy address formats
77
function validateAddressFormat(address: string, currency: string) {
78
if (currency === "BTC" && address.startsWith("1")) {
79
throw new NotSupportedLegacyAddress("Legacy P2PKH addresses are not supported");
80
}
81
}
82
83
// Handle Ethereum address compliance
84
function validateEthereumAddress(address: string) {
85
if (!isEIP55Compliant(address)) {
86
throw new ETHAddressNonEIP("Ethereum address must be EIP-55 compliant");
87
}
88
}
89
90
// Handle Ethereum contract data requirement
91
try {
92
await executeEthereumTransaction(contractTransaction);
93
} catch (error) {
94
if (error instanceof EthAppPleaseEnableContractData) {
95
console.log("Please enable contract data in your Ethereum app settings");
96
}
97
}
98
```
99
100
### Address Validation Errors
101
102
Errors related to cryptocurrency address validation and format checking.
103
104
```typescript { .api }
105
const InvalidAddress: CustomErrorFunc;
106
const InvalidAddressBecauseDestinationIsAlsoSource: CustomErrorFunc;
107
const InvalidXRPTag: CustomErrorFunc;
108
```
109
110
**Usage Examples:**
111
112
```typescript
113
import {
114
InvalidAddress,
115
InvalidAddressBecauseDestinationIsAlsoSource,
116
InvalidXRPTag
117
} from "@ledgerhq/errors";
118
119
// Validate address format
120
function validateAddress(address: string, currency: string) {
121
if (!isValidAddressFormat(address, currency)) {
122
throw new InvalidAddress(`Invalid ${currency} address format: ${address}`);
123
}
124
}
125
126
// Prevent self-transactions
127
function validateTransactionAddresses(fromAddress: string, toAddress: string) {
128
if (fromAddress === toAddress) {
129
throw new InvalidAddressBecauseDestinationIsAlsoSource(
130
"Cannot send funds to the same address"
131
);
132
}
133
}
134
135
// Validate XRP destination tags
136
function validateXRPTransaction(address: string, tag?: number) {
137
validateAddress(address, "XRP");
138
139
if (tag !== undefined && (tag < 0 || tag > 4294967295 || !Number.isInteger(tag))) {
140
throw new InvalidXRPTag("XRP destination tag must be a valid 32-bit unsigned integer");
141
}
142
}
143
144
// Example usage in transaction validation
145
function validateTransactionInput(transaction: Transaction) {
146
// Basic address validation
147
validateAddress(transaction.from, transaction.currency);
148
validateAddress(transaction.to, transaction.currency);
149
150
// Prevent self-transactions
151
validateTransactionAddresses(transaction.from, transaction.to);
152
153
// Currency-specific validation
154
if (transaction.currency === "XRP") {
155
validateXRPTransaction(transaction.to, transaction.destinationTag);
156
} else if (transaction.currency === "ETH") {
157
validateEthereumAddress(transaction.to);
158
}
159
}
160
```
161
162
### Transaction Requirements Errors
163
164
Errors related to missing required transaction parameters.
165
166
```typescript { .api }
167
const AmountRequired: CustomErrorFunc;
168
const RecipientRequired: CustomErrorFunc;
169
```
170
171
**Usage Examples:**
172
173
```typescript
174
import {
175
AmountRequired,
176
RecipientRequired
177
} from "@ledgerhq/errors";
178
179
// Validate transaction amount
180
function validateTransactionAmount(amount?: number | string) {
181
if (amount === undefined || amount === null || amount === "" || amount === 0) {
182
throw new AmountRequired("Transaction amount is required");
183
}
184
185
if (typeof amount === "string" && isNaN(Number(amount))) {
186
throw new AmountRequired("Transaction amount must be a valid number");
187
}
188
}
189
190
// Validate recipient address
191
function validateRecipient(recipient?: string) {
192
if (!recipient || recipient.trim() === "") {
193
throw new RecipientRequired("Recipient address is required");
194
}
195
}
196
197
// Example transaction builder
198
class TransactionBuilder {
199
private amount?: number;
200
private recipient?: string;
201
202
setAmount(amount: number) {
203
validateTransactionAmount(amount);
204
this.amount = amount;
205
return this;
206
}
207
208
setRecipient(recipient: string) {
209
validateRecipient(recipient);
210
this.recipient = recipient;
211
return this;
212
}
213
214
build() {
215
validateTransactionAmount(this.amount);
216
validateRecipient(this.recipient);
217
218
return {
219
amount: this.amount!,
220
recipient: this.recipient!
221
};
222
}
223
}
224
```
225
226
### Fee and Gas Errors
227
228
Errors related to transaction fees and gas estimation.
229
230
```typescript { .api }
231
const FeeEstimationFailed: CustomErrorFunc;
232
const FeeNotLoaded: CustomErrorFunc;
233
const FeeRequired: CustomErrorFunc;
234
const FeeTooHigh: CustomErrorFunc;
235
const NotEnoughGas: CustomErrorFunc;
236
const GasLessThanEstimate: CustomErrorFunc;
237
```
238
239
**Usage Examples:**
240
241
```typescript
242
import {
243
FeeEstimationFailed,
244
FeeNotLoaded,
245
FeeRequired,
246
FeeTooHigh,
247
NotEnoughGas,
248
GasLessThanEstimate
249
} from "@ledgerhq/errors";
250
251
// Handle fee estimation failures
252
async function estimateTransactionFee(transaction: Transaction) {
253
try {
254
return await feeEstimationService.estimate(transaction);
255
} catch (error) {
256
throw new FeeEstimationFailed("Unable to estimate transaction fee");
257
}
258
}
259
260
// Validate fee availability
261
function validateFeeData(feeData?: FeeData) {
262
if (!feeData) {
263
throw new FeeNotLoaded("Fee data must be loaded before creating transaction");
264
}
265
}
266
267
// Validate fee requirement
268
function validateTransactionFee(fee?: number) {
269
if (fee === undefined || fee === null) {
270
throw new FeeRequired("Transaction fee is required");
271
}
272
273
if (fee <= 0) {
274
throw new FeeRequired("Transaction fee must be greater than zero");
275
}
276
}
277
278
// Validate reasonable fee amounts
279
function validateFeeAmount(fee: number, amount: number, maxFeePercent: number = 0.1) {
280
const feePercent = fee / amount;
281
if (feePercent > maxFeePercent) {
282
throw new FeeTooHigh(
283
`Fee is ${(feePercent * 100).toFixed(2)}% of transaction amount (max ${maxFeePercent * 100}%)`
284
);
285
}
286
}
287
288
// Handle Ethereum gas validation
289
function validateEthereumGas(gasLimit: number, gasPrice: number, balance: number) {
290
const totalGasCost = gasLimit * gasPrice;
291
292
if (balance < totalGasCost) {
293
throw new NotEnoughGas(
294
`Insufficient ETH for gas: ${totalGasCost} required, ${balance} available`
295
);
296
}
297
}
298
299
// Validate gas limit against estimate
300
function validateGasLimit(gasLimit: number, estimatedGas: number) {
301
if (gasLimit < estimatedGas) {
302
throw new GasLessThanEstimate(
303
`Gas limit ${gasLimit} is less than estimated ${estimatedGas}`
304
);
305
}
306
}
307
308
// Example fee validation workflow
309
async function prepareTransaction(transactionData: TransactionInput) {
310
// Validate required fields
311
validateTransactionAmount(transactionData.amount);
312
validateRecipient(transactionData.recipient);
313
314
// Estimate and validate fees
315
let feeData;
316
try {
317
feeData = await estimateTransactionFee(transactionData);
318
} catch (error) {
319
if (error instanceof FeeEstimationFailed) {
320
console.log("Using default fee due to estimation failure");
321
feeData = getDefaultFeeData();
322
} else {
323
throw error;
324
}
325
}
326
327
validateFeeData(feeData);
328
validateTransactionFee(feeData.fee);
329
validateFeeAmount(feeData.fee, transactionData.amount);
330
331
// Additional validation for Ethereum
332
if (transactionData.currency === "ETH") {
333
validateEthereumGas(feeData.gasLimit, feeData.gasPrice, transactionData.senderBalance);
334
validateGasLimit(feeData.gasLimit, feeData.estimatedGas);
335
}
336
337
return {
338
...transactionData,
339
fee: feeData.fee,
340
gasLimit: feeData.gasLimit,
341
gasPrice: feeData.gasPrice
342
};
343
}
344
```
345
346
### Synchronization and Generic Errors
347
348
Errors related to data synchronization and general operation failures.
349
350
```typescript { .api }
351
const SyncError: CustomErrorFunc;
352
const TimeoutTagged: CustomErrorFunc;
353
```
354
355
**Usage Examples:**
356
357
```typescript
358
import {
359
SyncError,
360
TimeoutTagged
361
} from "@ledgerhq/errors";
362
363
// Handle synchronization failures
364
async function syncAccountData(accountId: string) {
365
try {
366
await synchronizeAccount(accountId);
367
} catch (error) {
368
throw new SyncError(`Failed to synchronize account data: ${error.message}`);
369
}
370
}
371
372
// Handle operation timeouts
373
async function executeWithTimeout<T>(
374
operation: () => Promise<T>,
375
timeoutMs: number,
376
operationName: string
377
): Promise<T> {
378
const timeoutPromise = new Promise<never>((_, reject) => {
379
setTimeout(() => {
380
reject(new TimeoutTagged(`${operationName} timed out after ${timeoutMs}ms`));
381
}, timeoutMs);
382
});
383
384
return Promise.race([operation(), timeoutPromise]);
385
}
386
387
// Example usage with timeout
388
async function fetchAccountBalance(accountId: string) {
389
try {
390
return await executeWithTimeout(
391
() => getAccountBalance(accountId),
392
30000,
393
"Account balance fetch"
394
);
395
} catch (error) {
396
if (error instanceof TimeoutTagged) {
397
console.log("Balance fetch timed out - using cached value");
398
return getCachedBalance(accountId);
399
}
400
throw error;
401
}
402
}
403
```