Skip to content

Commit

Permalink
Merge pull request #4846 from leather-wallet/release/brc-20-api
Browse files Browse the repository at this point in the history
Release/brc 20 api
  • Loading branch information
alter-eggo authored Jan 23, 2024
2 parents dbbab2e + 95b722c commit f0eac36
Show file tree
Hide file tree
Showing 11 changed files with 73 additions and 37 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { useCallback, useMemo } from 'react';

import * as btc from '@scure/btc-signer';

import { extractAddressIndexFromPath } from '@shared/crypto/bitcoin/bitcoin.utils';
import { Money, createMoney } from '@shared/models/money.model';

import { sumNumbers } from '@app/common/math/helpers';
Expand Down Expand Up @@ -35,7 +36,8 @@ export function useGenerateRetrieveTaprootFundsTx() {
const totalAmount = sumNumbers(uninscribedUtxos.map(utxo => utxo.value));

uninscribedUtxos.forEach(utxo => {
const signer = createSigner?.(utxo.addressIndex);
const addressIndex = extractAddressIndexFromPath(utxo.derivationPath);
const signer = createSigner?.(addressIndex);
if (!signer) return;

tx.addInput({
Expand All @@ -61,7 +63,10 @@ export function useGenerateRetrieveTaprootFundsTx() {

tx.addOutputAddress(recipient, paymentAmount, networkMode);

uninscribedUtxos.forEach(utxo => createSigner?.(utxo.addressIndex).sign(tx));
uninscribedUtxos.forEach(utxo => {
const addressIndex = extractAddressIndexFromPath(utxo.derivationPath);
return createSigner?.(addressIndex).sign(tx);
});

tx.finalize();
return tx.hex;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { isDefined } from '@shared/utils';
import { sumNumbers } from '@app/common/math/helpers';
import { BtcSizeFeeEstimator } from '@app/common/transactions/bitcoin/fees/btc-size-fee-estimator';
import { createCounter } from '@app/common/utils/counter';
import { TaprootUtxo, UtxoResponseItem } from '@app/query/bitcoin/bitcoin-client';
import { UtxoResponseItem, UtxoWithDerivationPath } from '@app/query/bitcoin/bitcoin-client';

const idealInscriptionValue = 10_000;

Expand All @@ -22,7 +22,7 @@ interface SelectInscriptionCoinFailure {
type SelectInscriptionCoinResult = SelectInscriptionCoinSuccess | SelectInscriptionCoinFailure;

interface SelectInscriptionTransferCoinsArgs {
inscriptionInput: TaprootUtxo;
inscriptionInput: UtxoWithDerivationPath;
nativeSegwitUtxos: UtxoResponseItem[];
feeRate: number;
recipient: string;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,20 @@
import { BitcoinNetworkModes } from '@shared/constants';
import { getNativeSegwitAddressIndexDerivationPath } from '@shared/crypto/bitcoin/p2wpkh-address-gen';
import { Inscription } from '@shared/models/inscription.model';

import { TaprootUtxo } from '@app/query/bitcoin/bitcoin-client';
import { UtxoWithDerivationPath } from '@app/query/bitcoin/bitcoin-client';

export function createUtxoFromInscription(inscription: Inscription): TaprootUtxo {
interface CreateUtxoFromInscriptionArgs {
inscription: Inscription;
network: BitcoinNetworkModes;
accountIndex: number;
}

export function createUtxoFromInscription({
inscription,
network,
accountIndex,
}: CreateUtxoFromInscriptionArgs): UtxoWithDerivationPath {
const { genesis_block_hash, genesis_timestamp, genesis_block_height, value, addressIndex } =
inscription;

Expand All @@ -16,6 +28,6 @@ export function createUtxoFromInscription(inscription: Inscription): TaprootUtxo
block_time: genesis_timestamp,
},
value: Number(value),
addressIndex,
derivationPath: getNativeSegwitAddressIndexDerivationPath(network, accountIndex, addressIndex),
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ import { AverageBitcoinFeeRates, BtcFeeType } from '@shared/models/fees/bitcoin-
import { SupportedInscription } from '@shared/models/inscription.model';

import { useOnMount } from '@app/common/hooks/use-on-mount';
import { TaprootUtxo } from '@app/query/bitcoin/bitcoin-client';
import { UtxoWithDerivationPath } from '@app/query/bitcoin/bitcoin-client';
import { useCurrentAccountIndex } from '@app/store/accounts/account';
import { useCurrentNetwork } from '@app/store/networks/networks.selectors';

import { useSendInscriptionRouteState } from '../hooks/use-send-inscription-route-state';
import { createUtxoFromInscription } from './create-utxo-from-inscription';
Expand All @@ -18,7 +20,7 @@ interface SendInscriptionContextState {
inscription: SupportedInscription;
selectedFeeType: BtcFeeType;
setSelectedFeeType(value: BtcFeeType | null): void;
utxo: TaprootUtxo;
utxo: UtxoWithDerivationPath;
}
export function useSendInscriptionState() {
const location = useLocation();
Expand All @@ -29,14 +31,22 @@ export function useSendInscriptionState() {
export function SendInscriptionContainer() {
const [selectedFeeType, setSelectedFeeType] = useState<BtcFeeType | null>(null);
const [inscription, setInscription] = useState<SupportedInscription | null>(null);
const [utxo, setUtxo] = useState<TaprootUtxo | null>(null);
const [utxo, setUtxo] = useState<UtxoWithDerivationPath | null>(null);

const routeState = useSendInscriptionRouteState();
const network = useCurrentNetwork();
const currentAccountIndex = useCurrentAccountIndex();

useOnMount(() => {
if (!routeState.inscription) return;
setInscription(routeState.inscription);
setUtxo(createUtxoFromInscription(routeState.inscription));
setUtxo(
createUtxoFromInscription({
inscription: routeState.inscription,
network: network.chain.bitcoin.bitcoinNetwork,
accountIndex: currentAccountIndex,
})
);
});

if (!inscription || !utxo) return null;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,22 @@
import * as btc from '@scure/btc-signer';
import { AddressType, getAddressInfo } from 'bitcoin-address-validation';

import { extractAddressIndexFromPath } from '@shared/crypto/bitcoin/bitcoin.utils';
import { BitcoinInputSigningConfig } from '@shared/crypto/bitcoin/signer-config';
import { logger } from '@shared/logger';
import { OrdinalSendFormValues } from '@shared/models/form.model';

import { determineUtxosForSpend } from '@app/common/transactions/bitcoin/coinselect/local-coin-selection';
import { createCounter } from '@app/common/utils/counter';
import { useCurrentNativeSegwitAccountSpendableUtxos } from '@app/query/bitcoin/address/utxos-by-address.hooks';
import { TaprootUtxo } from '@app/query/bitcoin/bitcoin-client';
import { UtxoWithDerivationPath } from '@app/query/bitcoin/bitcoin-client';
import { useBitcoinScureLibNetworkConfig } from '@app/store/accounts/blockchain/bitcoin/bitcoin-keychain';
import { useCurrentAccountNativeSegwitSigner } from '@app/store/accounts/blockchain/bitcoin/native-segwit-account.hooks';
import { useCurrentAccountTaprootSigner } from '@app/store/accounts/blockchain/bitcoin/taproot-account.hooks';

import { selectInscriptionTransferCoins } from '../coinselect/select-inscription-coins';

export function useGenerateUnsignedOrdinalTx(taprootInput: TaprootUtxo) {
export function useGenerateUnsignedOrdinalTx(inscriptionInput: UtxoWithDerivationPath) {
const createTaprootSigner = useCurrentAccountTaprootSigner();
const createNativeSegwitSigner = useCurrentAccountNativeSegwitSigner();
const networkMode = useBitcoinScureLibNetworkConfig();
Expand All @@ -30,9 +31,8 @@ export function useGenerateUnsignedOrdinalTx(taprootInput: TaprootUtxo) {
}

function formTaprootOrdinalTx(values: OrdinalSendFormValues) {
const inscriptionInput = taprootInput;

const taprootSigner = createTaprootSigner?.(inscriptionInput.addressIndex);
const addressIndex = extractAddressIndexFromPath(inscriptionInput.derivationPath);
const taprootSigner = createTaprootSigner?.(addressIndex);
const nativeSegwitSigner = createNativeSegwitSigner?.(0);

if (!taprootSigner || !nativeSegwitSigner || !nativeSegwitUtxos || !values.feeRate) return;
Expand All @@ -58,13 +58,13 @@ export function useGenerateUnsignedOrdinalTx(taprootInput: TaprootUtxo) {

// Inscription input
tx.addInput({
txid: taprootInput.txid,
index: taprootInput.vout,
txid: inscriptionInput.txid,
index: inscriptionInput.vout,
tapInternalKey: taprootSigner.payment.tapInternalKey,
sequence: 0,
witnessUtxo: {
script: taprootSigner.payment.script,
amount: BigInt(taprootInput.value),
amount: BigInt(inscriptionInput.value),
},
});
signingConfig.push({
Expand Down Expand Up @@ -122,7 +122,7 @@ export function useGenerateUnsignedOrdinalTx(taprootInput: TaprootUtxo) {
const tx = new btc.Transaction();

// Fee-covering Native Segwit inputs
[taprootInput, ...inputs].forEach(input =>
[inscriptionInput, ...inputs].forEach(input =>
tx.addInput({
txid: input.txid,
index: input.vout,
Expand All @@ -135,7 +135,7 @@ export function useGenerateUnsignedOrdinalTx(taprootInput: TaprootUtxo) {
);

// Inscription output
tx.addOutputAddress(values.recipient, BigInt(taprootInput.value), networkMode);
tx.addOutputAddress(values.recipient, BigInt(inscriptionInput.value), networkMode);

// Recipient and change outputs
outputs.forEach(output => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { baseCurrencyAmountInQuote } from '@app/common/money/calculate-money';
import { formatMoneyPadded, i18nFormatCurrency } from '@app/common/money/format-money';
import { FeesListItem } from '@app/components/bitcoin-fees-list/bitcoin-fees-list';
import { useCurrentNativeSegwitUtxos } from '@app/query/bitcoin/address/utxos-by-address.hooks';
import { TaprootUtxo } from '@app/query/bitcoin/bitcoin-client';
import { UtxoWithDerivationPath } from '@app/query/bitcoin/bitcoin-client';
import { useAverageBitcoinFeeRates } from '@app/query/bitcoin/fees/fee-estimates.hooks';
import { useCryptoCurrencyMarketData } from '@app/query/common/market-data/market-data.hooks';
import { useCurrentAccountNativeSegwitSigner } from '@app/store/accounts/blockchain/bitcoin/native-segwit-account.hooks';
Expand All @@ -16,7 +16,7 @@ import { selectInscriptionTransferCoins } from '../coinselect/select-inscription

interface UseSendInscriptionFeesListArgs {
recipient: string;
utxo: TaprootUtxo;
utxo: UtxoWithDerivationPath;
}
export function useSendInscriptionFeesList({ recipient, utxo }: UseSendInscriptionFeesListArgs) {
const createNativeSegwitSigner = useCurrentAccountNativeSegwitSigner();
Expand Down
4 changes: 2 additions & 2 deletions src/app/query/bitcoin/address/utxos-by-address.hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@ import { InscriptionResponseItem } from '@shared/models/inscription.model';

import { useCurrentAccountNativeSegwitIndexZeroSigner } from '@app/store/accounts/blockchain/bitcoin/native-segwit-account.hooks';

import { TaprootUtxo, UtxoResponseItem } from '../bitcoin-client';
import { UtxoResponseItem, UtxoWithDerivationPath } from '../bitcoin-client';
import { useInscriptionsByAddressQuery } from '../ordinals/inscriptions.query';
import { useBitcoinPendingTransactionsInputs } from './transactions-by-address.hooks';
import { useGetUtxosByAddressQuery } from './utxos-by-address.query';

export function filterUtxosWithInscriptions(
inscriptions: InscriptionResponseItem[],
utxos: TaprootUtxo[] | UtxoResponseItem[]
utxos: UtxoWithDerivationPath[] | UtxoResponseItem[]
) {
return utxos.filter(
utxo =>
Expand Down
23 changes: 16 additions & 7 deletions src/app/query/bitcoin/address/utxos-by-address.query.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { useQuery } from '@tanstack/react-query';

import { getTaprootAddress } from '@shared/crypto/bitcoin/bitcoin.utils';
import { getNativeSegwitAddressIndexDerivationPath } from '@shared/crypto/bitcoin/p2wpkh-address-gen';

import { createCounter } from '@app/common/utils/counter';
import { AppUseQueryConfig } from '@app/query/query-config';
Expand All @@ -10,7 +11,7 @@ import { useCurrentTaprootAccount } from '@app/store/accounts/blockchain/bitcoin
import { useBitcoinClient } from '@app/store/common/api-clients.hooks';
import { useCurrentNetwork } from '@app/store/networks/networks.selectors';

import { TaprootUtxo, UtxoResponseItem } from '../bitcoin-client';
import { UtxoResponseItem, UtxoWithDerivationPath } from '../bitcoin-client';
import { hasInscriptions } from './address.utils';

const staleTime = 3 * 60 * 1000;
Expand Down Expand Up @@ -50,7 +51,7 @@ export function useTaprootAccountUtxosQuery() {
async () => {
let currentNumberOfAddressesWithoutUtxos = 0;
const addressIndexCounter = createCounter(0);
let foundUnspentTransactions: TaprootUtxo[] = [];
let foundUnspentTransactions: UtxoWithDerivationPath[] = [];
while (currentNumberOfAddressesWithoutUtxos < stopSearchAfterNumberAddressesWithoutUtxos) {
const address = getTaprootAddress({
index: addressIndexCounter.getValue(),
Expand All @@ -67,11 +68,19 @@ export function useTaprootAccountUtxosQuery() {
}

foundUnspentTransactions = [
...unspentTransactions.map(utxo => ({
// adds addresss index of which utxo belongs
...utxo,
addressIndex: addressIndexCounter.getValue(),
})),
...unspentTransactions.map(utxo => {
const addressIndex = addressIndexCounter.getValue();
return {
// adds addresss index of which utxo belongs
...utxo,
addressIndex,
derivationPath: getNativeSegwitAddressIndexDerivationPath(
network.chain.bitcoin.bitcoinNetwork,
currentAccountIndex,
addressIndex
),
};
}),
...foundUnspentTransactions,
];

Expand Down
4 changes: 2 additions & 2 deletions src/app/query/bitcoin/balance/btc-taproot-balance.hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { sumNumbers } from '@app/common/math/helpers';

import { filterUtxosWithInscriptions } from '../address/utxos-by-address.hooks';
import { useTaprootAccountUtxosQuery } from '../address/utxos-by-address.query';
import { TaprootUtxo } from '../bitcoin-client';
import { UtxoWithDerivationPath } from '../bitcoin-client';
import { useGetInscriptionsInfiniteQuery } from '../ordinals/inscriptions.query';

export function useCurrentTaprootAccountUninscribedUtxos() {
Expand All @@ -19,7 +19,7 @@ export function useCurrentTaprootAccountUninscribedUtxos() {
return filterUtxosWithInscriptions(
inscriptions,
utxos.filter(utxo => utxo.status.confirmed)
) as TaprootUtxo[];
) as UtxoWithDerivationPath[];
}, [query.data?.pages, utxos]);
}

Expand Down
4 changes: 2 additions & 2 deletions src/app/query/bitcoin/bitcoin-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ export interface UtxoResponseItem {
value: number;
}

export interface TaprootUtxo extends UtxoResponseItem {
addressIndex: number;
export interface UtxoWithDerivationPath extends UtxoResponseItem {
derivationPath: string;
}

class AddressApi {
Expand Down
2 changes: 1 addition & 1 deletion src/app/query/bitcoin/ordinalsbot-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ class OrdinalsbotClient {
async order({ receiveAddress, file, fee, size, name }: InscriptionOrderArgs) {
return axios.post<InscriptionOrderSuccessResponse>(urlJoin(this.baseUrl, 'order'), {
receiveAddress,
files: [{ dataURL: file, size, name, type: 'plain/text', url: '' }],
files: [{ dataURL: file, size, name, type: 'plain/text' }],
fee,
lowPostage: true,
});
Expand Down

0 comments on commit f0eac36

Please sign in to comment.