diff --git a/config/wallet-config.json b/config/wallet-config.json
index b705ddadfa..5a17ca7f55 100644
--- a/config/wallet-config.json
+++ b/config/wallet-config.json
@@ -95,8 +95,8 @@
"swapsEnabled": true,
"sbtc": {
"enabled": true,
- "swapsEnabled": true,
- "emilyApiUrl": "https://beta.sbtc-emily.com",
+ "swapsEnabled": false,
+ "emilyApiUrl": "https://dev.sbtc-emily-dev.com",
"showPromoLinkOnNetworks": ["testnet", "sbtcTestnet"],
"contracts": {
"mainnet": {
diff --git a/package.json b/package.json
index d26b244db8..26a35d4ad9 100644
--- a/package.json
+++ b/package.json
@@ -248,7 +248,7 @@
"redux-persist": "6.0.0",
"remark-gfm": "4.0.0",
"rxjs": "7.8.1",
- "sbtc": "0.3.0",
+ "sbtc": "0.3.1",
"style-loader": "3.3.4",
"ts-debounce": "4.0.0",
"url": "0.11.3",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 4a6ca6e63a..158727b459 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -354,8 +354,8 @@ importers:
specifier: 7.8.1
version: 7.8.1
sbtc:
- specifier: 0.3.0
- version: 0.3.0(encoding@0.1.13)
+ specifier: 0.3.1
+ version: 0.3.1(encoding@0.1.13)
style-loader:
specifier: 3.3.4
version: 3.3.4(webpack@5.94.0(@swc/core@1.9.3)(esbuild@0.24.0)(webpack-cli@5.1.4(webpack-bundle-analyzer@4.10.2)(webpack-dev-server@4.15.1)(webpack@5.94.0)))
@@ -13245,8 +13245,8 @@ packages:
resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==}
engines: {node: '>=v12.22.7'}
- sbtc@0.3.0:
- resolution: {integrity: sha512-nB6lAEdm+c12RteLeDmdzEqZ57KEB/yPZUiKjeYVTN2VtipXj7wNyudwiT786Qux40LROLUbL2x5yrKYriU0Zg==}
+ sbtc@0.3.1:
+ resolution: {integrity: sha512-dAd0hxgIS1qchzb6tWbBTgoYio/v0Rln9pq3L23cX52DBSI4WpoMr2MRsM8z3lORTeqhIPLd3Tn/IdB+jKYdIA==}
sc-errors@3.0.0:
resolution: {integrity: sha512-rIqv2HTPb9DVreZwK/DV0ytRUqyw2DbDcoB9XTKjEQL7oMEQKsfPA8V8dGGr7p8ZYfmvaRIGZ4Wu5qwvs/hGDA==}
@@ -31414,7 +31414,7 @@ snapshots:
dependencies:
xmlchars: 2.2.0
- sbtc@0.3.0(encoding@0.1.13):
+ sbtc@0.3.1(encoding@0.1.13):
dependencies:
'@btc-helpers/rpc': 2.0.0(encoding@0.1.13)
'@noble/secp256k1': 2.1.0
diff --git a/src/app/pages/swap/bitflow-swap.utils.ts b/src/app/pages/swap/bitflow-swap.utils.ts
index c319215fba..715e2a16ad 100644
--- a/src/app/pages/swap/bitflow-swap.utils.ts
+++ b/src/app/pages/swap/bitflow-swap.utils.ts
@@ -55,6 +55,7 @@ export function getCrossChainSwapSubmissionData(values: SwapFormValues): SwapSub
fee: 0,
feeCurrency: 'BTC',
feeType: BtcFeeType.Standard,
+ liquidityFee: 0,
maxSignerFee: 0,
protocol: 'Bitcoin L2 Labs',
dexPath: [],
diff --git a/src/app/pages/swap/components/swap-details/swap-details.tsx b/src/app/pages/swap/components/swap-details/swap-details.tsx
index 90d8fb9f09..bc9399a17a 100644
--- a/src/app/pages/swap/components/swap-details/swap-details.tsx
+++ b/src/app/pages/swap/components/swap-details/swap-details.tsx
@@ -42,7 +42,6 @@ export function SwapDetails() {
)
return null;
- const liquidityFee = swapSubmissionData.liquidityFee;
const maxSignerFee = satToBtc(swapSubmissionData.maxSignerFee ?? 0);
const formattedMinToReceive = formatMoneyPadded(
@@ -80,18 +79,18 @@ export function SwapDetails() {
}
/>
+
-
- {liquidityFee ? (
-
- ) : null}
+
{maxSignerFee ? (
) : null}
-
{
+ const sBtcPegCap = sBtcLimits?.pegCap;
+ if (!sBtcPegCap) return;
+ const currentSupplyValue = supply?.result && cvToValue(hexToCV(supply?.result));
+ return convertAmountToFractionalUnit(
+ createMoney(new BigNumber(Number(sBtcPegCap - currentSupplyValue)), 'BTC', BTC_DECIMALS)
+ );
+ }, [sBtcLimits?.pegCap, supply?.result]);
+
+ const sBtcDepositCapMin = createMoney(
+ new BigNumber(sBtcLimits?.perDepositMinimum ?? defaultSbtcLimits.perDepositMinimum),
+ 'BTC'
+ );
+ const sBtcDepositCapMax = createMoney(
+ new BigNumber(sBtcLimits?.perDepositCap ?? defaultSbtcLimits.perDepositCap),
+ 'BTC'
+ );
const initialValues: SwapFormValues = {
fee: '0',
@@ -44,6 +78,55 @@ export function useSwapForm() {
return true;
},
})
+ .test({
+ message: `Min amount is ${convertAmountToBaseUnit(sBtcDepositCapMin).toString()} BTC`,
+ test(value) {
+ if (!isCrossChainSwap) return true;
+ const { swapAssetBase } = this.parent;
+ const valueInFractionalUnit = convertAmountToFractionalUnit(
+ createMoney(
+ new BigNumber(Number(value)),
+ swapAssetBase.balance.symbol,
+ swapAssetBase.balance.decimals
+ )
+ );
+ if (valueInFractionalUnit.isLessThan(sBtcDepositCapMin.amount)) return false;
+ return true;
+ },
+ })
+ .test({
+ message: `Max amount is ${convertAmountToBaseUnit(sBtcDepositCapMax).toString()} BTC`,
+ test(value) {
+ if (!isCrossChainSwap) return true;
+ const { swapAssetBase } = this.parent;
+ const valueInFractionalUnit = convertAmountToFractionalUnit(
+ createMoney(
+ new BigNumber(Number(value)),
+ swapAssetBase.balance.symbol,
+ swapAssetBase.balance.decimals
+ )
+ );
+ if (valueInFractionalUnit.isGreaterThan(sBtcDepositCapMax.amount)) return false;
+ return true;
+ },
+ })
+ .test({
+ message: 'Amount exceeds capped supply',
+ test(value) {
+ if (!isCrossChainSwap) return true;
+ const { swapAssetBase } = this.parent;
+ const valueInFractionalUnit = convertAmountToFractionalUnit(
+ createMoney(
+ new BigNumber(Number(value)),
+ swapAssetBase.balance.symbol,
+ swapAssetBase.balance.decimals
+ )
+ );
+ if (!remainingSbtcPegCapSupply) return true;
+ if (valueInFractionalUnit.isGreaterThan(remainingSbtcPegCapSupply)) return false;
+ return true;
+ },
+ })
.required(FormErrorMessages.AmountRequired)
.typeError(FormErrorMessages.MustBeNumber)
.positive(FormErrorMessages.MustBePositive),
diff --git a/src/app/pages/swap/swap.context.ts b/src/app/pages/swap/swap.context.ts
index fe67bf0e94..2ff547145e 100644
--- a/src/app/pages/swap/swap.context.ts
+++ b/src/app/pages/swap/swap.context.ts
@@ -5,7 +5,7 @@ import type { SwapAsset } from '@leather.io/query';
import type { SwapFormValues } from '@shared/models/form.model';
export interface SwapSubmissionData extends SwapFormValues {
- liquidityFee?: number;
+ liquidityFee: number;
maxSignerFee?: number;
protocol: string;
router: SwapAsset[];
diff --git a/src/app/query/sbtc/sbtc-limits.query.ts b/src/app/query/sbtc/sbtc-limits.query.ts
new file mode 100644
index 0000000000..df0e931af9
--- /dev/null
+++ b/src/app/query/sbtc/sbtc-limits.query.ts
@@ -0,0 +1,60 @@
+import { useQuery } from '@tanstack/react-query';
+import axios from 'axios';
+
+import { useStacksClient } from '@leather.io/query';
+import { getStacksContractIdStringParts } from '@leather.io/stacks';
+
+import { useCurrentStacksAccountAddress } from '@app/store/accounts/blockchain/stacks/stacks-account.hooks';
+
+import { useConfigSbtc } from '../common/remote-config/remote-config.query';
+
+export const defaultSbtcLimits = {
+ pegCap: 1000000000000,
+ perDepositMinimum: 100000,
+ perDepositCap: 100000000,
+ perWithdrawalCap: 100000000,
+ accountCaps: {},
+};
+
+interface GetSbtcLimitsResponse {
+ pegCap: number;
+ perDepositCap: number;
+ perWithdrawalCap: number;
+ perDepositMinimum: number;
+ accountCaps: Record;
+}
+
+async function getSbtcLimits(apiUrl: string): Promise {
+ const resp = await axios.get(`${apiUrl}/limits`, {
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ });
+ return resp.data;
+}
+
+export function useGetSbtcLimits() {
+ const { emilyApiUrl } = useConfigSbtc();
+ return useQuery({
+ queryKey: ['get-sbtc-limits'],
+ queryFn: () => getSbtcLimits(emilyApiUrl),
+ });
+}
+
+export function useGetCurrentSbtcSupply() {
+ const client = useStacksClient();
+ const { contractId } = useConfigSbtc();
+ const { contractAddress } = getStacksContractIdStringParts(contractId);
+ const stxAddress = useCurrentStacksAccountAddress();
+
+ return useQuery({
+ queryKey: ['get-current-sbtc-supply'],
+ queryFn: () =>
+ client.callReadOnlyFunction({
+ contractAddress,
+ contractName: 'sbtc-token',
+ functionName: 'get-total-supply',
+ readOnlyFunctionArgs: { sender: stxAddress, arguments: [] },
+ }),
+ });
+}