Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Release/brc 20 api #4846

Merged
merged 3 commits into from
Jan 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading