Skip to content

Commit

Permalink
feat: change ordapi to hiro ordinals api, closes #3417
Browse files Browse the repository at this point in the history
  • Loading branch information
alter-eggo committed May 10, 2023
1 parent 38749a4 commit fc35a55
Show file tree
Hide file tree
Showing 13 changed files with 166 additions and 72 deletions.
12 changes: 0 additions & 12 deletions src/app/components/inscription-loader.tsx

This file was deleted.

22 changes: 9 additions & 13 deletions src/app/features/collectibles/components/bitcoin/inscription.tsx
Original file line number Diff line number Diff line change
@@ -1,31 +1,27 @@
import { useNavigate } from 'react-router-dom';

import { Inscription as InscriptionType } from '@shared/models/inscription.model';
import { RouteUrls } from '@shared/route-urls';

import { openInNewTab } from '@app/common/utils/open-in-new-tab';
import { OrdinalIconFull } from '@app/components/icons/ordinal-icon-full';
import { OrdinalMinimalIcon } from '@app/components/icons/ordinal-minimal-icon';
import { useInscription } from '@app/query/bitcoin/ordinals/inscription.hooks';
import { TaprootUtxo } from '@app/query/bitcoin/ordinals/use-taproot-address-utxos.query';
import { convertInscriptionToSupportedInscriptionType } from '@app/query/bitcoin/ordinals/inscription.hooks';

import { CollectibleImage } from '../_collectible-types/collectible-image';
import { CollectibleOther } from '../_collectible-types/collectible-other';
import { InscriptionText } from './inscription-text';

