Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/shiny-poets-swap.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@sovryn/sdk": patch
---

Support non-18-decimal tokens (USDT0) in the RSK AMM swap route: add USDT0 to the tradeable token list and normalize amounts between the router's 18-decimal convention and each token's native units in quote/swap/approve
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,12 @@ export const RusdtMigrationNotice: FC<RusdtMigrationNoticeProps> = ({
}, [blockNumber]);

const migrationCapacityWei = useMemo(() => {
const balanceLimit = bigNumberic(migrationUsdtBalanceWei);
// `weiBalance` from useAssetBalance is 18-decimals-normalized while the
// allowance is read from the contract in USDT0's native 6 decimals —
// bring the balance down to native units before comparing.
const balanceLimit = bigNumberic(migrationUsdtBalanceWei).div(
RUSDT_TO_USDT_DECIMALS_MULTIPLIER,
);
const allowanceLimit = bigNumberic(migrationAllowanceWei);

return balanceLimit.lt(allowanceLimit) ? balanceLimit : allowanceLimit;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ export const SMART_ROUTER_STABLECOINS = [
COMMON_SYMBOLS.DOC,
'RDOC',
COMMON_SYMBOLS.RUSDT,
COMMON_SYMBOLS.USDT0,
'USDT',
'USDC',
'DAI',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,30 @@ import { COMMON_SYMBOLS } from '../../../utils/asset';
import { PromotionData } from './MarketMakingPage.types';
import { AmmLiquidityPool } from './utils/AmmLiquidityPool';

const isAddress = (value?: string) => /^0x[a-fA-F0-9]{40}$/.test(value || '');

// USDT0/RBTC pool activation. Leave empty until the pool is deployed.
const USDT0_BTC_AMM_CONVERTER = '0xd107e06964112d3f70cfb386565dfbda16ae71f3';
const USDT0_BTC_AMM_POOL_TOKEN = '0x591e07d721c2e22eeb4bf33d0b3377daca886fcc';

const MAINNET_AMM_USDT0_WRBTC =
isAddress(USDT0_BTC_AMM_CONVERTER) && isAddress(USDT0_BTC_AMM_POOL_TOKEN)
? [
new AmmLiquidityPool(
COMMON_SYMBOLS.USDT0,
COMMON_SYMBOLS.BTC,
1,
ChainIds.RSK_MAINNET,
USDT0_BTC_AMM_CONVERTER!,
USDT0_BTC_AMM_POOL_TOKEN!,
undefined,
true,
).setSovRewards(false),
]
: [];

export const MAINNET_AMM = [
...MAINNET_AMM_USDT0_WRBTC,
new AmmLiquidityPool(
COMMON_SYMBOLS.DLLR,
COMMON_SYMBOLS.BTC,
Expand Down Expand Up @@ -143,8 +166,6 @@ export const MAINNET_AMM = [
ChainIds.RSK_MAINNET,
'0xF1DeE3175593f4e13a2b9e09a5FaafC513c9A27F',
'0xfd834bbcde8c3ac4766bf5c1f5d861400103087b',
undefined,
true,
),
];

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,16 @@ import {
} from '@sovryn/ui';
import { Decimal } from '@sovryn/utils';

import { RSK_CHAIN_ID } from '../../../../../config/chains';

import { AssetRenderer } from '../../../../2_molecules/AssetRenderer/AssetRenderer';
import { CurrentStatistics } from '../../../../2_molecules/CurrentStatistics/CurrentStatistics';
import { LabelWithTabsAndMaxButton } from '../../../../2_molecules/LabelWithTabsAndMaxButton/LabelWithTabsAndMaxButton';
import { MaxButton } from '../../../../2_molecules/MaxButton/MaxButton';
import { useAccount } from '../../../../../hooks/useAccount';
import { useWeiAmountInput } from '../../../../../hooks/useWeiAmountInput';
import { translations } from '../../../../../locales/i18n';
import { COMMON_SYMBOLS } from '../../../../../utils/asset';
import { COMMON_SYMBOLS, findAsset } from '../../../../../utils/asset';
import { decimalic, toWei } from '../../../../../utils/math';
import { useCheckPoolBlocked } from '../../hooks/useCheckPoolBlocked';
import { useGetExpectedTokenAmount } from '../../hooks/useGetExpectedTokenAmount';
Expand Down Expand Up @@ -63,7 +65,13 @@ export const AdjustAndDepositModal: FC<AdjustAndDepositModalProps> = ({
}) => {
const [adjustType, setAdjustType] = useState(AdjustType.Deposit);
const [selectedAsset, setSelectedAsset] = useState(pool.assetA);
const [value, setValue, amount] = useWeiAmountInput('');
// Parse the input with the asset's real decimals so `amount` is in the
// token's native units (USDT0 has 6 decimals, other AMM tokens 18).
const tokenDecimals = useMemo(
() => findAsset(selectedAsset, RSK_CHAIN_ID)?.decimals ?? 18,
[selectedAsset],
);
const [value, setValue, amount] = useWeiAmountInput('', tokenDecimals);
const { account } = useAccount();

const poolBlocked = useCheckPoolBlocked(pool);
Expand Down Expand Up @@ -135,8 +143,10 @@ export const AdjustAndDepositModal: FC<AdjustAndDepositModalProps> = ({
account,
);

const decimalValue = useMemo(() => decimalic(value), [value]);

const poolWeiAmount = calculatePoolWeiAmount(
Decimal.fromBigNumberString(amount.toString()),
decimalValue,
selectedAsset === pool.assetA ? balanceA : balanceB,
tokenPoolBalance,
);
Expand Down Expand Up @@ -178,12 +188,16 @@ export const AdjustAndDepositModal: FC<AdjustAndDepositModalProps> = ({
[isDeposit, maxTokenToDepositAmount, maxWithdrawalAmount],
);

const handleMaxClick = useCallback(
() => setValue(maxBalance.toString()),
[maxBalance, setValue],
);

const decimalValue = useMemo(() => decimalic(value), [value]);
const handleMaxClick = useCallback(() => {
// Floor to the asset's decimals so the value stays parseable in the
// token's native units (e.g. USDT0 supports only 6 decimal places).
const [integer, fraction] = maxBalance.toString().split('.');
setValue(
fraction && fraction.length > tokenDecimals
? `${integer}.${fraction.slice(0, tokenDecimals)}`
: maxBalance.toString(),
);
}, [maxBalance, setValue, tokenDecimals]);

const expectedTokenAmount = useGetExpectedTokenAmount(pool, decimalValue);
const minReturn1 = getMinReturn(decimalAmount);
Expand Down Expand Up @@ -339,6 +353,7 @@ export const AdjustAndDepositModal: FC<AdjustAndDepositModalProps> = ({
value={value}
onChangeText={setValue}
maxAmount={maxBalance.toNumber()}
decimalPrecision={tokenDecimals}
label={t(translations.common.amount)}
className="max-w-none"
unit={<AssetRenderer asset={selectedAsset} />}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ export const NewPoolStatistics: FC<NewPoolStatisticsProps> = ({

return adjustType === AdjustType.Deposit || isInitialDeposit
? balanceB.add(expectedTokenAmount)
: decimalAmount.eq(balanceA)
: decimalValue.eq(balanceA)
? Decimal.ZERO
: balanceB.sub(expectedTokenAmount);
}, [
Expand All @@ -92,7 +92,6 @@ export const NewPoolStatistics: FC<NewPoolStatisticsProps> = ({
isInitialDeposit,
balanceB,
expectedTokenAmount,
decimalAmount,
balanceA,
isDeposit,
decimalValue,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { RSK_CHAIN_ID } from '../../../../../../config/chains';

import { asyncCall } from '../../../../../../store/rxjs/provider-cache';
import { COMMON_SYMBOLS } from '../../../../../../utils/asset';
import { decimalic, fromWei } from '../../../../../../utils/math';
import { AmmLiquidityPool } from '../../../utils/AmmLiquidityPool';

export const useGetPoolsBalance = (pool: AmmLiquidityPool) => {
Expand Down Expand Up @@ -39,7 +40,12 @@ export const useGetPoolsBalance = (pool: AmmLiquidityPool) => {
asyncCall(
`loanToken/${loanTokenContract.address}/balanceOf/${pool.converter}`,
() =>
contractA.balanceOf(pool.converter).then(Decimal.fromBigNumberString),
contractA
.balanceOf(pool.converter)
// Asset A may not use 18 decimals (e.g. USDT0 has 6).
.then(balance =>
decimalic(fromWei(balance, loanTokenContract.decimals)),
),
),
asyncCall(
`loanToken/${wrbtcContract.address}/balanceOf/${pool.converter}`,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ import { RSK_CHAIN_ID } from '../../../../config/chains';

import { useGetTokenContract } from '../../../../hooks/useGetContract';
import { asyncCall } from '../../../../store/rxjs/provider-cache';
import { COMMON_SYMBOLS } from '../../../../utils/asset';
import { COMMON_SYMBOLS, findAsset } from '../../../../utils/asset';
import { decimalic, fromWei } from '../../../../utils/math';
import { AmmLiquidityPool } from '../utils/AmmLiquidityPool';

export const useGetPoolLiquidity = (pool: AmmLiquidityPool) => {
Expand All @@ -31,13 +32,22 @@ export const useGetPoolLiquidity = (pool: AmmLiquidityPool) => {
}

try {
const fetchBalance = async (contract: Contract, type: string) =>
// Asset A may not use 18 decimals (e.g. USDT0 has 6).
const fetchBalance = async (
contract: Contract,
type: string,
decimals: number = 18,
) =>
await asyncCall(`${type}/balanceOf/${pool.converter}`, () =>
contract.balanceOf(pool.converter),
).then(Decimal.fromBigNumberString);
).then(balance => decimalic(fromWei(balance, decimals)));

const [tokenBalance, btcBalance] = await Promise.all([
fetchBalance(contractTokenA, pool.assetA),
fetchBalance(
contractTokenA,
pool.assetA,
findAsset(pool.assetA, RSK_CHAIN_ID)?.decimals ?? 18,
),
fetchBalance(contractTokenB, COMMON_SYMBOLS.WBTC),
]);

Expand All @@ -62,7 +72,11 @@ export const useGetPoolLiquidity = (pool: AmmLiquidityPool) => {
);

try {
const fetchBalance = async (tokenContract: Contract) =>
// Asset A may not use 18 decimals (e.g. USDT0 has 6).
const fetchBalance = async (
tokenContract: Contract,
decimals: number = 18,
) =>
await asyncCall(
`${
pool.converter
Expand All @@ -71,10 +85,13 @@ export const useGetPoolLiquidity = (pool: AmmLiquidityPool) => {
contract.reserveStakedBalance(
tokenContract.address.toLowerCase(),
),
).then(Decimal.fromBigNumberString);
).then(balance => decimalic(fromWei(balance, decimals)));

const [tokenBalance, btcBalance] = await Promise.all([
fetchBalance(contractTokenA),
fetchBalance(
contractTokenA,
findAsset(pool.assetA, RSK_CHAIN_ID)?.decimals ?? 18,
),
fetchBalance(contractTokenB),
]);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ import {
useGetProtocolContract,
useGetTokenContract,
} from '../../../../hooks/useGetContract';
import { COMMON_SYMBOLS } from '../../../../utils/asset';
import { COMMON_SYMBOLS, findAsset } from '../../../../utils/asset';
import { decimalic, fromWei } from '../../../../utils/math';
import { AmmLiquidityPool } from '../utils/AmmLiquidityPool';

export const useGetUserInfo = (pool: AmmLiquidityPool) => {
Expand Down Expand Up @@ -79,9 +80,12 @@ export const useGetUserInfo = (pool: AmmLiquidityPool) => {
const totalSupply = await contract
.totalSupply()
.then(Decimal.fromBigNumberString);
// Asset A may not use 18 decimals (e.g. USDT0 has 6), so convert its
// raw balance with the asset's real decimals.
const decimalsA = findAsset(assetA, chainId)?.decimals ?? 18;
const converterBalanceA = await contractTokenA
?.balanceOf(pool.converter)
.then(Decimal.fromBigNumberString);
.then(balance => decimalic(fromWei(balance, decimalsA)));
const converterBalanceB = await contractTokenB
?.balanceOf(pool.converter)
.then(Decimal.fromBigNumberString);
Expand Down Expand Up @@ -137,6 +141,8 @@ export const useGetUserInfo = (pool: AmmLiquidityPool) => {
account,
pool.converterVersion,
pool.converter,
assetA,
chainId,
contractTokenA,
contractTokenB,
poolTokenB,
Expand Down
4 changes: 2 additions & 2 deletions packages/sdk/src/_tests/swaps/smart-router.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,12 +117,12 @@ describe('SmartRouter', () => {
});

it('returns all available entries', async () => {
await expect(router.getEntries(chainId)).resolves.toHaveLength(17);
await expect(router.getEntries(chainId)).resolves.toHaveLength(18);
});

it('returns all available destinations for entry token', async () => {
await expect(router.getDestination(chainId, sov)).resolves.toHaveLength(
15,
16,
);
});

Expand Down
1 change: 1 addition & 0 deletions packages/sdk/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@ export const RSK_STABLECOINS: string[] = [
'XUSD',
'DOC',
'RUSDT',
'USDT0',
];
Loading
Loading