Skip to content

Commit

Permalink
feat: upgrade brc-20 api to bestinslot v3, closes #4970
Browse files Browse the repository at this point in the history
  • Loading branch information
alter-eggo committed Mar 1, 2024
1 parent 41a47ec commit bf58f6d
Show file tree
Hide file tree
Showing 14 changed files with 144 additions and 99 deletions.
1 change: 1 addition & 0 deletions .github/workflows/build-extension.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ jobs:
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
SEGMENT_WRITE_KEY: ${{ secrets.SEGMENT_WRITE_KEY_STAGING }}
TRANSAK_API_KEY: ${{ secrets.TRANSAK_API_KEY }}
BESTINSLOT_API_KEY: ${{ secrets.BESTINSLOT_API_KEY }}
PR_NUMBER: ${{ github.event.number }}
COMMIT_SHA: ${{ env.SHORT_SHA }}

Expand Down
1 change: 1 addition & 0 deletions .github/workflows/development-extension.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ env:
MOONPAY_API_KEY: ${{ secrets.MOONPAY_API_KEY }}
SEGMENT_WRITE_KEY: ${{ secrets.SEGMENT_WRITE_KEY_STAGING }}
TRANSAK_API_KEY: ${{ secrets.TRANSAK_API_KEY }}
BESTINSLOT_API_KEY: ${{ secrets.BESTINSLOT_API_KEY }}
PREVIEW_RELEASE: true
WALLET_ENVIRONMENT: preview

Expand Down
2 changes: 1 addition & 1 deletion src/app/components/brc20-tokens-loader.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useBrc20Tokens } from '@app/common/hooks/use-brc20-tokens';
import { Brc20Token } from '@app/query/bitcoin/ordinals/brc20/brc20-tokens.query';
import { Brc20Token } from '@app/query/bitcoin/bitcoin-client';

