diff --git a/public/assets/avatars/stx20-avatar-icon.png b/public/assets/avatars/stx20-avatar-icon.png
new file mode 100644
index 00000000000..7a686f283d5
Binary files /dev/null and b/public/assets/avatars/stx20-avatar-icon.png differ
diff --git a/src/app/common/transactions/bitcoin/coinselect/local-coin-selection.spec.ts b/src/app/common/transactions/bitcoin/coinselect/local-coin-selection.spec.ts
index efe3f638dcb..3403cd95625 100644
--- a/src/app/common/transactions/bitcoin/coinselect/local-coin-selection.spec.ts
+++ b/src/app/common/transactions/bitcoin/coinselect/local-coin-selection.spec.ts
@@ -21,25 +21,74 @@ const demoUtxos = [
{ value: 909 },
];
+function generate10kSpendWithDummyUtxoSet(recipient: string) {
+ return determineUtxosForSpend({
+ utxos: demoUtxos as any,
+ amount: 10_000,
+ feeRate: 20,
+ recipient,
+ });
+}
+
describe(determineUtxosForSpend.name, () => {
- function generate10kSpendWithTestData(recipient: string) {
- return determineUtxosForSpend({
- utxos: demoUtxos as any,
- amount: 10_000,
- feeRate: 20,
- recipient,
+ describe('Estimated size', () => {
+ test('that Native Segwit, 1 input 2 outputs weighs 140 vBytes', () => {
+ const estimation = determineUtxosForSpend({
+ utxos: [{ value: 50_000 }] as any[],
+ amount: 40_000,
+ recipient: 'tb1qt28eagxcl9gvhq2rpj5slg7dwgxae2dn2hk93m',
+ feeRate: 20,
+ });
+ console.log(estimation);
+ expect(estimation.txVBytes).toBeGreaterThan(140);
+ expect(estimation.txVBytes).toBeLessThan(142);
+ });
+
+ test('that Native Segwit, 2 input 2 outputs weighs 200vBytes', () => {
+ const estimation = determineUtxosForSpend({
+ utxos: [{ value: 50_000 }, { value: 50_000 }] as any[],
+ amount: 60_000,
+ recipient: 'tb1qt28eagxcl9gvhq2rpj5slg7dwgxae2dn2hk93m',
+ feeRate: 20,
+ });
+ console.log(estimation);
+ expect(estimation.txVBytes).toBeGreaterThan(208);
+ expect(estimation.txVBytes).toBeLessThan(209);
});
- }
- describe('sorting algorithm (biggest first and no dust)', () => {
+ test('that Native Segwit, 10 input 2 outputs weighs 200vBytes', () => {
+ const estimation = determineUtxosForSpend({
+ utxos: [
+ { value: 20_000 },
+ { value: 20_000 },
+ { value: 10_000 },
+ { value: 10_000 },
+ { value: 10_000 },
+ { value: 10_000 },
+ { value: 10_000 },
+ { value: 10_000 },
+ { value: 10_000 },
+ { value: 10_000 },
+ ] as any[],
+ amount: 100_000,
+ recipient: 'tb1qt28eagxcl9gvhq2rpj5slg7dwgxae2dn2hk93m',
+ feeRate: 20,
+ });
+ expect(estimation.txVBytes).toBeGreaterThan(750);
+ expect(estimation.txVBytes).toBeLessThan(751);
+ });
+ });
+
+ describe('sorting algorithm', () => {
test('that it filters out dust utxos', () => {
- const result = generate10kSpendWithTestData('tb1qt28eagxcl9gvhq2rpj5slg7dwgxae2dn2hk93m');
+ const result = generate10kSpendWithDummyUtxoSet('tb1qt28eagxcl9gvhq2rpj5slg7dwgxae2dn2hk93m');
+ console.log(result);
const hasDust = result.filteredUtxos.some(utxo => utxo.value <= BTC_P2WPKH_DUST_AMOUNT);
expect(hasDust).toBeFalsy();
});
test('that it sorts utxos in decending order', () => {
- const result = generate10kSpendWithTestData('tb1qt28eagxcl9gvhq2rpj5slg7dwgxae2dn2hk93m');
+ const result = generate10kSpendWithDummyUtxoSet('tb1qt28eagxcl9gvhq2rpj5slg7dwgxae2dn2hk93m');
result.inputs.forEach((u, i) => {
const nextUtxo = result.inputs[i + 1];
if (!nextUtxo) return;
@@ -50,29 +99,29 @@ describe(determineUtxosForSpend.name, () => {
test('that it accepts a wrapped segwit address', () =>
expect(() =>
- generate10kSpendWithTestData('33SVjoCHJovrXxjDKLFSXo1h3t5KgkPzfH')
+ generate10kSpendWithDummyUtxoSet('33SVjoCHJovrXxjDKLFSXo1h3t5KgkPzfH')
).not.toThrowError());
test('that it accepts a legacy addresses', () =>
expect(() =>
- generate10kSpendWithTestData('15PyZveQd28E2SHZu2ugkWZBp6iER41vXj')
+ generate10kSpendWithDummyUtxoSet('15PyZveQd28E2SHZu2ugkWZBp6iER41vXj')
).not.toThrowError());
test('that it throws an error with non-legit address', () => {
expect(() =>
- generate10kSpendWithTestData('whoop-de-da-boop-da-de-not-a-bitcoin-address')
+ generate10kSpendWithDummyUtxoSet('whoop-de-da-boop-da-de-not-a-bitcoin-address')
).toThrowError();
});
test('that given a set of utxos, legacy is more expensive', () => {
- const legacy = generate10kSpendWithTestData('15PyZveQd28E2SHZu2ugkWZBp6iER41vXj');
- const segwit = generate10kSpendWithTestData('33SVjoCHJovrXxjDKLFSXo1h3t5KgkPzfH');
+ const legacy = generate10kSpendWithDummyUtxoSet('15PyZveQd28E2SHZu2ugkWZBp6iER41vXj');
+ const segwit = generate10kSpendWithDummyUtxoSet('33SVjoCHJovrXxjDKLFSXo1h3t5KgkPzfH');
expect(legacy.fee).toBeGreaterThan(segwit.fee);
});
test('that given a set of utxos, wrapped segwit is more expensive than native', () => {
- const segwit = generate10kSpendWithTestData('33SVjoCHJovrXxjDKLFSXo1h3t5KgkPzfH');
- const native = generate10kSpendWithTestData('tb1qt28eagxcl9gvhq2rpj5slg7dwgxae2dn2hk93m');
+ const segwit = generate10kSpendWithDummyUtxoSet('33SVjoCHJovrXxjDKLFSXo1h3t5KgkPzfH');
+ const native = generate10kSpendWithDummyUtxoSet('tb1qt28eagxcl9gvhq2rpj5slg7dwgxae2dn2hk93m');
expect(segwit.fee).toBeGreaterThan(native.fee);
});
@@ -80,8 +129,8 @@ describe(determineUtxosForSpend.name, () => {
// Non-obvious behaviour.
// P2TR outputs = 34 vBytes
// P2WPKH outputs = 22 vBytes
- const native = generate10kSpendWithTestData('tb1qt28eagxcl9gvhq2rpj5slg7dwgxae2dn2hk93m');
- const taproot = generate10kSpendWithTestData(
+ const native = generate10kSpendWithDummyUtxoSet('tb1qt28eagxcl9gvhq2rpj5slg7dwgxae2dn2hk93m');
+ const taproot = generate10kSpendWithDummyUtxoSet(
'tb1parwmj7533de3k2fw2kntyqacspvhm67qnjcmpqnnpfvzu05l69nsczdywd'
);
expect(taproot.fee).toBeGreaterThan(native.fee);
diff --git a/src/app/common/transactions/bitcoin/coinselect/local-coin-selection.ts b/src/app/common/transactions/bitcoin/coinselect/local-coin-selection.ts
index 342390eb336..711c73e18a3 100644
--- a/src/app/common/transactions/bitcoin/coinselect/local-coin-selection.ts
+++ b/src/app/common/transactions/bitcoin/coinselect/local-coin-selection.ts
@@ -1,13 +1,15 @@
+import BigNumber from 'bignumber.js';
import { validate } from 'bitcoin-address-validation';
import type { RpcSendTransferRecipient } from '@shared/rpc/methods/send-transfer';
+import { sumNumbers } from '@app/common/math/helpers';
import { UtxoResponseItem } from '@app/query/bitcoin/bitcoin-client';
import {
filterUneconomicalUtxos,
filterUneconomicalUtxosMultipleRecipients,
- getSizeInfo,
+ getBitcoinTxSizeEstimation,
getSizeInfoMultipleRecipients,
} from '../utils';
@@ -33,9 +35,9 @@ export function determineUtxosForSpendAll({
if (!validate(recipient)) throw new Error('Cannot calculate spend of invalid address type');
const filteredUtxos = filterUneconomicalUtxos({ utxos, feeRate, address: recipient });
- const sizeInfo = getSizeInfo({
- inputLength: filteredUtxos.length,
- outputLength: 1,
+ const sizeInfo = getBitcoinTxSizeEstimation({
+ inputCount: filteredUtxos.length,
+ outputCount: 1,
recipient,
});
@@ -52,6 +54,10 @@ export function determineUtxosForSpendAll({
};
}
+function getUtxoTotal(utxos: UtxoResponseItem[]) {
+ return sumNumbers(utxos.map(utxo => utxo.value));
+}
+
export function determineUtxosForSpend({
amount,
feeRate,
@@ -60,47 +66,59 @@ export function determineUtxosForSpend({
}: DetermineUtxosForSpendArgs) {
if (!validate(recipient)) throw new Error('Cannot calculate spend of invalid address type');
- const orderedUtxos = utxos.sort((a, b) => b.value - a.value);
-
- const filteredUtxos = filterUneconomicalUtxos({
- utxos: orderedUtxos,
+ const filteredUtxos: UtxoResponseItem[] = filterUneconomicalUtxos({
+ utxos: utxos.sort((a, b) => b.value - a.value),
feeRate,
address: recipient,
});
- const neededUtxos = [];
- let sum = 0n;
- let sizeInfo = null;
+ if (!filteredUtxos.length) throw new InsufficientFundsError();
- for (const utxo of filteredUtxos) {
- sizeInfo = getSizeInfo({
- inputLength: neededUtxos.length,
- outputLength: 2,
+ // Prepopulate with first UTXO, at least one is needed
+ const neededUtxos: UtxoResponseItem[] = [filteredUtxos[0]];
+
+ function estimateTransactionSize() {
+ return getBitcoinTxSizeEstimation({
+ inputCount: neededUtxos.length,
+ outputCount: 2,
recipient,
});
- if (sum >= BigInt(amount) + BigInt(Math.ceil(sizeInfo.txVBytes * feeRate))) break;
+ }
- sum += BigInt(utxo.value);
- neededUtxos.push(utxo);
+ function hasSufficientUtxosForTx() {
+ const txEstimation = estimateTransactionSize();
+ const neededAmount = new BigNumber(txEstimation.txVBytes * feeRate).plus(amount);
+ return getUtxoTotal(neededUtxos).isGreaterThanOrEqualTo(neededAmount);
}
- if (!sizeInfo) throw new InsufficientFundsError();
+ function getRemainingUnspentUtxos() {
+ return filteredUtxos.filter(utxo => !neededUtxos.includes(utxo));
+ }
- const fee = Math.ceil(sizeInfo.txVBytes * feeRate);
+ while (!hasSufficientUtxosForTx()) {
+ const [nextUtxo] = getRemainingUnspentUtxos();
+ if (!nextUtxo) throw new InsufficientFundsError();
+ neededUtxos.push(nextUtxo);
+ }
+
+ const fee = Math.ceil(
+ new BigNumber(estimateTransactionSize().txVBytes).multipliedBy(feeRate).toNumber()
+ );
const outputs = [
// outputs[0] = the desired amount going to recipient
{ value: BigInt(amount), address: recipient },
// outputs[1] = the remainder to be returned to a change address
- { value: sum - BigInt(amount) - BigInt(fee) },
+ { value: BigInt(getUtxoTotal(neededUtxos).toString()) - BigInt(amount) - BigInt(fee) },
];
return {
filteredUtxos,
inputs: neededUtxos,
outputs,
- size: sizeInfo.txVBytes,
+ size: estimateTransactionSize().txVBytes,
fee,
+ ...estimateTransactionSize(),
};
}
diff --git a/src/app/common/transactions/bitcoin/use-generate-bitcoin-tx.ts b/src/app/common/transactions/bitcoin/use-generate-bitcoin-tx.ts
index 0b4a68eacc6..b90640fc5b2 100644
--- a/src/app/common/transactions/bitcoin/use-generate-bitcoin-tx.ts
+++ b/src/app/common/transactions/bitcoin/use-generate-bitcoin-tx.ts
@@ -166,7 +166,7 @@ export function useGenerateUnsignedNativeSegwitMultipleRecipientsTx() {
tx.addOutputAddress(output.address, BigInt(output.value), networkMode);
});
- return { hex: tx.hex, fee, psbt: tx.toPSBT(), inputs };
+ return { hex: tx.hex, fee: fee, psbt: tx.toPSBT(), inputs };
} catch (e) {
// eslint-disable-next-line no-console
console.log('Error signing bitcoin transaction', e);
diff --git a/src/app/common/transactions/bitcoin/utils.ts b/src/app/common/transactions/bitcoin/utils.ts
index 4a2845ed9e7..ae47d2c3a37 100644
--- a/src/app/common/transactions/bitcoin/utils.ts
+++ b/src/app/common/transactions/bitcoin/utils.ts
@@ -34,9 +34,9 @@ export function getSpendableAmount({
}) {
const balance = utxos.map(utxo => utxo.value).reduce((prevVal, curVal) => prevVal + curVal, 0);
- const size = getSizeInfo({
- inputLength: utxos.length,
- outputLength: 1,
+ const size = getBitcoinTxSizeEstimation({
+ inputCount: utxos.length,
+ outputCount: 1,
recipient: address,
});
const fee = Math.ceil(size.txVBytes * feeRate);
@@ -80,12 +80,12 @@ export function filterUneconomicalUtxos({
return filteredUtxos;
}
-export function getSizeInfo(payload: {
- inputLength: number;
- outputLength: number;
+export function getBitcoinTxSizeEstimation(payload: {
+ inputCount: number;
+ outputCount: number;
recipient: string;
}) {
- const { inputLength, recipient, outputLength } = payload;
+ const { inputCount, recipient, outputCount } = payload;
const addressInfo = validate(recipient) ? getAddressInfo(recipient) : null;
const outputAddressTypeWithFallback = addressInfo ? addressInfo.type : 'p2wpkh';
@@ -93,9 +93,9 @@ export function getSizeInfo(payload: {
const sizeInfo = txSizer.calcTxSize({
// Only p2wpkh is supported by the wallet
input_script: 'p2wpkh',
- input_count: inputLength,
+ input_count: inputCount,
// From the address of the recipient, we infer the output type
- [outputAddressTypeWithFallback + '_output_count']: outputLength,
+ [outputAddressTypeWithFallback + '_output_count']: outputCount,
});
return sizeInfo;
diff --git a/src/app/components/crypto-assets/bitcoin/src20-token-asset-list/src20-token-asset-list.tsx b/src/app/components/crypto-assets/bitcoin/src20-token-asset-list/src20-token-asset-list.tsx
index 022aec7a158..706537735a8 100644
--- a/src/app/components/crypto-assets/bitcoin/src20-token-asset-list/src20-token-asset-list.tsx
+++ b/src/app/components/crypto-assets/bitcoin/src20-token-asset-list/src20-token-asset-list.tsx
@@ -3,10 +3,10 @@ import type { Src20Token } from '@app/query/bitcoin/stamps/stamps-by-address.que
import { Src20TokenAssetItemLayout } from './src20-token-asset-item.layout';
interface Src20TokenAssetListProps {
- src20Tokens: Src20Token[];
+ tokens: Src20Token[];
}
-export function Src20TokenAssetList({ src20Tokens }: Src20TokenAssetListProps) {
- return src20Tokens.map((token, i) => (
+export function Src20TokenAssetList({ tokens }: Src20TokenAssetListProps) {
+ return tokens.map((token, i) => (
));
}
diff --git a/src/app/components/crypto-assets/stacks/stx20-token-asset-list/stx20-token-asset-item.layout.tsx b/src/app/components/crypto-assets/stacks/stx20-token-asset-list/stx20-token-asset-item.layout.tsx
new file mode 100644
index 00000000000..6cc4b7d2f1d
--- /dev/null
+++ b/src/app/components/crypto-assets/stacks/stx20-token-asset-list/stx20-token-asset-item.layout.tsx
@@ -0,0 +1,37 @@
+import { styled } from 'leather-styles/jsx';
+
+import { formatBalance } from '@app/common/format-balance';
+import type { Stx20Token } from '@app/query/stacks/stacks-client';
+import { Stx20AvatarIcon } from '@app/ui/components/avatar/stx20-avatar-icon';
+import { ItemLayout } from '@app/ui/components/item-layout/item-layout';
+import { BasicTooltip } from '@app/ui/components/tooltip/basic-tooltip';
+import { Pressable } from '@app/ui/pressable/pressable';
+
+interface Stx20TokenAssetItemLayoutProps {
+ token: Stx20Token;
+}
+export function Stx20TokenAssetItemLayout({ token }: Stx20TokenAssetItemLayoutProps) {
+ const balanceAsString = token.balance.amount.toString();
+ const formattedBalance = formatBalance(balanceAsString);
+
+ return (
+
+ }
+ titleLeft={token.tokenData.ticker}
+ captionLeft="STX-20"
+ titleRight={
+
+
+ {formattedBalance.value}
+
+
+ }
+ />
+
+ );
+}
diff --git a/src/app/components/crypto-assets/stacks/stx20-token-asset-list/stx20-token-asset-list.tsx b/src/app/components/crypto-assets/stacks/stx20-token-asset-list/stx20-token-asset-list.tsx
new file mode 100644
index 00000000000..18462051c5c
--- /dev/null
+++ b/src/app/components/crypto-assets/stacks/stx20-token-asset-list/stx20-token-asset-list.tsx
@@ -0,0 +1,12 @@
+import type { Stx20Token } from '@app/query/stacks/stacks-client';
+
+import { Stx20TokenAssetItemLayout } from './stx20-token-asset-item.layout';
+
+interface Stx20TokenAssetListProps {
+ tokens: Stx20Token[];
+}
+export function Stx20TokenAssetList({ tokens }: Stx20TokenAssetListProps) {
+ return tokens.map((token, i) => (
+
+ ));
+}
diff --git a/src/app/components/loaders/brc20-tokens-loader.tsx b/src/app/components/loaders/brc20-tokens-loader.tsx
index b5769cd74da..cbc9af4a770 100644
--- a/src/app/components/loaders/brc20-tokens-loader.tsx
+++ b/src/app/components/loaders/brc20-tokens-loader.tsx
@@ -2,9 +2,9 @@ import { Brc20Token } from '@app/query/bitcoin/bitcoin-client';
import { useBrc20Tokens } from '@app/query/bitcoin/ordinals/brc20/brc20-tokens.hooks';
interface Brc20TokensLoaderProps {
- children(brc20Tokens: Brc20Token[]): React.ReactNode;
+ children(tokens: Brc20Token[]): React.ReactNode;
}
export function Brc20TokensLoader({ children }: Brc20TokensLoaderProps) {
- const brc20Tokens = useBrc20Tokens();
- return children(brc20Tokens);
+ const tokens = useBrc20Tokens();
+ return children(tokens);
}
diff --git a/src/app/components/loaders/src20-tokens-loader.tsx b/src/app/components/loaders/src20-tokens-loader.tsx
index 170fdbfc3d1..1ce829f6beb 100644
--- a/src/app/components/loaders/src20-tokens-loader.tsx
+++ b/src/app/components/loaders/src20-tokens-loader.tsx
@@ -3,9 +3,9 @@ import type { Src20Token } from '@app/query/bitcoin/stamps/stamps-by-address.que
interface Src20TokensLoaderProps {
address: string;
- children(src20Tokens: Src20Token[]): React.ReactNode;
+ children(tokens: Src20Token[]): React.ReactNode;
}
export function Src20TokensLoader({ address, children }: Src20TokensLoaderProps) {
- const { data: src20Tokens = [] } = useSrc20TokensByAddress(address);
- return children(src20Tokens);
+ const { data: tokens = [] } = useSrc20TokensByAddress(address);
+ return children(tokens);
}
diff --git a/src/app/components/loaders/stx20-tokens-loader.tsx b/src/app/components/loaders/stx20-tokens-loader.tsx
new file mode 100644
index 00000000000..986d214f6e6
--- /dev/null
+++ b/src/app/components/loaders/stx20-tokens-loader.tsx
@@ -0,0 +1,11 @@
+import type { Stx20Token } from '@app/query/stacks/stacks-client';
+import { useStx20Tokens } from '@app/query/stacks/stx20/stx20-tokens.hooks';
+
+interface Stx20TokensLoaderProps {
+ address: string;
+ children(tokens: Stx20Token[]): React.ReactNode;
+}
+export function Stx20TokensLoader({ address, children }: Stx20TokensLoaderProps) {
+ const { data: tokens = [] } = useStx20Tokens(address);
+ return children(tokens);
+}
diff --git a/src/app/features/asset-list/asset-list.tsx b/src/app/features/asset-list/asset-list.tsx
index 4963ad8bcd9..0324823ece5 100644
--- a/src/app/features/asset-list/asset-list.tsx
+++ b/src/app/features/asset-list/asset-list.tsx
@@ -10,8 +10,16 @@ import {
BitcoinTaprootAccountLoader,
} from '@app/components/account/bitcoin-account-loader';
import { BitcoinContractEntryPoint } from '@app/components/bitcoin-contract-entry-point/bitcoin-contract-entry-point';
+import { Brc20TokenAssetList } from '@app/components/crypto-assets/bitcoin/brc20-token-asset-list/brc20-token-asset-list';
+import { RunesAssetList } from '@app/components/crypto-assets/bitcoin/runes-asset-list/runes-asset-list';
+import { Src20TokenAssetList } from '@app/components/crypto-assets/bitcoin/src20-token-asset-list/src20-token-asset-list';
import { CryptoCurrencyAssetItemLayout } from '@app/components/crypto-assets/crypto-currency-asset/crypto-currency-asset-item.layout';
+import { Stx20TokenAssetList } from '@app/components/crypto-assets/stacks/stx20-token-asset-list/stx20-token-asset-list';
+import { Brc20TokensLoader } from '@app/components/loaders/brc20-tokens-loader';
+import { RunesLoader } from '@app/components/loaders/runes-loader';
+import { Src20TokensLoader } from '@app/components/loaders/src20-tokens-loader';
import { CurrentStacksAccountLoader } from '@app/components/loaders/stacks-account-loader';
+import { Stx20TokensLoader } from '@app/components/loaders/stx20-tokens-loader';
import { useHasBitcoinLedgerKeychain } from '@app/store/accounts/blockchain/bitcoin/bitcoin.ledger';
import { useCurrentAccountNativeSegwitAddressIndexZero } from '@app/store/accounts/blockchain/bitcoin/native-segwit-account.hooks';
import { useCurrentNetwork } from '@app/store/networks/networks.selectors';
@@ -20,7 +28,6 @@ import { BtcAvatarIcon } from '@app/ui/components/avatar/btc-avatar-icon';
import { Collectibles } from '../collectibles/collectibles';
import { PendingBrc20TransferList } from '../pending-brc-20-transfers/pending-brc-20-transfers';
import { AddStacksLedgerKeysItem } from './components/add-stacks-ledger-keys-item';
-import { BitcoinFungibleTokenAssetList } from './components/bitcoin-fungible-tokens-asset-list';
import { ConnectLedgerAssetBtn } from './components/connect-ledger-asset-button';
import { StacksBalanceListItem } from './components/stacks-balance-list-item';
import { StacksFungibleTokenAssetList } from './components/stacks-fungible-token-asset-list';
@@ -74,6 +81,9 @@ export function AssetsList() {
<>
+
+ {tokens => }
+
>
)}
@@ -82,10 +92,17 @@ export function AssetsList() {
{nativeSegwitAccount => (
{taprootAccount => (
-
+ <>
+
+ {tokens => }
+
+
+ {tokens => }
+
+
+ {runes => }
+
+ >
)}
)}
diff --git a/src/app/features/asset-list/components/bitcoin-fungible-tokens-asset-list.tsx b/src/app/features/asset-list/components/bitcoin-fungible-tokens-asset-list.tsx
deleted file mode 100644
index ceacfafe001..00000000000
--- a/src/app/features/asset-list/components/bitcoin-fungible-tokens-asset-list.tsx
+++ /dev/null
@@ -1,29 +0,0 @@
-import { Brc20TokenAssetList } from '@app/components/crypto-assets/bitcoin/brc20-token-asset-list/brc20-token-asset-list';
-import { RunesAssetList } from '@app/components/crypto-assets/bitcoin/runes-asset-list/runes-asset-list';
-import { Src20TokenAssetList } from '@app/components/crypto-assets/bitcoin/src20-token-asset-list/src20-token-asset-list';
-import { Brc20TokensLoader } from '@app/components/loaders/brc20-tokens-loader';
-import { RunesLoader } from '@app/components/loaders/runes-loader';
-import { Src20TokensLoader } from '@app/components/loaders/src20-tokens-loader';
-
-interface BitcoinFungibleTokenAssetListProps {
- btcAddressNativeSegwit: string;
- btcAddressTaproot: string;
-}
-export function BitcoinFungibleTokenAssetList({
- btcAddressNativeSegwit,
- btcAddressTaproot,
-}: BitcoinFungibleTokenAssetListProps) {
- return (
- <>
-
- {brc20Tokens => }
-
-
- {src20Tokens => }
-
-
- {runes => }
-
- >
- );
-}
diff --git a/src/app/features/dialogs/increase-fee-dialog/hooks/use-btc-increase-fee.ts b/src/app/features/dialogs/increase-fee-dialog/hooks/use-btc-increase-fee.ts
index 48d2a3528c3..91d9666d902 100644
--- a/src/app/features/dialogs/increase-fee-dialog/hooks/use-btc-increase-fee.ts
+++ b/src/app/features/dialogs/increase-fee-dialog/hooks/use-btc-increase-fee.ts
@@ -1,3 +1,4 @@
+import { useMemo } from 'react';
import { useNavigate } from 'react-router-dom';
import * as btc from '@scure/btc-signer';
@@ -14,9 +15,9 @@ import { useBtcAssetBalance } from '@app/common/hooks/balance/btc/use-btc-balanc
import { btcToSat } from '@app/common/money/unit-conversion';
import { queryClient } from '@app/common/persistence';
import {
+ getBitcoinTxSizeEstimation,
getBitcoinTxValue,
getRecipientAddressFromOutput,
- getSizeInfo,
} from '@app/common/transactions/bitcoin/utils';
import { MAX_FEE_RATE_MULTIPLIER } from '@app/components/bitcoin-custom-fee/hooks/use-bitcoin-custom-fee';
import { useBitcoinFeesList } from '@app/components/bitcoin-fees-list/use-bitcoin-fees-list';
@@ -39,11 +40,16 @@ export function useBtcIncreaseFee(btcTx: BitcoinTx) {
const signTransaction = useSignBitcoinTx();
const { broadcastTx, isBroadcasting } = useBitcoinBroadcastTransaction();
const recipient = getRecipientAddressFromOutput(btcTx.vout, currentBitcoinAddress) || '';
- const sizeInfo = getSizeInfo({
- inputLength: btcTx.vin.length,
- recipient,
- outputLength: btcTx.vout.length,
- });
+
+ const sizeInfo = useMemo(
+ () =>
+ getBitcoinTxSizeEstimation({
+ inputCount: btcTx.vin.length,
+ recipient,
+ outputCount: btcTx.vout.length,
+ }),
+ [btcTx.vin.length, btcTx.vout.length, recipient]
+ );
const { btcAvailableAssetBalance } = useBtcAssetBalance(currentBitcoinAddress);
const sendingAmount = getBitcoinTxValue(currentBitcoinAddress, btcTx);
diff --git a/src/app/features/ledger/animations/plugging-in-cable.lottie.tsx b/src/app/features/ledger/animations/plugging-in-cable.lottie.tsx
index 59604db514a..ceb12981511 100644
--- a/src/app/features/ledger/animations/plugging-in-cable.lottie.tsx
+++ b/src/app/features/ledger/animations/plugging-in-cable.lottie.tsx
@@ -25,8 +25,8 @@ export default function PluggingInLedgerCableAnimation(props: BoxProps) {
return (
-
-
+
+
);
diff --git a/src/app/pages/rpc-sign-psbt/use-rpc-sign-psbt.tsx b/src/app/pages/rpc-sign-psbt/use-rpc-sign-psbt.tsx
index 79963033b10..7dbdcec1373 100644
--- a/src/app/pages/rpc-sign-psbt/use-rpc-sign-psbt.tsx
+++ b/src/app/pages/rpc-sign-psbt/use-rpc-sign-psbt.tsx
@@ -73,9 +73,7 @@ export function useRpcSignPsbt() {
txValue: formatMoney(transferTotalAsMoney),
};
- navigate(RouteUrls.RpcSignPsbtSummary, {
- state: psbtTxSummaryState,
- });
+ navigate(RouteUrls.RpcSignPsbtSummary, { state: psbtTxSummaryState });
},
onError(e) {
navigate(RouteUrls.RequestError, {
diff --git a/src/app/pages/send/ordinal-inscription/coinselect/select-inscription-coins.spec.ts b/src/app/pages/send/ordinal-inscription/coinselect/select-inscription-coins.spec.ts
index 67978daf466..0a66c13d460 100644
--- a/src/app/pages/send/ordinal-inscription/coinselect/select-inscription-coins.spec.ts
+++ b/src/app/pages/send/ordinal-inscription/coinselect/select-inscription-coins.spec.ts
@@ -37,7 +37,7 @@ describe(selectTaprootInscriptionTransferCoins.name, () => {
Number(inscriptionInputAmount)
);
- expect(result.txFee).toEqual(5048);
+ expect(result.txFee).toEqual(6608);
});
test('when there are not enough utxo to cover fee', () => {
diff --git a/src/app/pages/send/ordinal-inscription/coinselect/select-inscription-coins.ts b/src/app/pages/send/ordinal-inscription/coinselect/select-inscription-coins.ts
index 51ac5736e97..eab76a0ca44 100644
--- a/src/app/pages/send/ordinal-inscription/coinselect/select-inscription-coins.ts
+++ b/src/app/pages/send/ordinal-inscription/coinselect/select-inscription-coins.ts
@@ -37,10 +37,11 @@ export function selectTaprootInscriptionTransferCoins(
const txSizer = new BtcSizeFeeEstimator();
const initialTxSize = txSizer.calcTxSize({
- input_script: 'p2tr',
+ input_script: 'p2wpkh',
input_count: 1,
// From the address of the recipient, we infer the output type
p2tr_output_count: 1,
+ p2wpkh_output_count: 1,
});
const neededInputs: UtxoResponseItem[] = [];
@@ -65,9 +66,10 @@ export function selectTaprootInscriptionTransferCoins(
if (nextUtxo) neededInputs.push(nextUtxo);
utxos = remainingUtxos;
txSize = txSizer.calcTxSize({
- input_script: 'p2tr',
+ input_script: 'p2wpkh',
input_count: neededInputs.length + 1,
p2tr_output_count: 1,
+ p2wpkh_output_count: 1,
});
indexCounter.increment();
}
diff --git a/src/app/pages/send/ordinal-inscription/hooks/use-generate-ordinal-tx.ts b/src/app/pages/send/ordinal-inscription/hooks/use-generate-ordinal-tx.ts
index cff9efcdab6..7a7258f003a 100644
--- a/src/app/pages/send/ordinal-inscription/hooks/use-generate-ordinal-tx.ts
+++ b/src/app/pages/send/ordinal-inscription/hooks/use-generate-ordinal-tx.ts
@@ -104,7 +104,7 @@ export function useGenerateUnsignedOrdinalTx(inscriptionInput: UtxoWithDerivatio
if (e instanceof InsufficientFundsError) {
throw new InsufficientFundsError();
}
- logger.error('Unable to sign transaction');
+ logger.error('Unable to sign transaction', e);
return null;
}
}
@@ -159,7 +159,7 @@ export function useGenerateUnsignedOrdinalTx(inscriptionInput: UtxoWithDerivatio
if (e instanceof InsufficientFundsError) {
throw new InsufficientFundsError();
}
- logger.error('Unable to sign transaction');
+ logger.error('Unable to sign transaction', e);
return null;
}
}
diff --git a/src/app/pages/send/send-crypto-asset-form/form/btc/btc-choose-fee.tsx b/src/app/pages/send/send-crypto-asset-form/form/btc/btc-choose-fee.tsx
index 0104ce58810..864fac42002 100644
--- a/src/app/pages/send/send-crypto-asset-form/form/btc/btc-choose-fee.tsx
+++ b/src/app/pages/send/send-crypto-asset-form/form/btc/btc-choose-fee.tsx
@@ -17,7 +17,6 @@ export function useBtcChooseFeeState() {
const isSendingMax = useLocationStateWithCache('isSendingMax') as boolean;
const txValues = useLocationStateWithCache('values') as BitcoinSendFormValues;
const utxos = useLocationStateWithCache('utxos') as UtxoResponseItem[];
-
return { isSendingMax, txValues, utxos };
}
diff --git a/src/app/pages/send/send-crypto-asset-form/form/btc/btc-send-form-confirmation.tsx b/src/app/pages/send/send-crypto-asset-form/form/btc/btc-send-form-confirmation.tsx
index 7d0f68256fe..00b9fb55e04 100644
--- a/src/app/pages/send/send-crypto-asset-form/form/btc/btc-send-form-confirmation.tsx
+++ b/src/app/pages/send/send-crypto-asset-form/form/btc/btc-send-form-confirmation.tsx
@@ -1,3 +1,4 @@
+import { useMemo } from 'react';
import { useLocation, useNavigate } from 'react-router-dom';
import { hexToBytes } from '@noble/hashes/utils';
@@ -50,13 +51,52 @@ export function BtcSendFormConfirmation() {
const navigate = useNavigate();
const { tx, recipient, fee, arrivesIn, feeRowValue } = useBtcSendFormConfirmationState();
+ const transaction = useMemo(() => btc.Transaction.fromRaw(hexToBytes(tx)), [tx]);
+ // const inputs = useMemo(() => getPsbtTxInputs(transaction), [transaction]);
+
+ // const inputTransactions = useGetBitcoinTransactionQueries(
+ // inputs
+ // .map(input => input.txid)
+ // .filter(isDefined)
+ // .map(txid => bytesToHex(txid))
+ // );
+
const { refetch } = useCurrentNativeSegwitUtxos();
const analytics = useAnalytics();
const btcMarketData = useCryptoCurrencyMarketDataMeanAverage('BTC');
const { broadcastTx, isBroadcasting } = useBitcoinBroadcastTransaction();
- const transaction = btc.Transaction.fromRaw(hexToBytes(tx));
+ // console.log({ transaction });
+
+ // useMemo(() => {
+ // if (inputTransactions.some(query => !query.data)) return null;
+
+ // const inputTotal = sumNumbers(
+ // inputs
+ // .map((input, index) => inputTransactions[index].data?.vout[input.index ?? 0].value)
+ // .filter(isDefined)
+ // );
+
+ // const outputs = getPsbtTxOutputs(transaction);
+
+ // const outputTotal = sumNumbers(
+ // outputs
+ // .map(output => output.amount)
+ // .filter(isDefined)
+ // .map(val => Number(val))
+ // );
+
+ // // console.log('Presented fee', fee);
+ // // console.log('fee === ', inputTotal.minus(outputTotal).toNumber());
+
+ // console.log('Actual vsize ', transaction.vsize);
+ // console.log('Fee ', fee);
+ // console.log('Fee row value', feeRowValue);
+ // console.log('Sats per vbytes ', new BigNumber(fee).dividedBy(transaction.vsize).toNumber());
+ // }, [fee, feeRowValue, inputTransactions, inputs, transaction]);
+
+ // console.log({ inputs, outputs });
const decodedTx = decodeBitcoinTx(transaction.hex);
diff --git a/src/app/pages/swap/components/swap-review.tsx b/src/app/pages/swap/components/swap-review.tsx
index e4e5e1ab8dd..e99767770a8 100644
--- a/src/app/pages/swap/components/swap-review.tsx
+++ b/src/app/pages/swap/components/swap-review.tsx
@@ -23,7 +23,7 @@ export function SwapReview() {