interface InscriptionProps {
path: string;
utxo: TaprootUtxo;
rawInscription: InscriptionType;
}
export function Inscription({ path, utxo }: InscriptionProps) {
const { isLoading, isError, data: inscription } = useInscription(path);
export function Inscription({ rawInscription }: InscriptionProps) {
const inscription = convertInscriptionToSupportedInscriptionType(rawInscription);
const navigate = useNavigate();

if (isLoading) return null;
if (isError) return null;

function openSendInscriptionModal() {
navigate(RouteUrls.SendOrdinalInscription, {
state: { inscription, utxo },
state: { inscription },
});
}

Expand All @@ -39,15 +35,15 @@ export function Inscription({ path, utxo }: InscriptionProps) {
onClickSend={() => openSendInscriptionModal()}
src={inscription.src}
subtitle="Ordinal inscription"
title={`# ${inscription.inscription_number}`}
title={`# ${inscription.number}`}
/>
);
}
case 'text': {
return (
<InscriptionText
contentSrc={inscription.contentSrc}
inscriptionNumber={inscription.inscription_number}
inscriptionNumber={inscription.number}
onClickCallToAction={() => openInNewTab(inscription.infoUrl)}
onClickSend={() => openSendInscriptionModal()}
/>
Expand All @@ -60,7 +56,7 @@ export function Inscription({ path, utxo }: InscriptionProps) {
onClickCallToAction={() => openInNewTab(inscription.infoUrl)}
onClickSend={() => openSendInscriptionModal()}
subtitle="Ordinal inscription"
title={`# ${inscription.inscription_number}`}
title={`# ${inscription.number}`}
>
<OrdinalIconFull />
</CollectibleOther>
Expand Down
19 changes: 8 additions & 11 deletions src/app/features/collectibles/components/bitcoin/ordinals.tsx
Original file line number Diff line number Diff line change
@@ -1,30 +1,27 @@
import { useEffect } from 'react';

import { useAnalytics } from '@app/common/hooks/analytics/use-analytics';
import { InscriptionLoader } from '@app/components/inscription-loader';
import { useTaprootAccountUtxosQuery } from '@app/query/bitcoin/ordinals/use-taproot-address-utxos.query';
import { useGetInscriptionsQuery } from '@app/query/bitcoin/ordinals/use-inscriptions.query';

import { Inscription } from './inscription';

export function Ordinals() {
const { data: utxos = [] } = useTaprootAccountUtxosQuery();
const { data: inscriptions = [] } = useGetInscriptionsQuery();
const analytics = useAnalytics();

useEffect(() => {
if (utxos.length > 0) {
if (inscriptions.length > 0) {
void analytics.track('view_collectibles', {
ordinals_count: utxos.length,
ordinals_count: inscriptions.length,
});
void analytics.identify({ ordinals_count: utxos.length });
void analytics.identify({ ordinals_count: inscriptions.length });
}
}, [utxos.length, analytics]);
}, [inscriptions.length, analytics]);

return (
<>
{utxos.map(utxo => (
<InscriptionLoader key={utxo.txid + utxo.vout} utxo={utxo}>
{path => <Inscription path={path} utxo={utxo} />}
</InscriptionLoader>
{inscriptions.map(inscription => (
<Inscription rawInscription={inscription} key={inscription.id} />
))}
</>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export function PsbtInputWithInscription({
hoverLabel={address}
image={<InscriptionPreview inscription={inscription} height="40px" width="40px" />}
subtitle={truncateMiddle(address)}
subValue={`#${inscription.inscription_number}`}
subValue={`#${inscription.number}`}
subValueAction={() => openInNewTab(inscription.infoUrl)}
title="Ordinal inscription"
value={`- ${inputValue}`}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,12 @@ import { RouteUrls } from '@shared/route-urls';

import { FeeEstimateMempoolSpaceApi } from '@app/query/bitcoin/bitcoin-client';
import { useBitcoinFeeRate } from '@app/query/bitcoin/fees/fee-estimates.hooks';
import { TaprootUtxo } from '@app/query/bitcoin/ordinals/use-taproot-address-utxos.query';

import { useSendOrdinalInscriptionRouteState } from './use-send-ordinal-inscription-route-state';

interface InscriptionSendState {
fees: FeeEstimateMempoolSpaceApi;
inscription: SupportedInscription;
utxo: TaprootUtxo;
}

export function useInscriptionSendState() {
Expand All @@ -27,17 +25,17 @@ interface SendInscriptionLoaderProps {
children(data: InscriptionSendState): JSX.Element;
}
function SendInscriptionLoader({ children }: SendInscriptionLoaderProps) {
const { inscription, utxo } = useSendOrdinalInscriptionRouteState();
const { inscription } = useSendOrdinalInscriptionRouteState();
const { data: fees } = useBitcoinFeeRate();
if (!fees) return null;
if (!inscription || !utxo) return <Navigate to={RouteUrls.Home} />;
return children({ inscription, utxo, fees });
if (!inscription) return <Navigate to={RouteUrls.Home} />;
return children({ inscription, fees });
}

export function SendInscription() {
return (
<SendInscriptionLoader>
{({ fees, inscription, utxo }) => <Outlet context={{ fees, inscription, utxo }} />}
{({ fees, inscription }) => <Outlet context={{ fees, inscription }} />}
</SendInscriptionLoader>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,18 @@ import { CollectibleAsset } from './components/collectible-asset';
import { useInscriptionSendState } from './send-inscription-container';
import { useGenerateSignedOrdinalTx } from './use-generate-ordinal-tx';
import { useOrdinalInscriptionFormValidationSchema } from './use-ordinal-inscription-form-validation-schema';
import { createUtxoFromInscription } from './utils';

export const recipeintFieldName = 'recipient';

export function SendInscriptionForm() {
const [currentError, setShowError] = useState<null | string>(null);
const navigate = useNavigate();
const { inscription, utxo, recipient } = useInscriptionSendState();

const { inscription, recipient } = useInscriptionSendState();
const validationSchema = useOrdinalInscriptionFormValidationSchema();
const [isLoading, setIsLoading] = useState(false);
const analytics = useAnalytics();

const utxo = createUtxoFromInscription(inscription);
const { coverFeeFromAdditionalUtxos } = useGenerateSignedOrdinalTx(utxo);

async function reviewTransaction(values: OrdinalSendFormValues) {
Expand Down Expand Up @@ -63,7 +63,7 @@ export function SendInscriptionForm() {

const { hex } = resp;
return navigate(RouteUrls.SendOrdinalInscriptionReview, {
state: { fee: resp.fee, inscription, utxo, recipient: values.recipient, tx: hex },
state: { fee: resp.fee, inscription, recipient: values.recipient, tx: hex },
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export function SendInscriptionReview() {

const { signedTx, recipient, fee } = useSendInscriptionReviewState();

const { inscription, utxo } = useInscriptionSendState();
const { inscription } = useInscriptionSendState();
const { refetch } = useCurrentNativeSegwitUtxos();
const { broadcastTx, isBroadcasting } = useBitcoinBroadcastTransaction();

Expand All @@ -52,7 +52,6 @@ export function SendInscriptionReview() {
navigate(RouteUrls.SendOrdinalInscriptionSent, {
state: {
inscription,
utxo,
recipient,
arrivesIn,
txId,
Expand Down
21 changes: 21 additions & 0 deletions src/app/pages/send/ordinal-inscription/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { Inscription } from '@shared/models/inscription.model';

import { TaprootUtxo } from '@app/query/bitcoin/ordinals/use-taproot-address-utxos.query';

export function createUtxoFromInscription(inscription: Inscription): TaprootUtxo {
const { genesis_block_hash, genesis_timestamp, genesis_block_height, value, addressIndex } =
inscription;

return {
txid: inscription.tx_id,
vout: Number(inscription.output.split(':')[1]),
status: {
confirmed: Boolean(genesis_block_hash),
block_height: genesis_block_height,
block_hash: genesis_block_hash,
block_time: genesis_timestamp,
},
value: Number(value),
addressIndex,
};
}
20 changes: 12 additions & 8 deletions src/app/query/bitcoin/ordinals/inscription.hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,27 +6,31 @@ import {

import { useGetInscriptionQuery } from './inscription.query';

function createInfoUrl(contentPath: string) {
return `https://ordinals.hiro.so${contentPath}`.replace('content', 'inscription');
function createInfoUrl(id: string) {
return `https://ordinals.hiro.so/inscription/${id}`;
}

function convertInscriptionToSupportedInscriptionType(inscription: Inscription) {
export function convertInscriptionToSupportedInscriptionType(inscription: Inscription) {
const title = `Inscription ${inscription.number}`;
return whenInscriptionType<SupportedInscription>(inscription.content_type, {
image: () => ({
infoUrl: createInfoUrl(inscription.content),
src: `https://ordapi.xyz${inscription.content}`,
infoUrl: createInfoUrl(inscription.id),
src: `https://api.hiro.so/ordinals/v1/inscriptions/${inscription.id}/content`,
type: 'image',
title,
...inscription,
}),
text: () => ({
contentSrc: `https://ordapi.xyz${inscription.content}`,
infoUrl: createInfoUrl(inscription.content),
contentSrc: `https://api.hiro.so/ordinals/v1/inscriptions/${inscription.id}/content`,
infoUrl: createInfoUrl(inscription.id),
type: 'text',
title,
...inscription,
}),
other: () => ({
infoUrl: createInfoUrl(inscription.content),
infoUrl: createInfoUrl(inscription.id),
type: 'other',
title,
...inscription,
}),
});
Expand Down
4 changes: 3 additions & 1 deletion src/app/query/bitcoin/ordinals/inscription.query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ const inscriptionQueryOptions = {
*/
function fetchInscription() {
return async (path: string) => {
const res = await fetch(`https://ordapi.xyz${path}`);
const res = await fetch(
`https://api.hiro.so/ordinals/v1${path.replace('inscription', 'inscriptions')}`
);
if (!res.ok) throw new Error('Error retrieving inscription metadata');
const data = await res.json();
return data as Inscription;
Expand Down
80 changes: 80 additions & 0 deletions src/app/query/bitcoin/ordinals/use-inscriptions.query.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { useQuery } from '@tanstack/react-query';

import { Inscription, InscriptionResponseItem } from '@shared/models/inscription.model';

import { createNullArrayOfLength } from '@app/common/utils';
import { createCounter } from '@app/common/utils/counter';
import { QueryPrefixes } from '@app/query/query-prefixes';
import { useCurrentAccountIndex } from '@app/store/accounts/account';
import { useCurrentTaprootAccountKeychain } from '@app/store/accounts/blockchain/bitcoin/taproot-account.hooks';
import { useCurrentNetwork } from '@app/store/networks/networks.selectors';

import { getTaprootAddress } from './utils';

const stopSearchAfterNumberAddressesWithoutOrdinals = 20;
const addressesSimultaneousFetchLimit = 5;

// max limit value in Hiro API
const inscriptionsLimit = 60;

async function fetchInscriptions(addresses: string) {
const res = await fetch(
`https://api.hiro.so/ordinals/v1/inscriptions?limit=${inscriptionsLimit}&${addresses}`
);
if (!res.ok) throw new Error('Error retrieving inscription metadata');
const data = await res.json();
return data.results as InscriptionResponseItem[];
}

/**
* Returns all inscriptions for the user's current taproot account
*/
export function useGetInscriptionsQuery() {
const network = useCurrentNetwork();
const keychain = useCurrentTaprootAccountKeychain();
const currentAccountIndex = useCurrentAccountIndex();

return useQuery(
[QueryPrefixes.InscriptionsFromApi, currentAccountIndex, network.id],
async () => {
let currentNumberOfAddressesWithoutOrdinals = 0;
const addressIndexCounter = createCounter(0);
const inscriptionsArr: Inscription[] = [];
while (
currentNumberOfAddressesWithoutOrdinals < stopSearchAfterNumberAddressesWithoutOrdinals
) {
const addresses = createNullArrayOfLength(addressesSimultaneousFetchLimit).map(
(_, index) => {
if (index > 0) {
addressIndexCounter.increment();
}
return getTaprootAddress({
index: addressIndexCounter.getValue(),
keychain,
network: network.chain.bitcoin.network,
});
}
);

const addressesQueryParam = addresses.reduce((acc, address, index) => {
return (acc += `${index > 0 ? '&' : ''}address=${address}`);
}, '');

const inscriptions = await fetchInscriptions(addressesQueryParam);

if (inscriptions.length === 0) {
currentNumberOfAddressesWithoutOrdinals += addressesSimultaneousFetchLimit;
continue;
}
inscriptionsArr.push(
...inscriptions.map(inscription => ({
...inscription,
addressIndex: addressIndexCounter.getValue(),
}))
);
currentNumberOfAddressesWithoutOrdinals = 0;
}
return inscriptionsArr;
}
);
}
1 change: 1 addition & 0 deletions src/app/query/query-prefixes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export enum QueryPrefixes {
BnsNamesByAddress = 'bns-names-by-address',
InscriptionMetadata = 'inscription-metadata',
InscriptionFromTxid = 'inscription-from-txid',
InscriptionsFromApi = 'inscriptions-from-api',
GetNftMetadata = 'get-nft-metadata',

StampCollection = 'stamp-collection',
Expand Down
Loading

0 comments on commit fc35a55

Please sign in to comment.