interface Brc20TokensLoaderProps {
children(brc20Tokens: Brc20Token[]): React.JSX.Element;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { RouteUrls } from '@shared/route-urls';
import { noop } from '@shared/utils';

import { useNativeSegwitBalance } from '@app/query/bitcoin/balance/btc-native-segwit-balance.hooks';
import { Brc20Token } from '@app/query/bitcoin/ordinals/brc20/brc20-tokens.query';
import { Brc20Token } from '@app/query/bitcoin/bitcoin-client';
import { useCurrentAccountNativeSegwitAddressIndexZero } from '@app/store/accounts/blockchain/bitcoin/native-segwit-account.hooks';

import { Brc20TokenAssetItemLayout } from './components/brc20-token-asset-item.layout';
Expand All @@ -19,9 +19,9 @@ export function Brc20TokenAssetList(props: { brc20Tokens?: Brc20Token[] }) {
btcCryptoCurrencyAssetBalance.balance.amount.isGreaterThan(0);

function navigateToBrc20SendForm(token: Brc20Token) {
const { tick, available_balance, decimals } = token;
navigate(RouteUrls.SendBrc20SendForm.replace(':ticker', tick), {
state: { balance: available_balance, tick, decimals },
const { ticker, available_balance, decimals, holderAddress } = token;
navigate(RouteUrls.SendBrc20SendForm.replace(':ticker', ticker), {
state: { balance: available_balance, ticker, decimals, holderAddress },
});
}

Expand All @@ -31,7 +31,7 @@ export function Brc20TokenAssetList(props: { brc20Tokens?: Brc20Token[] }) {
<Brc20AssetListLayout>
{props.brc20Tokens?.map(token => (
<Brc20TokenAssetItemLayout
key={token.tick}
key={token.ticker}
displayNotEnoughBalance={!hasPositiveBtcBalanceForFees}
token={token}
onClick={hasPositiveBtcBalanceForFees ? () => navigateToBrc20SendForm(token) : noop}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { styled } from 'leather-styles/jsx';
import { createMoney } from '@shared/models/money.model';

import { formatBalance } from '@app/common/format-balance';
import { Brc20Token } from '@app/query/bitcoin/ordinals/brc20/brc20-tokens.query';
import { Brc20Token } from '@app/query/bitcoin/bitcoin-client';
import { Brc20TokenIcon } from '@app/ui/components/avatar-icon/brc20-token-icon';
import { ItemInteractive } from '@app/ui/components/item/item-interactive';
import { ItemLayout } from '@app/ui/components/item/item.layout';
Expand All @@ -19,7 +19,7 @@ export function Brc20TokenAssetItemLayout({
displayNotEnoughBalance,
token,
}: Brc20TokenAssetItemLayoutProps) {
const balance = createMoney(Number(token.overall_balance), token.tick, 0);
const balance = createMoney(Number(token.overall_balance), token.ticker, 0);
const formattedBalance = formatBalance(balance.amount.toString());

return (
Expand All @@ -32,15 +32,15 @@ export function Brc20TokenAssetItemLayout({
<ItemInteractive onClick={onClick}>
<ItemLayout
flagImg={<Brc20TokenIcon />}
titleLeft={token.tick}
titleLeft={token.ticker}
captionLeft="BRC-20"
titleRight={
<BasicTooltip
asChild
label={formattedBalance.isAbbreviated ? balance.amount.toString() : undefined}
side="left"
>
<styled.span data-testid={token.tick} fontWeight={500} textStyle="label.02">
<styled.span data-testid={token.ticker} fontWeight={500} textStyle="label.02">
{formattedBalance.value}
</styled.span>
</BasicTooltip>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Stack } from 'leather-styles/jsx';

import { Brc20TokenAssetItemLayout } from '@app/components/crypto-assets/bitcoin/brc20-token-asset-list/components/brc20-token-asset-item.layout';
import { Brc20Token } from '@app/query/bitcoin/ordinals/brc20/brc20-tokens.query';
import { Brc20Token } from '@app/query/bitcoin/bitcoin-client';

interface BitcoinFungibleTokenAssetListProps {
brc20Tokens?: Brc20Token[];
Expand All @@ -12,7 +12,7 @@ export function BitcoinFungibleTokenAssetList({ brc20Tokens }: BitcoinFungibleTo
return (
<Stack gap="space.05">
{brc20Tokens.map(token => (
<Brc20TokenAssetItemLayout key={token.tick} token={token} />
<Brc20TokenAssetItemLayout key={token.ticker} token={token} />
))}
</Stack>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,21 +31,22 @@ import { useSendBitcoinAssetContextState } from '../../family/bitcoin/components
function useBrc20ChooseFeeState() {
const location = useLocation();
return {
tick: get(location.state, 'tick') as string,
ticker: get(location.state, 'ticker') as string,
amount: get(location.state, 'amount') as string,
recipient: get(location.state, 'recipient') as string,
utxos: get(location.state, 'utxos') as UtxoResponseItem[],
holderAddress: get(location.state, 'holderAddress') as string,
};
}

export function BrcChooseFee() {
const navigate = useNavigate();
const { amount, recipient, tick, utxos } = useBrc20ChooseFeeState();
const { amount, recipient, ticker, utxos, holderAddress } = useBrc20ChooseFeeState();
const generateTx = useGenerateUnsignedNativeSegwitSingleRecipientTx();
const signTx = useSignBitcoinTx();
const { selectedFeeType, setSelectedFeeType } = useSendBitcoinAssetContextState();
const { initiateTransfer } = useBrc20Transfers();
const amountAsMoney = createMoney(Number(amount), tick, 0);
const { initiateTransfer } = useBrc20Transfers(holderAddress);
const amountAsMoney = createMoney(Number(amount), ticker, 0);
const { feesList, isLoading } = useBitcoinFeesList({
amount: amountAsMoney,
recipient,
Expand All @@ -61,7 +62,7 @@ export function BrcChooseFee() {
async function previewTransaction({ feeRate, feeValue, isCustomFee }: OnChooseFeeArgs) {
setIsLoadingOrder(true);
try {
const { order, id } = await initiateTransfer(tick, amount);
const { order, id } = await initiateTransfer(ticker, amount);
setIsLoadingOrder(false);

const { charge } = order.data;
Expand All @@ -87,7 +88,7 @@ export function BrcChooseFee() {
signedTx.finalize();

const feeRowValue = formFeeRowValue(feeRate, isCustomFee);
navigate(RouteUrls.SendBrc20Confirmation.replace(':ticker', tick), {
navigate(RouteUrls.SendBrc20Confirmation.replace(':ticker', ticker), {
state: {
tx: signedTx.hex,
orderId: id,
Expand All @@ -96,8 +97,9 @@ export function BrcChooseFee() {
serviceFeeRecipient,
recipient,
feeRowValue,
tick,
ticker,
amount,
holderAddress,
hasHeaderTitle: true,
},
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,14 +39,15 @@ function useBrc20SendFormConfirmationState() {
tick: get(location.state, 'tick') as string,
amount: get(location.state, 'amount') as string,
tx: get(location.state, 'tx') as string,
holderAddress: get(location.state, 'holderAddress') as string,
};
}

export function Brc20SendFormConfirmation() {
const navigate = useNavigate();
const analytics = useAnalytics();

const { amount, recipient, fee, tick, serviceFee, tx, orderId, feeRowValue } =
const { amount, recipient, fee, tick, serviceFee, tx, orderId, feeRowValue, holderAddress } =
useBrc20SendFormConfirmationState();

const summaryFeeMoney = createMoney(Number(fee), 'BTC');
Expand All @@ -63,7 +64,8 @@ export function Brc20SendFormConfirmation() {

const psbt = decodeBitcoinTx(tx);
const nav = useSendFormNavigate();
const { inscriptionPaymentTransactionComplete } = useBrc20Transfers();

const { inscriptionPaymentTransactionComplete } = useBrc20Transfers(holderAddress);

async function initiateTransaction() {
await broadcastTx({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,21 +21,22 @@ function useBrc20SendFormRouteState() {
const { state } = useLocation();
return {
balance: get(state, 'balance', '') as string,
tick: get(state, 'tick', '') as string,
ticker: get(state, 'ticker', '') as string,
decimals: get(state, 'decimals', '') as number,
holderAddress: get(state, 'holderAddress', '') as string,
};
}

export function Brc20SendForm() {
const { balance, tick, decimals } = useBrc20SendFormRouteState();
const { balance, ticker, decimals, holderAddress } = useBrc20SendFormRouteState();
const {
initialValues,
chooseTransactionFee,
validationSchema,
formRef,
onFormStateChange,
moneyBalance,
} = useBrc20SendForm({ balance, tick, decimals });
} = useBrc20SendForm({ balance, ticker, decimals, holderAddress });

return (
<Box pb="space.04" width="100%">
Expand All @@ -61,7 +62,7 @@ export function Brc20SendForm() {
}
autoComplete="off"
/>
<SelectedAssetField icon={<Brc20TokenIcon />} name={tick} symbol={tick} />
<SelectedAssetField icon={<Brc20TokenIcon />} name={ticker} symbol={ticker} />
<Callout variant="info" title="Sending BRC-20 tokens requires two steps">
<styled.ol mb="space.02">
<li>1. Create transfer inscription with amount to send</li>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,17 @@ interface Brc20SendFormValues {

interface UseBrc20SendFormArgs {
balance: string;
tick: string;
ticker: string;
decimals: number;
holderAddress: string;
}

export function useBrc20SendForm({ balance, tick, decimals }: UseBrc20SendFormArgs) {
export function useBrc20SendForm({
balance,
ticker,
decimals,
holderAddress,
}: UseBrc20SendFormArgs) {
const formRef = useRef<FormikProps<Brc20SendFormValues>>(null);
const { whenWallet } = useWalletType();
const navigate = useNavigate();
Expand All @@ -53,14 +59,14 @@ export function useBrc20SendForm({ balance, tick, decimals }: UseBrc20SendFormAr
const initialValues = createDefaultInitialFormValues({
recipient: nativeSegwitSigner.address,
amount: '',
symbol: tick,
symbol: ticker,
});

const validationSchema = yup.object({
amount: yup
.number()
.concat(currencyAmountValidator())
.concat(tokenAmountValidator(createMoney(new BigNumber(balance), tick, 0))),
.concat(tokenAmountValidator(createMoney(new BigNumber(balance), ticker, 0))),
recipient: yup
.string()
.concat(btcAddressValidator())
Expand All @@ -78,8 +84,8 @@ export function useBrc20SendForm({ balance, tick, decimals }: UseBrc20SendFormAr
await formikHelpers.validateForm();
whenWallet({
software: () =>
navigate(RouteUrls.SendBrc20ChooseFee.replace(':ticker', tick), {
state: { ...values, tick, utxos, hasHeaderTitle: true },
navigate(RouteUrls.SendBrc20ChooseFee.replace(':ticker', ticker), {
state: { ...values, ticker, utxos, holderAddress, hasHeaderTitle: true },
}),
ledger: noop,
})();
Expand All @@ -93,7 +99,7 @@ export function useBrc20SendForm({ balance, tick, decimals }: UseBrc20SendFormAr
onFormStateChange,
moneyBalance: createMoney(
unitToFractionalUnit(decimals)(new BigNumber(balance)),
tick,
ticker,
decimals
),
};
Expand Down
68 changes: 63 additions & 5 deletions src/app/query/bitcoin/bitcoin-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,45 @@ export interface BestinslotInscriptionsByTxIdResponse {
blockHeight: number;
}

class BestinslotInscriptionsApi {
interface Brc20TokenResponse {
ticker: string;
overall_balance: string;
available_balance: string;
transferrable_balance: string;
image_url: string | null;
}

export interface Brc20Token extends Brc20TokenResponse {
decimals: number;
holderAddress: string;
}

interface Brc20TokenTicker {
decimals: number;
deploy_incr_number: number;
deploy_ts: string;
holder_count: number;
image_url: string | null;
limit_per_mint: string;
max_supply: string;
mint_progress: number;
minted_supply: string;
ticker: string;
tx_count: number;
}

interface Brc20TickerResponse {
data: Brc20TokenTicker;
block_height: number;
}

interface BestinslotBrc20AddressBalanceResponse {
block_height: number;
data: Brc20TokenResponse[];
}

class BestinslotApi {
url = 'https://api.bestinslot.xyz/v3';
private defaultOptions = {
headers: {
'x-api-key': `${process.env.BESTINSLOT_API_KEY}`,
Expand All @@ -61,7 +99,7 @@ class BestinslotInscriptionsApi {

async getInscriptionsByTransactionId(id: string) {
const resp = await axios.get<BestinslotInscriptionsByTxIdResponse>(
`https://api.bestinslot.xyz/v3/inscription/in_transaction?tx_id=${id}`,
`${this.url}/inscription/in_transaction?tx_id=${id}`,
{
...this.defaultOptions,
}
Expand All @@ -72,7 +110,27 @@ class BestinslotInscriptionsApi {

async getInscriptionById(id: string) {
const resp = await axios.get<BestinslotInscriptionByIdResponse>(
`https://api.bestinslot.xyz/v3/inscription/single_info_id?inscription_id=${id}`,
`${this.url}/inscription/single_info_id?inscription_id=${id}`,
{
...this.defaultOptions,
}
);
return resp.data;
}

async getBrc20Balance(address: string) {
const resp = await axios.get<BestinslotBrc20AddressBalanceResponse>(
`${this.url}/brc20/wallet_balances?address=${address}`,
{
...this.defaultOptions,
}
);
return resp.data;
}

async getBrc20TickerData(ticker: string) {
const resp = await axios.get<Brc20TickerResponse>(
`${this.url}/brc20/ticker_info?ticker=${ticker}`,
{
...this.defaultOptions,
}
Expand Down Expand Up @@ -191,13 +249,13 @@ export class BitcoinClient {
addressApi: AddressApi;
feeEstimatesApi: FeeEstimatesApi;
transactionsApi: TransactionsApi;
bestinslotInscriptionsApi: BestinslotInscriptionsApi;
BestinslotApi: BestinslotApi;

constructor(basePath: string) {
this.configuration = new Configuration(basePath);
this.addressApi = new AddressApi(this.configuration);
this.feeEstimatesApi = new FeeEstimatesApi(this.configuration);
this.transactionsApi = new TransactionsApi(this.configuration);
this.bestinslotInscriptionsApi = new BestinslotInscriptionsApi(this.configuration);
this.BestinslotApi = new BestinslotApi(this.configuration);
}
}
Loading

0 comments on commit bf58f6d

Please sign in to comment.