diff --git a/src/assets/icons-v3/arrow_right.svg b/src/assets/icons-v3/arrow_right.svg index 15f08aa4..d0907c22 100644 --- a/src/assets/icons-v3/arrow_right.svg +++ b/src/assets/icons-v3/arrow_right.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/src/components/FeeRateSelector.vue b/src/components/FeeRateSelector.vue index 99dfeb57..4fc8040b 100644 --- a/src/components/FeeRateSelector.vue +++ b/src/components/FeeRateSelector.vue @@ -59,7 +59,7 @@ const selectCustom = () => {
Fee Rate
{{ feeRate }} sat/vB - + diff --git a/src/components/ui/drawer/DrawerContent.vue b/src/components/ui/drawer/DrawerContent.vue index d715c9b9..320fc72c 100644 --- a/src/components/ui/drawer/DrawerContent.vue +++ b/src/components/ui/drawer/DrawerContent.vue @@ -19,7 +19,7 @@ const forwarded = useForwardPropsEmits(props, emits) v-bind="forwarded" :class=" cn( - 'absolute inset-x-0 bottom-0 z-50 mt-24 flex h-auto flex-col rounded-t-[10px] border bg-white after:hidden', + 'absolute inset-x-0 bottom-0 z-50 mt-24 flex h-auto flex-col rounded-t-[10px] border bg-white', props.class ) " diff --git a/src/data/assets.ts b/src/data/assets.ts index 1b64b8cb..e5243278 100644 --- a/src/data/assets.ts +++ b/src/data/assets.ts @@ -1,5 +1,6 @@ import { TokenTransfer } from '@/queries/brc20' import { SymbolTicker } from '@/lib/asset-symbol' +import { Protocol } from './../lib/types/protocol' import { CoinCategory } from '@/queries/exchange-rates' import { Balance, BRC20Balance } from '@/queries/types/balance' @@ -12,6 +13,7 @@ export interface Asset { decimal: number balance?: Balance contract?: string + protocol?: Protocol } export interface BRC20Asset extends Asset { @@ -39,6 +41,13 @@ export interface MRC20Asset extends Asset { deployAvatar: string } +export interface MetaContractAsset extends Asset { + codeHash: string + genesis: string + icon?: string + sensibleId: string +} + export interface Tag { name: string bg: string diff --git a/src/data/bridge-pairs.ts b/src/data/bridge-pairs.ts index 2c30e101..135f34ef 100644 --- a/src/data/bridge-pairs.ts +++ b/src/data/bridge-pairs.ts @@ -1,23 +1,12 @@ -import btc from '@/assets/btc.svg?url' - import { InjectionKey } from 'vue' import { ref, Ref } from 'vue' -import { type assetReqReturnType } from '@/queries/bridge-api' -import { network } from '@/lib/network' +import { type assetReqReturnType } from '@/queries/types/bridge' import { useBridgePairStore } from '@/stores/bridge-pair' const bridgePairStore = useBridgePairStore() -const bridgePairs: Ref = - network.value === 'testnet' - ? ref(bridgePairStore.list) - : ref([ - { - network: 'BTC', - targetSymbol: 'BTC', - originSymbol: 'BTC', - id: 1, - }, - ]) +console.log('bridgePairStore', bridgePairStore.list) + +const bridgePairs: Ref = ref(bridgePairStore.list) export default bridgePairs diff --git a/src/data/hosts.ts b/src/data/hosts.ts index c7fc8914..3acc898d 100644 --- a/src/data/hosts.ts +++ b/src/data/hosts.ts @@ -25,13 +25,11 @@ export const API_RUNES_TESTNET_ORDERS_EXCHANGE = 'https://api-runes-testnet.orde export const UNISAT_HOST = 'https://wallet-api.unisat.io/v5' export const UNISAT_TESTNET_HOST = 'https://wallet-api-testnet.unisat.io/v5' -export const proxy = - (import.meta.env.VITE_HOST_PROXY || 'https://api2.orders.exchange') + '/api' +export const proxy = (import.meta.env.VITE_HOST_PROXY || 'https://api2.orders.exchange') + '/api' -export const runes = - (import.meta.env.VITE_HOST_RUNES || - 'https://runes-preview-swap-queue.vercel.app') + '/api' +export const runes = (import.meta.env.VITE_HOST_RUNES || 'https://runes-preview-swap-queue.vercel.app') + '/api' -export const swap = - (import.meta.env.VITE_HOST_SWAP || 'https://api-swap.orders.exchange') + - '/api' +export const swap = (import.meta.env.VITE_HOST_SWAP || 'https://api-swap.orders.exchange') + '/api' + +export const OCTOPUS_HOST = 'https://api.octopus.space/api-bridge/' +export const OCTOPUS_TESTNET_HOST = 'https://api.octopus.space/api-bridge-testnet/' diff --git a/src/hooks/bridge/useBTCPool.ts b/src/hooks/bridge/useBTCPool.ts new file mode 100644 index 00000000..6af3bf60 --- /dev/null +++ b/src/hooks/bridge/useBTCPool.ts @@ -0,0 +1,18 @@ +import { computed } from 'vue' +import { useRouteParams } from '@vueuse/router' + +export default function useBTCPool() { + const pairStr = useRouteParams('pair') + if (!pairStr.value) { + pairStr.value = `BTC-WBTC` + } + + const token1 = computed(() => pairStr.value.split('-')[0]) + const token2 = computed(() => pairStr.value.split('-')[1]) + + return { + pairStr, + token1, + token2, + } +} diff --git a/src/hooks/bridge/useBridgePool.ts b/src/hooks/bridge/useBridgePool.ts new file mode 100644 index 00000000..d3e7cf4d --- /dev/null +++ b/src/hooks/bridge/useBridgePool.ts @@ -0,0 +1,26 @@ +import { ref } from 'vue' +import useBTCPool from './useBTCPool' +import { useRouter } from 'vue-router' +import { toast } from '@/components/ui/toast' +import { useRouteParams } from '@vueuse/router' + +const router = useRouter() + +export default function useBridgePool() { + const protocol = useRouteParams('protocol') + console.log('protocol', protocol.value) + + if (!protocol.value) { + protocol.value = 'BTC' + } + + if (protocol.value === 'BTC') { + return { ...useBTCPool(), protocol } + } else { + toast({ toastType: 'warning', title: 'Unsupported protocol type.' }) + // FIXME: go undefined + router.go(-1) + } + + return { token1: ref(''), token2: ref(''), protocol } +} diff --git a/src/hooks/swap/useSwapPool.ts b/src/hooks/swap/useSwapPool.ts index cdb04924..1fe60ab6 100644 --- a/src/hooks/swap/useSwapPool.ts +++ b/src/hooks/swap/useSwapPool.ts @@ -17,7 +17,7 @@ export function useSwapPool() { const protocol = useRouteParams('protocol') if (!protocol.value) { if (chain.value === Chain.BTC) { - protocol.value = Protocol.Rune.toLocaleLowerCase() + protocol.value = Protocol.Runes.toLocaleLowerCase() } else if (chain.value === Chain.MVC) { protocol.value = Protocol.MetaContract.toLocaleLowerCase() } else { @@ -27,10 +27,11 @@ export function useSwapPool() { } } - if (protocol.value === Protocol.Rune.toLocaleLowerCase()) { + if (protocol.value === Protocol.Runes.toLocaleLowerCase()) { return { ...useRunesPool(), chain } } else { toast({ toastType: 'warning', title: 'Unsupported protocol type.' }) + // FIXME: go undefined router.go(-1) } diff --git a/src/hooks/use-bridge-pair.ts b/src/hooks/use-bridge-pair.ts index 1cc7ddef..b0f7f78f 100644 --- a/src/hooks/use-bridge-pair.ts +++ b/src/hooks/use-bridge-pair.ts @@ -1,19 +1,23 @@ -import { type Ref, computed, ref } from 'vue' -import { useRouteParams } from '@vueuse/router' import { useRouter } from 'vue-router' +import { type Ref, computed, ref } from 'vue' import bridgePairs from '@/data/bridge-pairs' +import { useRouteParams } from '@vueuse/router' +import { useBridgePairsQuery } from '@/queries/bridge' +import { CoinCategory } from '@/queries/exchange-rates' -export function useBridgePair() { +export async function useBridgePair(coinCategory: CoinCategory) { const router = useRouter() - - - if(!bridgePairs.value.length){ + console.log('bridgePairs', bridgePairs.value) + + if (!bridgePairs.value.length) { + const { data } = useBridgePairsQuery(ref(coinCategory)) + bridgePairs.value = data.value! } - const selectedPairId = ref(bridgePairs.value[0].id) - + const selectedPairId = ref(bridgePairs.value[0].originTokenId) + const selectedPair = computed(() => { - const pair = bridgePairs.value.find((a) => a.id === selectedPairId.value) + const pair = bridgePairs.value.find((a) => a.originTokenId === selectedPairId.value) if (pair) { return pair } @@ -24,34 +28,26 @@ export function useBridgePair() { const defaultPairStr = `${bridgePairs.value[0].originSymbol}-${bridgePairs.value[0].targetSymbol}` const pairStr = useRouteParams('pair', defaultPairStr) as Ref const fromSymbol = computed(() => pairStr.value.split('-')[0]) - let toSymbol:any - if(pairStr.value.split('-').length > 2){ - toSymbol= computed(() => `${pairStr.value.split(/\-/)[1]}-${pairStr.value.split(/\-/)[2]}`) -}else{ - toSymbol= computed(() => pairStr.value.split(/\-/)[1]) -} - - + let toSymbol: any + if (pairStr.value.split('-').length > 2) { + toSymbol = computed(() => `${pairStr.value.split(/\-/)[1]}-${pairStr.value.split(/\-/)[2]}`) + } else { + toSymbol = computed(() => pairStr.value.split(/\-/)[1]) + } - const selected = bridgePairs.value.find( - (a) => - a.originName === fromSymbol.value && - a.targetName === toSymbol.value, - ) + const selected = bridgePairs.value.find((a) => a.originName === fromSymbol.value && a.targetName === toSymbol.value) if (selected) { - selectedPairId.value = selected.id + selectedPairId.value = selected.originTokenId } - function selectBridgePair(id: number) { - + function selectBridgePair(id: string) { selectedPairId.value = id - + // redirect - const pair = bridgePairs.value.find((pair) => pair.id === id) + const pair = bridgePairs.value.find((pair) => pair.originTokenId === id) if (pair) { - const pairSymbol = `${pair.originName}-${pair.targetName}` - + router.push({ path: `/bridge/${pairSymbol}`, }) diff --git a/src/lib/bridge-utils.ts b/src/lib/bridge-utils.ts new file mode 100644 index 00000000..ee45f5c3 --- /dev/null +++ b/src/lib/bridge-utils.ts @@ -0,0 +1,506 @@ +import dayjs from 'dayjs' +import Decimal from 'decimal.js' +import BigNumber from 'bignumber.js' +import { assetReqReturnType, bridgeAssetPairReturnType } from '@/queries/types/bridge' +export const formatSat = (value: string | number, dec = 8) => { + if (!value) return '0' + + const v = BigNumber(value).div(Math.pow(10, dec)) + const arr = v.toString().split('.') + if (v.toString().indexOf('e') > -1 || (arr[1] && arr[1].length > dec)) { + return BigNumber(v).toFixed(dec) + } + return v.toString() +} + +export function amountRaw(amount: string, decimals: number, maxValue?: BigNumber | bigint | string) { + const _amountRaw = new BigNumber(amount).multipliedBy(Math.pow(10, decimals)) + if (maxValue) { + const _maxValue = new BigNumber(maxValue.toString()).multipliedBy(9999999).div(10000000) + if (_amountRaw.gte(_maxValue)) { + return maxValue.toString() + } + } + return _amountRaw.toFixed(0) +} + +const confirmNumberBySeqAndAmount = function ( + amount: number, + seq: number[][], + network: 'BTC' | 'BRC20' | 'RUNES' | 'MVC' | 'MRC20' +) { + for (const item of seq) { + const [start, end, confirmBtc, confirmMvc] = item + if (end) { + if (start <= amount && amount <= end) { + if (network === 'MVC') { + return confirmMvc + } else { + return confirmBtc + } + } + } else { + if (start <= amount) { + if (network === 'MVC') { + return confirmMvc + } else { + return confirmBtc + } + } + } + } + return 5 +} + +export type FeeInfo = { + receiveAmount: number | string + minerFee: number | string + bridgeFee: number | string + totalFee: number | string + confirmNumber: number | string +} +export const calcRedeemBtcInfo = (redeemAmount: number, assetInfo: bridgeAssetPairReturnType): FeeInfo => { + const { feeBtc, amountLimitMaximum, amountLimitMinimum, confirmSequence, transactionSize, assetList } = assetInfo + if (redeemAmount < Number(amountLimitMinimum)) { + throw new Error('amount less than minimum amount') + } + if (redeemAmount > Number(amountLimitMaximum)) { + throw new Error('amount greater than maximum amount') + } + const confirmNumber = confirmNumberBySeqAndAmount( + redeemAmount, + confirmSequence, + // mint btc -> mvc, get mvc confirm number + 'MVC' + ) + const btcAsset = assetList.find((item) => item.network === 'BTC') + if (!btcAsset) throw new Error('no assrt') + const bridgeFee = (redeemAmount * btcAsset.feeRateNumeratorRedeem) / 10000 + btcAsset.feeRateConstRedeem + const minerFee = transactionSize.BTC_REDEEM * feeBtc + const totalFee = Math.floor(bridgeFee + minerFee) + const receiveAmount = redeemAmount - totalFee + return { + receiveAmount: formatSat(receiveAmount), + minerFee: formatSat(minerFee), + bridgeFee: formatSat(bridgeFee), + totalFee: formatSat(totalFee), + confirmNumber, + } +} + +export const calcRedeemBrc20Info = ( + redeemAmount: number, + assetInfo: bridgeAssetPairReturnType, + asset: assetReqReturnType +): FeeInfo => { + const { btcPrice, feeBtc, amountLimitMaximum, amountLimitMinimum, confirmSequence, transactionSize } = assetInfo + + const brcAmount = redeemAmount / 10 ** (asset.decimals - asset.trimDecimals) + const redeemBrc20EqualBtcAmount = ((asset.price * Number(brcAmount)) / btcPrice) * 10 ** 8 + + if (redeemBrc20EqualBtcAmount < Number(amountLimitMinimum)) { + throw new Error('amount less than minimum amount') + } + if (redeemBrc20EqualBtcAmount > Number(amountLimitMaximum)) { + throw new Error('amount greater than maximum amount') + } + + const confirmNumber = confirmNumberBySeqAndAmount( + redeemBrc20EqualBtcAmount, + confirmSequence, + // mint btc -> mvc, get mvc confirm number + 'MVC' + ) + const bridgeFeeConst = BigInt( + Math.floor( + (((asset.feeRateConstRedeem / 10 ** 8) * btcPrice) / asset.price) * 10 ** (asset.decimals - asset.trimDecimals) + ) + ) + const bridgeFeePercent = (BigInt(redeemAmount) * BigInt(asset.feeRateNumeratorRedeem)) / 10000n + const bridgeFee = bridgeFeeConst + bridgeFeePercent + const minerFee = BigInt( + Math.floor( + (((transactionSize.BRC20_REDEEM / 10 ** 8) * feeBtc * btcPrice) / asset.price) * + 10 ** (asset.decimals - asset.trimDecimals) + ) + ) + const totalFee = bridgeFee + minerFee + const receiveAmount = BigInt(redeemAmount) - totalFee + return { + receiveAmount: formatSat(String(receiveAmount), asset.decimals - asset.trimDecimals), + minerFee: formatSat(String(minerFee), asset.decimals - asset.trimDecimals), + bridgeFee: formatSat(String(bridgeFee), asset.decimals - asset.trimDecimals), + totalFee: formatSat(String(totalFee), asset.decimals - asset.trimDecimals), + confirmNumber, + } +} + +export const calcMintBtcInfo = (mintAmount: number, assetInfo: bridgeAssetPairReturnType): FeeInfo => { + const { + btcPrice, + mvcPrice, + feeBtc, + feeMvc, + amountLimitMaximum, + amountLimitMinimum, + confirmSequence, + transactionSize, + assetList, + } = assetInfo + console.log('mintAmount', mintAmount) + + if (mintAmount < Number(amountLimitMinimum)) { + throw new Error('amount less than minimum amount') + } + if (mintAmount > Number(amountLimitMaximum)) { + throw new Error('amount greater than maximum amount') + } + const confirmNumber = confirmNumberBySeqAndAmount( + mintAmount, + confirmSequence, + // mint btc -> mvc, get mvc confirm number + 'BTC' + ) + const btcAsset = assetList.find((item) => item.network === 'BTC') + if (!btcAsset) throw new Error('no asset') + let bridgeFee: number = 0 + let minerFee: number = 0 + if (btcAsset.feeRateNumeratorMint > 0 || btcAsset.feeRateConstMint > 0) { + bridgeFee = (mintAmount * btcAsset.feeRateNumeratorMint) / 10000 + btcAsset.feeRateConstMint + minerFee = (transactionSize.BTC_MINT * feeMvc * mvcPrice) / btcPrice + } + const totalFee = Math.floor(bridgeFee + minerFee) + const receiveAmount = mintAmount - totalFee + + return { + receiveAmount: formatSat(receiveAmount), + minerFee: formatSat(minerFee), + bridgeFee: formatSat(bridgeFee), + totalFee: formatSat(totalFee), + confirmNumber, + } +} + +export const calcMintBtcRange = (assetInfo: bridgeAssetPairReturnType) => { + const { amountLimitMaximum, amountLimitMinimum } = assetInfo + return [Number(formatSat(amountLimitMinimum)), Number(formatSat(amountLimitMaximum))] +} + +export const calcRedeemBtcRange = (assetInfo: bridgeAssetPairReturnType) => { + const { amountLimitMaximum, amountLimitMinimum } = assetInfo + return [Number(formatSat(amountLimitMinimum)), Number(formatSat(amountLimitMaximum))] +} + +export const calcMintBrc20Info = ( + mintAmount: number, + assetInfo: bridgeAssetPairReturnType, + asset: assetReqReturnType +): FeeInfo => { + const { + btcPrice, + mvcPrice, + feeBtc, + feeMvc, + amountLimitMaximum, + amountLimitMinimum, + confirmSequence, + transactionSize, + assetList, + } = assetInfo + const assetRdex = asset + const mintBrc20EqualBtcAmount = ((assetRdex.price * Number(mintAmount)) / btcPrice) * 10 ** 8 + if (Number(mintBrc20EqualBtcAmount) < Number(amountLimitMinimum)) { + throw new Error('amount less than minimum amount') + } + if (Number(mintBrc20EqualBtcAmount) > Number(amountLimitMaximum)) { + throw new Error('amount greater than maximum amount') + } + + const confirmNumber = confirmNumberBySeqAndAmount( + mintBrc20EqualBtcAmount, + confirmSequence, + // mint btc -> mvc, get mvc confirm number + 'BRC20' + ) + let bridgeFee: number = 0 + let minerFee: number = 0 + if (assetRdex.feeRateNumeratorMint > 0 || assetRdex.feeRateConstMint > 0) { + bridgeFee = + (Number(mintAmount) * assetRdex.feeRateNumeratorMint) / 10000 + + ((assetRdex.feeRateConstMint / 10 ** 8) * btcPrice) / assetRdex.price + minerFee = (transactionSize.BTC_MINT * feeMvc * mvcPrice) / 10 ** 8 / assetRdex.price + } + const totalFee = bridgeFee + minerFee + const receiveAmount = Number(mintAmount) - totalFee + const receiveAmountFixed = receiveAmount.toFixed(assetRdex.decimals - assetRdex.trimDecimals) + return { + receiveAmount: receiveAmountFixed, + minerFee, + bridgeFee, + totalFee, + confirmNumber, + } +} + +export const calcMintBrc20Range = (assetInfo: bridgeAssetPairReturnType, asset: assetReqReturnType) => { + const { btcPrice, amountLimitMaximum, amountLimitMinimum } = assetInfo + const assetRdex = asset + const minAmount = ((Number(amountLimitMinimum) / 1e8) * btcPrice) / assetRdex.price + const maxAmount = ((Number(amountLimitMaximum) / 1e8) * btcPrice) / assetRdex.price + return [minAmount, maxAmount] +} + +export function determineAddressInfo(address: string): string { + if (address.startsWith('bc1q')) { + return 'p2wpkh' + } + if (address.startsWith('tb1q')) { + return 'p2wpkh' + } + + if (address.startsWith('bc1p')) { + return 'p2tr' + } + + if (address.startsWith('tb1p')) { + return 'p2tr' + } + + if (address.startsWith('1')) { + return 'p2pkh' + } + if (address.startsWith('3') || address.startsWith('2')) { + return 'p2sh' + } + if (address.startsWith('m') || address.startsWith('n')) { + return 'p2pkh' + } + return 'unknown' +} + +export function prettyTimestamp(timestamp: number, isInSeconds = false, cutThisYear = false) { + if (isInSeconds) timestamp = timestamp * 1000 + + if (cutThisYear) return dayjs(timestamp).format('MM-DD HH:mm:ss') + + return dayjs(timestamp).format('YYYY-MM-DD HH:mm:ss') +} + +export const formatUnitToSats = (value: number | string, decimal: number = 8) => { + if (!value) { + return 0 + } + return new Decimal(value).mul(10 ** decimal).toNumber() +} + +export const formatUnitToBtc = (value: number | string, decimal: number = 8) => { + if (!value) { + return 0 + } + return new Decimal(value).div(10 ** decimal).toNumber() +} + +export const calcMintRunesInfo = ( + mintAmount: number, + assetInfo: bridgeAssetPairReturnType, + asset: assetReqReturnType +): FeeInfo => { + const { + btcPrice, + mvcPrice, + feeBtc, + feeMvc, + amountLimitMaximum, + amountLimitMinimum, + confirmSequence, + transactionSize, + assetList, + } = assetInfo + console.log('asset:', asset) + + console.log(new Decimal(mintAmount).mul(10 ** asset.decimals)) + + // // 转换成btc价值 + const mintRunesEqualBtcAmount = ((asset.price * Number(mintAmount)) / btcPrice) * 10 ** 8 + console.log('mintRunesEqualBtcAmount:', mintRunesEqualBtcAmount) + console.log('mintRunesEqualBtcAmount:', amountLimitMinimum, amountLimitMaximum) + + if (Number(mintRunesEqualBtcAmount) < Number(amountLimitMinimum)) { + throw new Error('amount less than minimum amount') + } + if (Number(mintRunesEqualBtcAmount) > Number(amountLimitMaximum)) { + throw new Error('amount greater than maximum amount') + } + + const confirmNumber = confirmNumberBySeqAndAmount( + mintRunesEqualBtcAmount, + confirmSequence, + // mint btc -> mvc, get mvc confirm number + 'RUNES' + ) + let bridgeFee: number = 0 + let minerFee: number = 0 + if (asset.feeRateNumeratorMint > 0 || asset.feeRateConstMint > 0) { + bridgeFee = + (Number(mintAmount) * asset.feeRateNumeratorMint) / 10000 + + ((asset.feeRateConstMint / 10 ** 8) * btcPrice) / asset.price + minerFee = (transactionSize.BTC_MINT * feeMvc * mvcPrice) / 10 ** 8 / asset.price + } + const totalFee = bridgeFee + minerFee + const receiveAmount = Number(mintAmount) - totalFee + const receiveAmountFixed = receiveAmount.toFixed(asset.decimals - asset.trimDecimals) + return { + receiveAmount: receiveAmountFixed, + minerFee, + bridgeFee, + totalFee, + confirmNumber, + } +} + +export const calcRedeemRunesInfo = ( + redeemAmount: number, + assetInfo: bridgeAssetPairReturnType, + asset: assetReqReturnType +): FeeInfo => { + const { btcPrice, feeBtc, amountLimitMaximum, amountLimitMinimum, confirmSequence, transactionSize } = assetInfo + + const runesAmount = redeemAmount / 10 ** (asset.decimals - asset.trimDecimals) + const redeemBrc20EqualBtcAmount = ((asset.price * Number(runesAmount)) / btcPrice) * 10 ** 8 + + if (redeemBrc20EqualBtcAmount < Number(amountLimitMinimum)) { + throw new Error('amount less than minimum amount') + } + if (redeemBrc20EqualBtcAmount > Number(amountLimitMaximum)) { + throw new Error('amount greater than maximum amount') + } + + const confirmNumber = confirmNumberBySeqAndAmount( + redeemBrc20EqualBtcAmount, + confirmSequence, + // mint btc -> mvc, get mvc confirm number + 'MVC' + ) + const bridgeFeeConst = BigInt( + Math.floor( + (((asset.feeRateConstRedeem / 10 ** 8) * btcPrice) / asset.price) * 10 ** (asset.decimals - asset.trimDecimals) + ) + ) + const bridgeFeePercent = (BigInt(redeemAmount) * BigInt(asset.feeRateNumeratorRedeem)) / 10000n + const bridgeFee = bridgeFeeConst + bridgeFeePercent + const minerFee = BigInt( + Math.floor( + (((transactionSize.RUNES_REDEEM / 10 ** 8) * feeBtc * btcPrice) / asset.price) * + 10 ** (asset.decimals - asset.trimDecimals) + ) + ) + const totalFee = bridgeFee + minerFee + const receiveAmount = BigInt(redeemAmount) - totalFee + return { + receiveAmount: formatSat(String(receiveAmount), asset.decimals - asset.trimDecimals), + minerFee: formatSat(String(minerFee), asset.decimals - asset.trimDecimals), + bridgeFee: formatSat(String(bridgeFee), asset.decimals - asset.trimDecimals), + totalFee: formatSat(String(totalFee), asset.decimals - asset.trimDecimals), + confirmNumber, + } +} + +export const calcRedeemMrc20Info = ( + redeemAmount: number, + assetInfo: bridgeAssetPairReturnType, + asset: assetReqReturnType +): FeeInfo => { + const { btcPrice, feeBtc, amountLimitMaximum, amountLimitMinimum, confirmSequence, transactionSize } = assetInfo + + const brcAmount = redeemAmount / 10 ** (asset.decimals - asset.trimDecimals) + const redeemMrc20EqualBtcAmount = ((asset.price * Number(brcAmount)) / btcPrice) * 10 ** 8 + + if (redeemMrc20EqualBtcAmount < Number(amountLimitMinimum)) { + throw new Error('amount less than minimum amount') + } + if (redeemMrc20EqualBtcAmount > Number(amountLimitMaximum)) { + throw new Error('amount greater than maximum amount') + } + + const confirmNumber = confirmNumberBySeqAndAmount( + redeemMrc20EqualBtcAmount, + confirmSequence, + // mint btc -> mvc, get mvc confirm number + 'MVC' + ) + const bridgeFeeConst = BigInt( + Math.floor( + (((asset.feeRateConstRedeem / 10 ** 8) * btcPrice) / asset.price) * 10 ** (asset.decimals - asset.trimDecimals) + ) + ) + const bridgeFeePercent = (BigInt(redeemAmount) * BigInt(asset.feeRateNumeratorRedeem)) / 10000n + const bridgeFee = bridgeFeeConst + bridgeFeePercent + const minerFee = BigInt( + Math.floor( + (((transactionSize.RUNES_REDEEM / 10 ** 8) * feeBtc * btcPrice) / asset.price) * + 10 ** (asset.decimals - asset.trimDecimals) + ) + ) + const totalFee = bridgeFee + minerFee + const receiveAmount = BigInt(redeemAmount) - totalFee + return { + receiveAmount: formatSat(String(receiveAmount), asset.decimals - asset.trimDecimals), + minerFee: formatSat(String(minerFee), asset.decimals - asset.trimDecimals), + bridgeFee: formatSat(String(bridgeFee), asset.decimals - asset.trimDecimals), + totalFee: formatSat(String(totalFee), asset.decimals - asset.trimDecimals), + confirmNumber, + } +} + +export const calcMintMRC20Info = ( + mintAmount: number, + assetInfo: bridgeAssetPairReturnType, + asset: assetReqReturnType +): FeeInfo => { + const { + btcPrice, + mvcPrice, + feeBtc, + feeMvc, + amountLimitMaximum, + amountLimitMinimum, + confirmSequence, + transactionSize, + assetList, + } = assetInfo + + const mintRawAmount = new Decimal(mintAmount).mul(10 ** asset.decimals).toFixed(0) + const mintMrc20EqualBtcAmount = ((asset.price * Number(mintAmount)) / btcPrice) * 10 ** 8 + + if (Number(mintMrc20EqualBtcAmount) < Number(amountLimitMinimum)) { + throw new Error('amount less than minimum amount') + } + if (Number(mintMrc20EqualBtcAmount) > Number(amountLimitMaximum)) { + throw new Error('amount greater than maximum amount') + } + + const confirmNumber = confirmNumberBySeqAndAmount( + mintMrc20EqualBtcAmount, + confirmSequence, + // mint btc -> mvc, get mvc confirm number + 'MRC20' + ) + let bridgeFee: number = 0 + let minerFee: number = 0 + if (asset.feeRateNumeratorMint > 0 || asset.feeRateConstMint > 0) { + bridgeFee = + (Number(mintAmount) * asset.feeRateNumeratorMint) / 10000 + + ((asset.feeRateConstMint / 10 ** 8) * btcPrice) / asset.price + minerFee = (transactionSize.BTC_MINT * feeMvc * mvcPrice) / 10 ** 8 / asset.price + } + const totalFee = bridgeFee + minerFee + const receiveAmount = Number(mintAmount) - totalFee + const receiveAmountFixed = receiveAmount.toFixed(asset.decimals - asset.trimDecimals) + return { + receiveAmount: receiveAmountFixed, + minerFee: minerFee.toFixed(asset.decimals), + bridgeFee: bridgeFee.toFixed(asset.decimals), + totalFee: totalFee.toFixed(asset.decimals), + confirmNumber, + } +} diff --git a/src/lib/types/protocol.ts b/src/lib/types/protocol.ts index 200c4826..27b44841 100644 --- a/src/lib/types/protocol.ts +++ b/src/lib/types/protocol.ts @@ -1,7 +1,7 @@ export enum Protocol { - Rune = 'Rune', - BRC20 = 'BRC-20', - MRC20 = 'MRC-20', Native = 'Native', + MRC20 = 'MRC20', + BRC20 = 'BRC20', + Runes = 'Runes', MetaContract = 'MetaContract', } diff --git a/src/pages/wallet/Bridge/Index.vue b/src/pages/wallet/Bridge/Index.vue index f5241463..9a420c08 100644 --- a/src/pages/wallet/Bridge/Index.vue +++ b/src/pages/wallet/Bridge/Index.vue @@ -1,48 +1,38 @@