Skip to content

Commit c85168c

Browse files
committed
refactor(ecash): improve utility and helper functions
1 parent e022219 commit c85168c

5 files changed

Lines changed: 99 additions & 58 deletions

File tree

packages/extension/src/providers/ecash/libs/utils.ts

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
11
import { Address } from 'ecash-lib';
22
import { toBN } from 'web3-utils';
33

4-
export const isValidECashAddress = (address: string): boolean => {
4+
export const isValidECashAddress = (
5+
address: string,
6+
cashAddrPrefix: string = 'ecash',
7+
): boolean => {
58
try {
69
const addr = Address.parse(address);
7-
return Boolean(addr);
10+
if (addr.prefix !== cashAddrPrefix) return false;
11+
if (!addr.hash || addr.hash.length === 0) return false;
12+
return true;
813
} catch {
914
return false;
1015
}
@@ -65,19 +70,20 @@ export function sumSatoshis(items: any[]): string {
6570
*/
6671
export function calculateTransactionValue(
6772
outputs: any[],
68-
normalizedAddress: string,
73+
ownedAddresses: string[],
6974
isReceive: boolean,
7075
cashAddrPrefix: string = 'ecash',
7176
): string {
77+
const ownedSet = new Set(ownedAddresses);
7278
return outputs
7379
.filter((output: any) => {
7480
const outputAddress = scriptToAddress(
7581
output.outputScript,
7682
cashAddrPrefix,
7783
);
7884
return isReceive
79-
? outputAddress === normalizedAddress
80-
: outputAddress !== normalizedAddress;
85+
? ownedSet.has(outputAddress)
86+
: !ownedSet.has(outputAddress);
8187
})
8288
.reduce((sum, output) => sum.add(toBN(extractSats(output))), toBN('0'))
8389
.toString();
@@ -91,27 +97,40 @@ export function calculateOnchainTxFee(tx: any): number {
9197

9298
export function getTransactionAddresses(
9399
tx: any,
94-
normalizedAddress: string,
100+
ownedAddresses: string[],
95101
isReceive: boolean,
96102
isSend: boolean,
97103
cashAddrPrefix: string = 'ecash',
98104
): { fromAddress: string; toAddress: string } {
99105
let fromAddress = 'Unknown';
100106
let toAddress = 'Unknown';
107+
const ownedSet = new Set(ownedAddresses);
101108

102109
if (isReceive) {
110+
// From: first input (external sender)
103111
fromAddress = tx.inputs[0]?.outputScript
104112
? scriptToAddress(tx.inputs[0].outputScript, cashAddrPrefix)
105113
: 'Unknown';
106-
toAddress = normalizedAddress;
114+
// To: first owned address that received funds
115+
const receivingOutput = tx.outputs.find((output: any) => {
116+
const outputAddress = scriptToAddress(
117+
output.outputScript,
118+
cashAddrPrefix,
119+
);
120+
return ownedSet.has(outputAddress);
121+
});
122+
toAddress = receivingOutput
123+
? scriptToAddress(receivingOutput.outputScript, cashAddrPrefix)
124+
: (ownedAddresses[0] ?? 'Unknown');
107125
} else if (isSend) {
108-
fromAddress = normalizedAddress;
126+
fromAddress = ownedAddresses[0] ?? 'Unknown';
127+
// To: first EXTERNAL output (not owned = recipient, not change)
109128
const recipientOutput = tx.outputs.find((output: any) => {
110129
const outputAddress = scriptToAddress(
111130
output.outputScript,
112131
cashAddrPrefix,
113132
);
114-
return outputAddress !== normalizedAddress;
133+
return !ownedSet.has(outputAddress);
115134
});
116135
toAddress = recipientOutput
117136
? scriptToAddress(recipientOutput.outputScript, cashAddrPrefix)

packages/extension/src/providers/ecash/tests/utils.test.ts

Lines changed: 40 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,32 @@ import {
1313

1414
describe('ECash Utils Tests', () => {
1515
describe('isValidECashAddress', () => {
16-
it('should validate correct eCash address', () => {
16+
it('should validate correct eCash address with prefix', () => {
1717
const validAddress = 'ecash:qq42tdgdvx5np0pgpjd9x9jupkaugmdw7sjp5dqa63';
1818
expect(isValidECashAddress(validAddress)).toBe(true);
1919
});
2020

21-
it('should validate correct eCash address without prefix', () => {
21+
it('should validate correct eCash address without prefix (default ecash)', () => {
2222
const validAddress = 'qq42tdgdvx5np0pgpjd9x9jupkaugmdw7sjp5dqa63';
2323
expect(isValidECashAddress(validAddress)).toBe(true);
2424
});
2525

26+
it('should validate correct ectest address without prefix', () => {
27+
const validAddress = 'qz5fdmzx8cdqspevemxe20z94y6689zhdqm5xdfvsm';
28+
expect(isValidECashAddress(validAddress, 'ectest')).toBe(true);
29+
});
30+
31+
it('should reject ecash address when ectest prefix is expected', () => {
32+
const validEcashAddress =
33+
'ecash:qq42tdgdvx5np0pgpjd9x9jupkaugmdw7sjp5dqa63';
34+
expect(isValidECashAddress(validEcashAddress, 'ectest')).toBe(false);
35+
});
36+
37+
it('should reject ectest address when ecash prefix is expected', () => {
38+
const ectestAddress = 'ectest:qq42tdgdvx5np0pgpjd9x9jupkaugmdw7sjp5dqa63';
39+
expect(isValidECashAddress(ectestAddress, 'ecash')).toBe(false);
40+
});
41+
2642
it('should reject invalid eCash address', () => {
2743
const invalidAddress = 'invalid_address_123';
2844
expect(isValidECashAddress(invalidAddress)).toBe(false);
@@ -31,6 +47,12 @@ describe('ECash Utils Tests', () => {
3147
it('should reject empty string', () => {
3248
expect(isValidECashAddress('')).toBe(false);
3349
});
50+
51+
it('should use ecash as default prefix', () => {
52+
const validAddress = 'ecash:qq42tdgdvx5np0pgpjd9x9jupkaugmdw7sjp5dqa63';
53+
expect(isValidECashAddress(validAddress)).toBe(true);
54+
expect(isValidECashAddress(validAddress, 'ecash')).toBe(true);
55+
});
3456
});
3557

3658
describe('scriptToAddress', () => {
@@ -124,7 +146,11 @@ describe('ECash Utils Tests', () => {
124146
},
125147
];
126148
const normalizedAddress = scriptToAddress(outputs[0].outputScript);
127-
const value = calculateTransactionValue(outputs, normalizedAddress, true);
149+
const value = calculateTransactionValue(
150+
outputs,
151+
[normalizedAddress],
152+
true,
153+
);
128154
expect(value).to.equal('1000');
129155
});
130156

@@ -142,7 +168,7 @@ describe('ECash Utils Tests', () => {
142168
const normalizedAddress = scriptToAddress(outputs[0].outputScript);
143169
const value = calculateTransactionValue(
144170
outputs,
145-
normalizedAddress,
171+
[normalizedAddress],
146172
false,
147173
);
148174
expect(value).to.equal('2000');
@@ -157,7 +183,7 @@ describe('ECash Utils Tests', () => {
157183
];
158184
const value = calculateTransactionValue(
159185
outputs,
160-
'nonexistent_address',
186+
['nonexistent_address'],
161187
true,
162188
);
163189
expect(value).to.equal('0');
@@ -207,15 +233,16 @@ describe('ECash Utils Tests', () => {
207233
],
208234
outputs: [
209235
{
210-
outputScript: '76a914b9b67dd5f6b3f3b4f7c8d9e0f1a2b3c4d5e6f788ac',
236+
outputScript: '76a9142aa5b50d61a930bc280c9a53165c0dbbc46daef488ac',
211237
sats: 1000,
212238
},
213239
],
214240
};
241+
// The owned address matches the output script above
215242
const normalizedAddress = 'qq42tdgdvx5np0pgpjd9x9jupkaugmdw7sjp5dqa63';
216243
const { fromAddress, toAddress } = getTransactionAddresses(
217244
tx,
218-
normalizedAddress,
245+
[normalizedAddress],
219246
true,
220247
false,
221248
);
@@ -232,19 +259,20 @@ describe('ECash Utils Tests', () => {
232259
],
233260
outputs: [
234261
{
235-
outputScript: '76a914b9b67dd5f6b3f3b4f7c8d9e0f1a2b3c4d5e6f788ac',
262+
outputScript: '76a9142aa5b50d61a930bc280c9a53165c0dbbc46daef488ac',
236263
sats: 1000,
237264
},
238265
{
239-
outputScript: '76a914c0c78ee6f7c4f4c5f8d9e0f1a2b3c4d5e6f788ac',
266+
outputScript: '76a914b9b67dd5f6b3f3b4f7c8d9e0f1a2b3c4d5e6f788ac',
240267
sats: 2000,
241268
},
242269
],
243270
};
244-
const normalizedAddress = scriptToAddress(tx.outputs[0].outputScript);
271+
// owned address is the first output (change), recipient is the second
272+
const normalizedAddress = 'qq42tdgdvx5np0pgpjd9x9jupkaugmdw7sjp5dqa63';
245273
const { fromAddress, toAddress } = getTransactionAddresses(
246274
tx,
247-
normalizedAddress,
275+
[normalizedAddress],
248276
false,
249277
true,
250278
);
@@ -260,7 +288,7 @@ describe('ECash Utils Tests', () => {
260288
};
261289
const { fromAddress, toAddress } = getTransactionAddresses(
262290
tx,
263-
'someaddress',
291+
['someaddress'],
264292
false,
265293
false,
266294
);

packages/extension/src/providers/ecash/types/ecash-network.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,8 @@ export const getAddress = (
3939
pubkey: string,
4040
cashAddrPrefix: string = 'ecash',
4141
): string => {
42-
if (isValidECashAddress(pubkey)) return getAddressWithoutPrefix(pubkey);
42+
if (isValidECashAddress(pubkey, cashAddrPrefix))
43+
return getAddressWithoutPrefix(pubkey);
4344

4445
try {
4546
let cleanPubkey = pubkey;
@@ -56,6 +57,6 @@ export const getAddress = (
5657
return getAddressWithoutPrefix(address);
5758
} catch (error) {
5859
console.error('Error converting pubkey to cashaddr:', error);
59-
return pubkey;
60+
return '';
6061
}
6162
};

packages/extension/src/providers/ecash/ui/libs/fee-calculator.ts

Lines changed: 20 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,15 @@ export const calculateTransactionFee = (
5959
if (bSats.lt(aSats)) return -1;
6060
return 0;
6161
});
62-
62+
if (nonTokenUTXOs.length === 0) {
63+
console.warn(
64+
'⚠️ [calculateTransactionFee] No spendable XEC UTXOs available',
65+
);
66+
return {
67+
feeInXEC: fromBase(fallbackByteSize.toString(), networkDecimals),
68+
txSize: fallbackByteSize,
69+
};
70+
}
6371
const result = calculateNativeXECFee(
6472
sendAmount,
6573
nonTokenUTXOs,
@@ -146,30 +154,17 @@ export const buildGasCostValues = (
146154
): GasFeeType => {
147155
const feeUSD = new BigNumber(feeInXEC).times(assetPrice || '0').toString();
148156

157+
const entry = {
158+
nativeValue: feeInXEC,
159+
fiatValue: feeUSD,
160+
nativeSymbol: currencyName,
161+
fiatSymbol: 'USD',
162+
};
163+
149164
return {
150-
[GasPriceTypes.ECONOMY]: {
151-
nativeValue: new BigNumber(feeInXEC).times(0.8).toString(),
152-
fiatValue: new BigNumber(feeUSD).times(0.8).toString(),
153-
nativeSymbol: currencyName,
154-
fiatSymbol: 'USD',
155-
},
156-
[GasPriceTypes.REGULAR]: {
157-
nativeValue: feeInXEC,
158-
fiatValue: feeUSD,
159-
nativeSymbol: currencyName,
160-
fiatSymbol: 'USD',
161-
},
162-
[GasPriceTypes.FAST]: {
163-
nativeValue: new BigNumber(feeInXEC).times(1.2).toString(),
164-
fiatValue: new BigNumber(feeUSD).times(1.2).toString(),
165-
nativeSymbol: currencyName,
166-
fiatSymbol: 'USD',
167-
},
168-
[GasPriceTypes.FASTEST]: {
169-
nativeValue: new BigNumber(feeInXEC).times(1.5).toString(),
170-
fiatValue: new BigNumber(feeUSD).times(1.5).toString(),
171-
nativeSymbol: currencyName,
172-
fiatSymbol: 'USD',
173-
},
165+
[GasPriceTypes.ECONOMY]: entry,
166+
[GasPriceTypes.REGULAR]: entry,
167+
[GasPriceTypes.FAST]: entry,
168+
[GasPriceTypes.FASTEST]: entry,
174169
};
175170
};

packages/extension/src/providers/ecash/ui/libs/send-utils.ts

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,19 @@ import { toBN } from 'web3-utils';
22
import { toBase, fromBase } from '@enkryptcom/utils';
33

44
interface UTXO {
5-
sats?: number;
6-
value?: number;
5+
sats?: number | bigint;
6+
value?: number | bigint;
77
token?: any;
88
}
99

1010
export const calculateUTXOBalance = (
1111
accountUTXOs: UTXO[],
1212
): ReturnType<typeof toBN> => {
1313
const nonTokenUTXOs = accountUTXOs.filter((utxo: UTXO) => !utxo.token);
14-
return toBN(
15-
nonTokenUTXOs.reduce(
16-
(acc, utxo) => acc + Number(utxo.sats || utxo.value || 0),
17-
0,
18-
),
19-
);
14+
return nonTokenUTXOs.reduce((acc, utxo) => {
15+
const sats = utxo.sats ?? utxo.value ?? 0;
16+
return acc.add(toBN(sats.toString()));
17+
}, toBN(0));
2018
};
2119

2220
export const calculateBalanceAfterTransaction = (
@@ -41,8 +39,8 @@ export const isBelowDustLimit = (
4139
assetDecimals: number,
4240
dustLimit: number,
4341
): boolean => {
44-
const amountInSatoshis = toBase(sendAmount, assetDecimals);
45-
return Number(amountInSatoshis) < dustLimit && Number(sendAmount) > 0;
42+
const amountInSats = toBN(toBase(sendAmount, assetDecimals));
43+
return amountInSats.lt(toBN(dustLimit)) && amountInSats.gt(toBN(0));
4644
};
4745

4846
export const calculateMaxSendableValue = (

0 commit comments

Comments
 (0)