Skip to content

Commit

Permalink
Merge branch 'develop' into mahmoud/eng-3261-expl-mob-1-xverse-featur…
Browse files Browse the repository at this point in the history
…ed-apps-api
  • Loading branch information
dhriaznov authored Feb 19, 2024
2 parents fef4a21 + 62d50c2 commit 8bcbc99
Show file tree
Hide file tree
Showing 23 changed files with 1,106 additions and 120 deletions.
19 changes: 13 additions & 6 deletions currency/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,20 +15,27 @@ const getStxFiatEquivalent = (stxAmount: BigNumber, stxBtcRate: BigNumber, btcFi
const getBtcFiatEquivalent = (btcAmount: BigNumber, btcFiatRate: BigNumber): BigNumber =>
satsToBtc(btcAmount).multipliedBy(btcFiatRate);

const getFiatBtcEquivalent = (fiatAmount: BigNumber, btcFiatRate: BigNumber): BigNumber =>
new BigNumber(fiatAmount.dividedBy(btcFiatRate).toFixed(8));

const getStxTokenEquivalent = (fiatAmount: BigNumber, stxBtcRate: BigNumber, btcFiatRate: BigNumber): BigNumber =>
fiatAmount.dividedBy(stxBtcRate).dividedBy(btcFiatRate);

/**
* @deprecated Use getBtcFiatEquivalent instead
*/
const getBtcEquivalent = (fiatAmount: BigNumber, btcFiatRate: BigNumber): BigNumber =>
fiatAmount.dividedBy(btcFiatRate);

export {
fetchBtcFeeRate,
satsToBtc,
btcToSats,
microstacksToStx,
stxToMicrostacks,
getStxFiatEquivalent,
fetchBtcFeeRate,
getBtcEquivalent,
getBtcFiatEquivalent,
getFiatBtcEquivalent,
getStxFiatEquivalent,
getStxTokenEquivalent,
getBtcEquivalent,
microstacksToStx,
satsToBtc,
stxToMicrostacks,
};
1 change: 1 addition & 0 deletions hooks/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from './brc20';
export * from './inscriptions';
export * from './transactions';
120 changes: 120 additions & 0 deletions hooks/transactions/helpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import { deserializeTransaction, estimateTransaction } from '@stacks/transactions';
import BigNumber from 'bignumber.js';
import { RbfRecommendedFees, getRawTransaction, rbf } from '../../transactions';
import {
AppInfo,
RecommendedFeeResponse,
SettingsNetwork,
BtcTransactionData,
StacksNetwork,
StacksTransaction,
StxTransactionData,
} from '../../types';
import { microstacksToStx } from '../../currency';

export type RbfData = {
rbfTransaction?: InstanceType<typeof rbf.RbfTransaction>;
rbfTxSummary?: {
currentFee: number;
currentFeeRate: number;
minimumRbfFee: number;
minimumRbfFeeRate: number;
};
rbfRecommendedFees?: RbfRecommendedFees;
mempoolFees?: RecommendedFeeResponse;
isLoading?: boolean;
errorCode?: 'SOMETHING_WENT_WRONG';
};

export const isBtcTransaction = (
transaction: BtcTransactionData | StxTransactionData,
): transaction is BtcTransactionData => transaction?.txType === 'bitcoin';

export const constructRecommendedFees = (
lowerName: keyof RbfRecommendedFees,
lowerFeeRate: number,
higherName: keyof RbfRecommendedFees,
higherFeeRate: number,
stxAvailableBalance: string,
): RbfRecommendedFees => {
const bigNumLowerFee = BigNumber(lowerFeeRate);
const bigNumHigherFee = BigNumber(higherFeeRate);

return {
[lowerName]: {
enoughFunds: bigNumLowerFee.lte(BigNumber(stxAvailableBalance)),
feeRate: microstacksToStx(bigNumLowerFee).toNumber(),
fee: microstacksToStx(bigNumLowerFee).toNumber(),
},
[higherName]: {
enoughFunds: bigNumHigherFee.lte(BigNumber(stxAvailableBalance)),
feeRate: microstacksToStx(bigNumHigherFee).toNumber(),
fee: microstacksToStx(bigNumHigherFee).toNumber(),
},
};
};

export const sortFees = (fees: RbfRecommendedFees) =>
Object.fromEntries(
Object.entries(fees).sort((a, b) => {
const priorityOrder = ['highest', 'higher', 'high', 'medium'];
return priorityOrder.indexOf(a[0]) - priorityOrder.indexOf(b[0]);
}),
);

export const calculateStxRbfData = async (
fee: BigNumber,
feeEstimations: {
fee: number;
fee_rate: number;
}[],
appInfo: AppInfo | null,
stxAvailableBalance: string,
): Promise<RbfData> => {
const [slow, medium, high] = feeEstimations;
const shouldCapFee = appInfo?.thresholdHighStacksFee && high.fee > appInfo.thresholdHighStacksFee;

const mediumFee = shouldCapFee ? appInfo.thresholdHighStacksFee : medium.fee;
const highFee = shouldCapFee ? appInfo.thresholdHighStacksFee * 1.5 : high.fee;
const higherFee = fee.multipliedBy(1.25).toNumber();
const highestFee = fee.multipliedBy(1.5).toNumber();

const defaultMinimumFee = fee.multipliedBy(1.25).toNumber();
const minimumFee = !Number.isSafeInteger(defaultMinimumFee) ? Math.ceil(defaultMinimumFee) : defaultMinimumFee;

const feePresets: RbfRecommendedFees = fee.lt(BigNumber(mediumFee))
? constructRecommendedFees('medium', mediumFee, 'high', highFee, stxAvailableBalance)
: constructRecommendedFees('higher', higherFee, 'highest', highestFee, stxAvailableBalance);

return {
rbfTxSummary: {
currentFee: microstacksToStx(fee).toNumber(),
currentFeeRate: microstacksToStx(fee).toNumber(),
minimumRbfFee: microstacksToStx(BigNumber(minimumFee)).toNumber(),
minimumRbfFeeRate: microstacksToStx(BigNumber(minimumFee)).toNumber(),
},
rbfRecommendedFees: sortFees(feePresets),
mempoolFees: {
fastestFee: microstacksToStx(BigNumber(high.fee)).toNumber(),
halfHourFee: microstacksToStx(BigNumber(medium.fee)).toNumber(),
hourFee: microstacksToStx(BigNumber(slow.fee)).toNumber(),
economyFee: microstacksToStx(BigNumber(slow.fee)).toNumber(),
minimumFee: microstacksToStx(BigNumber(slow.fee)).toNumber(),
},
};
};

export const fetchStxRbfData = async (
transaction: StxTransactionData,
btcNetwork: SettingsNetwork,
stacksNetwork: StacksNetwork,
appInfo: AppInfo | null,
stxAvailableBalance: string,
): Promise<RbfData> => {
const { fee } = transaction;
const txRaw: string = await getRawTransaction(transaction.txid, btcNetwork);
const unsignedTx: StacksTransaction = deserializeTransaction(txRaw);
const feeEstimations = await estimateTransaction(unsignedTx.payload, undefined, stacksNetwork);

return calculateStxRbfData(fee, feeEstimations, appInfo, stxAvailableBalance);
};
2 changes: 2 additions & 0 deletions hooks/transactions/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { default as useBtcRbfTransactionData } from './useBtcRbfTransactionData';
export { default as useStxRbfTransactionData } from './useStxRbfTransactionData';
48 changes: 48 additions & 0 deletions hooks/transactions/useBtcRbfTransactionData.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { useQuery } from '@tanstack/react-query';
import { rbf } from '../../transactions';
import { Account, BtcTransactionData, SettingsNetwork } from '../../types';
import { BitcoinEsploraApiProvider, mempoolApi } from '../../api';
import { RbfData, sortFees } from './helpers';

type Props = {
account: Account | null;
transaction?: BtcTransactionData;
btcNetwork: SettingsNetwork;
esploraProvider: BitcoinEsploraApiProvider;
isLedgerAccount: boolean;
};

const useBtcRbfTransactionData = ({ account, transaction, btcNetwork, esploraProvider, isLedgerAccount }: Props) => {
const fetchRbfData = async (): Promise<RbfData | undefined> => {
if (!account || !transaction) {
return;
}

const rbfTx = new rbf.RbfTransaction(transaction, {
...account,
accountType: account.accountType || 'software',
accountId: isLedgerAccount && account.deviceAccountIndex ? account.deviceAccountIndex : account.id,
network: btcNetwork.type,
esploraProvider,
});

const mempoolFees = await mempoolApi.getRecommendedFees(btcNetwork.type);
const rbfRecommendedFees = await rbfTx.getRbfRecommendedFees(mempoolFees);
const rbfTransactionSummary = await rbf.getRbfTransactionSummary(esploraProvider, transaction.txid);

return {
rbfTransaction: rbfTx,
rbfTxSummary: rbfTransactionSummary,
rbfRecommendedFees: sortFees(rbfRecommendedFees),
mempoolFees,
};
};

return useQuery({
queryKey: ['btc-rbf-transaction-data', transaction?.txid],
queryFn: fetchRbfData,
enabled: !!transaction && !!account,
});
};

export default useBtcRbfTransactionData;
30 changes: 30 additions & 0 deletions hooks/transactions/useStxRbfTransactionData.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { useQuery } from '@tanstack/react-query';
import { StxTransactionData, SettingsNetwork, StacksNetwork, AppInfo } from '../../types';
import { fetchStxRbfData } from './helpers';

type Props = {
transaction?: StxTransactionData;
btcNetwork: SettingsNetwork;
stacksNetwork: StacksNetwork;
appInfo: AppInfo | null;
stxAvailableBalance: string;
};

const useStxRbfTransactionData = ({ transaction, btcNetwork, stacksNetwork, appInfo, stxAvailableBalance }: Props) => {
const fetchRbfData = async () => {
if (!transaction) {
return;
}

const stxRbfData = await fetchStxRbfData(transaction, btcNetwork, stacksNetwork, appInfo, stxAvailableBalance);
return stxRbfData;
};

return useQuery({
queryKey: ['stx-rbf-transaction-data', transaction?.txid],
queryFn: fetchRbfData,
enabled: !!transaction,
});
};

export default useStxRbfTransactionData;
48 changes: 46 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@secretkeylabs/xverse-core",
"version": "10.0.0",
"version": "11.0.0",
"description": "",
"engines": {
"node": "^18.18.2"
Expand All @@ -19,6 +19,7 @@
"@stacks/storage": "^6.9.0",
"@stacks/transactions": "6.9.0",
"@stacks/wallet-sdk": "^6.9.0",
"@tanstack/react-query": "^4.29.3",
"@zondax/ledger-stacks": "^1.0.4",
"async-mutex": "^0.4.0",
"axios": "1.6.2",
Expand Down
Loading

0 comments on commit 8bcbc99

Please sign in to comment.