)}
diff --git a/src/components/transfer.tsx b/src/components/transfer.tsx
index 48d7ea5..9d777b1 100644
--- a/src/components/transfer.tsx
+++ b/src/components/transfer.tsx
@@ -4,7 +4,7 @@ import Button from "@/ui/button";
import BalanceInput from "./balance-input";
import ChainSelect from "./chain-select";
import TransferSection from "./transfer-section";
-import { isAssetExcess, parseCross } from "@/utils";
+import { formatBalance, isAssetExcess, parseCross } from "@/utils";
import { useTalisman, useTransfer } from "@/hooks";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import SwitchCross from "./switch-cross";
@@ -13,6 +13,7 @@ import { useAccount, useNetwork, useSwitchNetwork } from "wagmi";
import { Asset, ChainConfig, WalletID } from "@/types";
import { BN_ZERO } from "@polkadot/util";
import notification from "@/ui/notification";
+import Image from "next/image";
const {
defaultSourceChainOptions,
@@ -33,6 +34,7 @@ export default function Transfer() {
targetChain,
sourceAsset,
targetAsset,
+ usdtBalance,
sourceBalance,
targetBalance,
transferAmount,
@@ -76,6 +78,23 @@ export default function Transfer() {
[chain, sourceChain, activeSenderWallet],
);
+ const alert = useMemo(() => {
+ const fee = bridgeInstance?.getCrossInfo()?.fee;
+ const balance = usdtBalance?.asset.value;
+
+ if (fee && balance && fee.amount.gt(balance)) {
+ return (
+
+ );
+ }
+ return null;
+ }, [bridgeInstance, usdtBalance?.asset.value]);
+
const sourceChainRef = useRef(sourceChain);
const targetChainRef = useRef(targetChain);
const sourceAssetRef = useRef(sourceAsset);
@@ -219,7 +238,7 @@ export default function Transfer() {
: [];
return (
-
+
{/* From */}
{/* Send */}
-
);
}
diff --git a/src/config/chains/assethub-polkadot-chain.ts b/src/config/chains/assethub-polkadot-chain.ts
new file mode 100644
index 0000000..ece7e54
--- /dev/null
+++ b/src/config/chains/assethub-polkadot-chain.ts
@@ -0,0 +1,75 @@
+import { ChainConfig, ChainID, ParachainID, WalletID } from "@/types";
+import { bnToBn } from "@polkadot/util";
+
+export const assethubPolkadotChain: ChainConfig = {
+ /**
+ * Chain
+ */
+ id: ChainID.INVALID,
+ network: "assethub-polkadot",
+ name: "Polkadot AssetHub",
+ nativeCurrency: {
+ name: "DOT",
+ symbol: "DOT",
+ decimals: 10,
+ },
+ rpcUrls: {
+ default: {
+ http: [],
+ webSocket: [],
+ },
+ public: {
+ http: [],
+ webSocket: [],
+ },
+ },
+ blockExplorers: {
+ default: {
+ name: "Subscan",
+ url: "https://assethub-polkadot.subscan.io",
+ },
+ },
+
+ /**
+ * Custom
+ */
+ logo: "asset-hub.svg",
+ assets: [
+ {
+ icon: "usdt.svg",
+ id: 1984,
+ name: "Tether USD",
+ symbol: "USDT",
+ decimals: 6,
+ cross: [
+ {
+ isReserve: true,
+ target: { network: "darwinia", symbol: "ahUSDT" },
+ fee: { amount: bnToBn(20000), asset: { id: 1984, decimals: 6, symbol: "USDT", native: true } }, // 0.02 USDT
+ },
+ ],
+ },
+ {
+ icon: "pink.jpg",
+ id: 23,
+ name: "PINK",
+ symbol: "PINK",
+ decimals: 10,
+ cross: [
+ {
+ isReserve: true,
+ target: { network: "darwinia", symbol: "ahPINK" },
+ fee: { amount: bnToBn(20000), asset: { id: 1984, decimals: 6, symbol: "USDT", native: false } }, // 0.02 USDT
+ },
+ ],
+ },
+ ],
+ wallets: [WalletID.TALISMAN],
+ addressType: "substrate",
+
+ /**
+ * Substrate
+ */
+ endpoint: "wss://polkadot-asset-hub-rpc.polkadot.io",
+ parachainId: ParachainID.ASSETHUB_POLKADOT,
+};
diff --git a/src/config/chains/assethub-rococo-chain.ts b/src/config/chains/assethub-rococo-chain.ts
index 8eec5d3..2bdd097 100644
--- a/src/config/chains/assethub-rococo-chain.ts
+++ b/src/config/chains/assethub-rococo-chain.ts
@@ -1,4 +1,4 @@
-import { ChainConfig, ChainID, WalletID } from "@/types";
+import { ChainConfig, ChainID, ParachainID, WalletID } from "@/types";
import { bnToBn } from "@polkadot/util";
export const assethubRococoChain: ChainConfig = {
@@ -42,16 +42,21 @@ export const assethubRococoChain: ChainConfig = {
name: "Tether USD Test",
symbol: "USDT",
decimals: 6,
- cross: [{ target: { network: "pangolin", symbol: "ahUSDT" }, isReserve: true }],
+ cross: [
+ {
+ isReserve: true,
+ target: { network: "pangolin", symbol: "ahUSDT" },
+ fee: { amount: bnToBn(125000), asset: { id: 7777, decimals: 6, symbol: "USDT", native: true } }, // 0.125 USDT
+ },
+ ],
},
],
wallets: [WalletID.TALISMAN],
addressType: "substrate",
- minCross: bnToBn(125000), // 0.125 USDT
/**
* Substrate
*/
endpoint: "wss://rococo-asset-hub-rpc.polkadot.io",
- parachainId: 1000,
+ parachainId: ParachainID.ASSETHUB_ROCOCO,
};
diff --git a/src/config/chains/darwinia-chain.ts b/src/config/chains/darwinia-chain.ts
new file mode 100644
index 0000000..d140aee
--- /dev/null
+++ b/src/config/chains/darwinia-chain.ts
@@ -0,0 +1,75 @@
+import { ChainConfig, ChainID, ParachainID, WalletID } from "@/types";
+import { bnToBn } from "@polkadot/util";
+
+export const darwiniaChain: ChainConfig = {
+ /**
+ * Chain
+ */
+ id: ChainID.DARWINIA,
+ network: "darwinia",
+ name: "Darwinia",
+ nativeCurrency: {
+ name: "RING",
+ symbol: "RING",
+ decimals: 18,
+ },
+ rpcUrls: {
+ default: {
+ http: ["https://rpc.darwinia.network"],
+ webSocket: ["wss://rpc.darwinia.network"],
+ },
+ public: {
+ http: ["https://rpc.darwinia.network"],
+ webSocket: ["wss://rpc.darwinia.network"],
+ },
+ },
+ blockExplorers: {
+ default: {
+ name: "Subscan",
+ url: "https://darwinia.subscan.io",
+ },
+ },
+
+ /**
+ * Custom
+ */
+ logo: "darwinia.png",
+ assets: [
+ {
+ icon: "usdt.svg",
+ id: 1027,
+ name: "Tether USD",
+ symbol: "ahUSDT",
+ decimals: 6,
+ cross: [
+ {
+ isReserve: false,
+ target: { network: "assethub-polkadot", symbol: "USDT" },
+ fee: { amount: bnToBn(700000), asset: { id: 1984, decimals: 6, symbol: "ahUSDT", native: true } }, // 0.7 USDT
+ },
+ ],
+ },
+ {
+ icon: "pink.jpg",
+ id: 1028,
+ name: "PINK",
+ symbol: "ahPINK",
+ decimals: 10,
+ cross: [
+ {
+ isReserve: false,
+ target: { network: "assethub-polkadot", symbol: "PINK" },
+ fee: { amount: bnToBn(700000), asset: { id: 1984, decimals: 6, symbol: "ahUSDT", native: false } }, // 0.7 USDT
+ },
+ ],
+ },
+ ],
+ wallets: [WalletID.RAINBOW, WalletID.TALISMAN],
+ addressType: "evm",
+
+ /**
+ * Substrate
+ */
+ endpoint: "wss://rpc.darwinia.network",
+ parachainId: ParachainID.DARWINIA,
+};
diff --git a/src/config/chains/index.ts b/src/config/chains/index.ts
index e154efc..fa4b4d7 100644
--- a/src/config/chains/index.ts
+++ b/src/config/chains/index.ts
@@ -1,2 +1,4 @@
export * from "./pangolin-chain";
+export * from "./darwinia-chain";
export * from "./assethub-rococo-chain";
+export * from "./assethub-polkadot-chain";
diff --git a/src/config/chains/pangolin-chain.ts b/src/config/chains/pangolin-chain.ts
index 4c67104..e010311 100644
--- a/src/config/chains/pangolin-chain.ts
+++ b/src/config/chains/pangolin-chain.ts
@@ -1,4 +1,4 @@
-import { ChainConfig, ChainID, WalletID } from "@/types";
+import { ChainConfig, ChainID, ParachainID, WalletID } from "@/types";
import { bnToBn } from "@polkadot/util";
export const pangolinChain: ChainConfig = {
@@ -44,20 +44,20 @@ export const pangolinChain: ChainConfig = {
decimals: 6,
cross: [
{
- target: { network: "assethub-rococo", symbol: "USDT" },
isReserve: false,
+ target: { network: "assethub-rococo", symbol: "USDT" },
+ fee: { amount: bnToBn(3600000), asset: { id: 7777, decimals: 6, symbol: "ahUSDT", native: true } }, // 3.6 USDT
},
],
},
],
wallets: [WalletID.RAINBOW, WalletID.TALISMAN],
addressType: "evm",
- minCross: bnToBn(3600000), // 3.6 USDT
hasAssetLimit: true,
/**
* Substrate
*/
endpoint: "wss://pangolin-rpc.darwinia.network",
- parachainId: 2105,
+ parachainId: ParachainID.PANGOLIN,
};
diff --git a/src/hooks/use-balance.ts b/src/hooks/use-balance.ts
index 6e87a82..d980a3d 100644
--- a/src/hooks/use-balance.ts
+++ b/src/hooks/use-balance.ts
@@ -7,19 +7,21 @@ import { forkJoin, EMPTY } from "rxjs";
export function useBalance(
bridge: EvmBridge | undefined,
value: { address: string; valid: boolean } | undefined,
- position: "source" | "target",
+ type: "source" | "target" | "usdt",
) {
const [balance, setBalance] = useState<{ asset: { value: BN; asset: Asset } }>();
const updateBalance = useCallback(() => {
if (bridge && value?.address && value.valid) {
return forkJoin([
- position === "source"
+ type === "usdt"
+ ? bridge.getSourceUsdtBalance(value.address)
+ : type === "source"
? bridge.getSourceAssetBalance(value.address)
: bridge.getTargetAssetBalance(value.address),
]).subscribe({
next: ([asset]) => {
- setBalance({ asset });
+ setBalance(asset ? { asset } : undefined);
},
error: (err) => {
console.error(err);
@@ -31,7 +33,7 @@ export function useBalance(
}
return EMPTY.subscribe();
- }, [bridge, value, position]);
+ }, [bridge, value, type]);
useEffect(() => {
const sub$$ = updateBalance();
diff --git a/src/libs/bridge/base.ts b/src/libs/bridge/base.ts
index 9d0f78b..7aaf2d1 100644
--- a/src/libs/bridge/base.ts
+++ b/src/libs/bridge/base.ts
@@ -96,6 +96,14 @@ export abstract class BaseBridge {
return { value, asset };
}
+ async getSourceUsdtBalance(address: string) {
+ const asset = this.sourceChain.assets.find(({ symbol }) => symbol.toLowerCase().includes("usdt"));
+ if (asset) {
+ const value = await this.getAssetBalance(this.sourceApi, asset, address);
+ return { value, asset };
+ }
+ }
+
/**
* Supply
*/
diff --git a/src/libs/bridge/evm.ts b/src/libs/bridge/evm.ts
index 1b3a9d9..f7f869b 100644
--- a/src/libs/bridge/evm.ts
+++ b/src/libs/bridge/evm.ts
@@ -32,8 +32,8 @@ export class EvmBridge extends SubstrateBridge {
return;
}
- async transferAssetWithPrecompile(sender: string, recipient: string, amount: BN) {
- const extrinsic = await this.transferAsset(recipient, amount);
+ async transferAssetsWithPrecompile(sender: string, recipient: string, amount: BN) {
+ const extrinsic = await this.transferAssets(recipient, amount);
const account = sender as Address;
// const estimateGas = await this.publicClient.estimateGas({
diff --git a/src/libs/bridge/substrate.ts b/src/libs/bridge/substrate.ts
index d0b93d7..90535ee 100644
--- a/src/libs/bridge/substrate.ts
+++ b/src/libs/bridge/substrate.ts
@@ -1,5 +1,5 @@
import { BaseBridge } from "./base";
-import { BN, BN_ZERO, bnToBn, u8aToHex } from "@polkadot/util";
+import { BN, bnToBn, u8aToHex } from "@polkadot/util";
import { Asset, ChainConfig } from "@/types";
import { ApiPromise } from "@polkadot/api";
import { decodeAddress } from "@polkadot/util-crypto";
@@ -25,20 +25,19 @@ export class SubstrateBridge extends BaseBridge {
}
/**
- * From Pangolin to Asset-Hub
+ * To Asset-Hub
* @param recipient Address
* @param amount Transfer amount
* @returns Promise
>
*/
- async transferAsset(recipient: string, amount: BN) {
+ async transferAssets(recipient: string, amount: BN) {
const section = "xTokens";
- const method = "transferMultiasset";
+ const method = "transferMultiassets";
const fn = this.sourceApi.tx[section][method];
const Parachain = bnToBn(this.targetChain.parachainId);
-
- const _asset = {
- V3: {
+ const assetItems = [
+ {
id: {
Concrete: {
parents: 1,
@@ -49,7 +48,23 @@ export class SubstrateBridge extends BaseBridge {
},
fun: { Fungible: amount },
},
- };
+ ];
+ if (this.cross && !this.cross.fee.asset.native) {
+ assetItems.push({
+ id: {
+ Concrete: {
+ parents: 1,
+ interior: {
+ X3: [{ Parachain }, { PalletInstance: 50 }, { GeneralIndex: bnToBn(this.cross.fee.asset.id) }],
+ },
+ },
+ },
+ fun: { Fungible: this.cross.fee.amount },
+ });
+ }
+
+ const _assets = { V3: assetItems };
+ const _feeAssetItem = bnToBn(assetItems.length - 1);
const _dest = {
V3: {
parents: 1,
@@ -60,39 +75,49 @@ export class SubstrateBridge extends BaseBridge {
};
const _destWeightLimit = { Unlimited: null };
- const extrinsic = fn(_asset, _dest, _destWeightLimit);
+ const extrinsic = fn(_assets, _feeAssetItem, _dest, _destWeightLimit);
return extrinsic;
}
/**
- * From Asset-Hub to Pangolin
+ * From Asset-Hub
* @param recipient Address
* @param amount Transfer amount
* @returns Promise>
*/
- async limitedReserveTransferAsset(recipient: string, amount: BN) {
+ async limitedReserveTransferAssets(recipient: string, amount: BN) {
const section = "polkadotXcm";
const method = "limitedReserveTransferAssets";
const fn = this.sourceApi.tx[section][method];
const Parachain = bnToBn(this.targetChain.parachainId);
+ const assetItems = [
+ {
+ id: {
+ Concrete: {
+ parents: 0,
+ interior: { X2: [{ PalletInstance: 50 }, { GeneralIndex: bnToBn(this.sourceAsset.id) }] },
+ },
+ },
+ fun: { Fungible: amount },
+ },
+ ];
+ if (this.cross && !this.cross.fee.asset.native) {
+ assetItems.push({
+ id: {
+ Concrete: {
+ parents: 0,
+ interior: { X2: [{ PalletInstance: 50 }, { GeneralIndex: bnToBn(this.cross.fee.asset.id) }] },
+ },
+ },
+ fun: { Fungible: this.cross.fee.amount },
+ });
+ }
const _dest = { V3: { parents: 1, interior: { X1: { Parachain } } } };
const _beneficiary = { V3: { parents: 0, interior: { X1: { AccountKey20: { network: null, key: recipient } } } } };
- const _assets = {
- V3: [
- {
- id: {
- Concrete: {
- parents: 0,
- interior: { X2: [{ PalletInstance: 50 }, { GeneralIndex: bnToBn(this.sourceAsset.id) }] },
- },
- },
- fun: { Fungible: amount },
- },
- ],
- };
- const _feeAssetItem = BN_ZERO;
+ const _assets = { V3: assetItems };
+ const _feeAssetItem = bnToBn(assetItems.length - 1);
const _weightLimit = { Unlimited: null };
const extrinsic = fn(_dest, _beneficiary, _assets, _feeAssetItem, _weightLimit);
diff --git a/src/providers/rainbow-provider.tsx b/src/providers/rainbow-provider.tsx
index baa4beb..641d0c1 100644
--- a/src/providers/rainbow-provider.tsx
+++ b/src/providers/rainbow-provider.tsx
@@ -6,13 +6,13 @@ import { darkTheme, getDefaultWallets, RainbowKitProvider } from "@rainbow-me/ra
import { configureChains, createConfig, WagmiConfig } from "wagmi";
import { publicProvider } from "wagmi/providers/public";
import { PropsWithChildren } from "react";
-import { pangolinChain } from "@/config/chains";
+import { darwiniaChain } from "@/config/chains";
import { APP_NAME } from "@/config";
const projectId = process.env.NEXT_PUBLIC_WALLET_CONNECT_ID || "";
const appName = APP_NAME;
-const { chains, publicClient } = configureChains([pangolinChain], [publicProvider()]);
+const { chains, publicClient } = configureChains([darwiniaChain], [publicProvider()]);
const { connectors } = getDefaultWallets({ appName, projectId, chains });
diff --git a/src/providers/transfer-provider.tsx b/src/providers/transfer-provider.tsx
index 72fc060..0089fba 100644
--- a/src/providers/transfer-provider.tsx
+++ b/src/providers/transfer-provider.tsx
@@ -16,6 +16,7 @@ interface TransferCtx {
assetLimit: BN | undefined;
targetAssetDetails: PalletAssetsAssetDetails | undefined;
bridgeInstance: EvmBridge | undefined;
+ usdtBalance: { asset: { value: BN; asset: Asset } } | undefined;
sourceBalance: { asset: { value: BN; asset: Asset } } | undefined;
targetBalance: { asset: { value: BN; asset: Asset } } | undefined;
transferAmount: { valid: boolean; input: string; amount: BN };
@@ -66,6 +67,7 @@ const defaultValue: TransferCtx = {
assetLimit: undefined,
targetAssetDetails: undefined,
bridgeInstance: undefined,
+ usdtBalance: undefined,
sourceBalance: undefined,
targetBalance: undefined,
transferAmount: { valid: true, input: "", amount: BN_ZERO },
@@ -142,6 +144,7 @@ export default function TransferProvider({ children }: PropsWithChildren {
try {
- const receipt = await _bridge.transferAssetWithPrecompile(_sender, _recipient, _amount);
+ const receipt = await _bridge.transferAssetsWithPrecompile(_sender, _recipient, _amount);
notifyTransaction(receipt, _bridge.getSourceChain());
if (receipt?.status === "success") {
options.successCb();
@@ -176,8 +179,8 @@ export default function TransferProvider({ children }: PropsWithChildren