Skip to content

Commit

Permalink
Many utxos (#10)
Browse files Browse the repository at this point in the history
* Basic handling for many UTXOS

* Handle Nano S limit
  • Loading branch information
coderofstuff authored Jan 10, 2024
1 parent f189b47 commit 29d1f93
Show file tree
Hide file tree
Showing 4 changed files with 87 additions and 44 deletions.
3 changes: 2 additions & 1 deletion app/wallet/overview-tab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { fetchAddressDetails, fetchTransaction, getAddress } from '@/lib/ledger'
import { delay } from '@/lib/util';

import styles from './overview-tab.module.css';
import { sompiToKas } from '@/lib/kaspa-util';

export default function OverviewTab(props) {
const groupRef = useRef(null);
Expand Down Expand Up @@ -127,7 +128,7 @@ export default function OverviewTab(props) {
selectedAddress.derivationPath,
);

selectedAddress.balance = addressDetails.balance / 100000000;
selectedAddress.balance = sompiToKas(addressDetails.balance);
selectedAddress.utxos = addressDetails.utxos;
selectedAddress.newTransactions++;
// selectedAddress.txCount = addressDetails.txCount;
Expand Down
20 changes: 18 additions & 2 deletions app/wallet/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { delay } from '@/lib/util';
import { useElementSize } from '@mantine/hooks';
import { notifications } from '@mantine/notifications';
import SettingsStore from '@/lib/settings-store';
import { kasToSompi, sompiToKas, NETWORK_UTXO_LIMIT } from '@/lib/kaspa-util';

let loadingAddressBatch = false;
let addressInitialized = false;
Expand All @@ -36,7 +37,7 @@ function loadAddressDetails(rawAddress) {
const fetchAddressPromise = fetchAddressDetails(rawAddress.address, rawAddress.derivationPath);

return fetchAddressPromise.then((addressDetails) => {
rawAddress.balance = addressDetails.balance / 100000000;
rawAddress.balance = sompiToKas(addressDetails.balance);
rawAddress.utxos = addressDetails.utxos;
// rawAddress.txCount = addressDetails.txCount;
rawAddress.loading = false;
Expand Down Expand Up @@ -111,15 +112,30 @@ async function demoLoadAddress(bip32, setAddresses, setRawAddresses, lastReceive
const demoAddresses = [];

for (let i = 0; i <= lastReceiveIndex; i++) {
const balance = Math.round(Math.random() * 10000);
const currAddress = {
key: i,
address: bip32.getAddress(0, i),
balance: Math.round(Math.random() * 10000),
balance,
derivationPath: `44'/111111'/0'/0/${i}`,
utxos: [],
loading: true,
};

currAddress.utxos.push({
prevTxId: 'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF',
outpointIndex: 0,
amount: kasToSompi(balance - (NETWORK_UTXO_LIMIT - 1)),
});

for (let j = 0; j < NETWORK_UTXO_LIMIT - 1; j++) {
currAddress.utxos.push({
prevTxId: 'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF',
outpointIndex: 0,
amount: kasToSompi(1),
});
}

demoAddresses.push(currAddress);

setRawAddresses([...demoAddresses]);
Expand Down
106 changes: 65 additions & 41 deletions components/send-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import { useState, useEffect } from 'react';
import { createTransaction, sendAmount, selectUtxos } from '@/lib/ledger';
import AddressText from '@/components/address-text';
import { useForm } from '@mantine/form';
import { kasToSompi, sompiToKas } from '@/lib/kaspa-util';
import { kasToSompi, sompiToKas, NETWORK_UTXO_LIMIT } from '@/lib/kaspa-util';

export default function SendForm(props) {
const [confirming, setConfirming] = useState(false);
Expand Down Expand Up @@ -128,12 +128,30 @@ export default function SendForm(props) {
cleanupOnSuccess(transactionId);
} catch (e) {
console.error(e);
notifications.show({
title: 'Error',
color: 'red',
message: e.message,
loading: false,
});

if (e.statusCode == 0xb005 && props.addressContext.utxos.length > 15) {
// This is probably a Nano S
const maxCompoundableAmount = sompiToKas(
props.addressContext.utxos.slice(0, 15).reduce((acc, utxo) => {
return acc + utxo.amount;
}, 0),
);
notifications.show({
title: 'Error',
color: 'red',
message: `You have too many UTXOs to send this amount. Please compound first by sending KAS to your address. Maximum sendable without compounding (including fee): ${maxCompoundableAmount}`,
autoClose: false,
loading: false,
});
} else {
notifications.show({
title: 'Error',
color: 'red',
message: e.message,
loading: false,
});
}

setConfirming(false);
} finally {
notifications.hide(notifId);
Expand All @@ -146,44 +164,50 @@ export default function SendForm(props) {

if (amount && sendTo) {
let calculatedFee: string | number = '-';
if (deviceType === 'demo') {
calculatedFee =
fee === '-' ? sompiToKas(Math.round(Math.random() * 10000)) : Number(fee);
setCanSendAmount(Number(amount) <= props.addressContext.balance - calculatedFee);
if (includeFeeInAmount) {
const afterFeeDisplay = sompiToKas(kasToSompi(amount) - calculatedFee);
setAmountDescription(`Amount after fee: ${afterFeeDisplay}`);

const {
hasEnough,
utxos,
fee: feeCalcResult,
total: utxoTotalAmount,
} = selectUtxos(kasToSompi(amount), props.addressContext.utxos, includeFeeInAmount);

if (utxos.length > NETWORK_UTXO_LIMIT) {
const maxCompoundableAmount = sompiToKas(
utxos.slice(0, NETWORK_UTXO_LIMIT).reduce((acc, utxo) => {
return acc + utxo.amount;
}, 0),
);
notifications.show({
title: 'Error',
color: 'red',
message: `You have too many UTXOs to send this amount. Please compound first by sending KAS to your address. Maximum sendable without compounding (including fee): ${maxCompoundableAmount}`,
autoClose: false,
loading: false,
});
setCanSendAmount(false);
} else if (hasEnough) {
let changeAmount = utxoTotalAmount - kasToSompi(amount);
if (!includeFeeInAmount) {
changeAmount -= feeCalcResult;
}
} else if (deviceType === 'usb') {
const {
hasEnough,
fee: feeCalcResult,
total: utxoTotalAmount,
} = selectUtxos(kasToSompi(amount), props.addressContext.utxos, includeFeeInAmount);

if (hasEnough) {
let changeAmount = utxoTotalAmount - kasToSompi(amount);
if (!includeFeeInAmount) {
changeAmount -= feeCalcResult;
}

let expectedFee = feeCalcResult;
// The change is added to the fee if it's less than 0.0001 KAS
console.info('changeAmount', changeAmount);
if (changeAmount < 10000) {
console.info(`Adding dust change ${changeAmount} sompi to fee`);
expectedFee += changeAmount;
}
let expectedFee = feeCalcResult;
// The change is added to the fee if it's less than 0.0001 KAS
console.info('changeAmount', changeAmount);
if (changeAmount < 10000) {
console.info(`Adding dust change ${changeAmount} sompi to fee`);
expectedFee += changeAmount;
}

calculatedFee = sompiToKas(expectedFee);
const afterFeeDisplay = sompiToKas(kasToSompi(amount) - expectedFee);
setCanSendAmount(true);
if (includeFeeInAmount) {
setAmountDescription(`Amount after fee: ${afterFeeDisplay}`);
}
} else {
setCanSendAmount(false);
calculatedFee = sompiToKas(expectedFee);
const afterFeeDisplay = sompiToKas(kasToSompi(amount) - expectedFee);
setCanSendAmount(true);
if (includeFeeInAmount) {
setAmountDescription(`Amount after fee: ${afterFeeDisplay}`);
}
} else {
setCanSendAmount(false);
}

if (fee === '-' || fee !== calculatedFee) {
Expand Down
2 changes: 2 additions & 0 deletions lib/kaspa-util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -181,3 +181,5 @@ export function kasToSompi(amount: number) {
throw new Error('Invalid amount');
}
}

export const NETWORK_UTXO_LIMIT = 84;

0 comments on commit 29d1f93

Please sign in to comment.