diff --git a/app/history/page.tsx b/app/history/page.tsx
index 239a4315..04c47f41 100644
--- a/app/history/page.tsx
+++ b/app/history/page.tsx
@@ -62,6 +62,44 @@ import { AppRouterInstance } from "next/dist/shared/lib/app-router-context.share
const ITEMS_PER_PAGE = 5;
+/**
+ * Check if two transfers are the same by comparing their unique identifiers.
+ * For V2 transfers, the backend returns database-generated IDs, while locally
+ * created transfers use messageId/txHash as ID. This function matches by
+ * transaction hash or extrinsic hash to handle this case.
+ */
+const isSameTransfer = (t1: Transfer, t2: Transfer): boolean => {
+ // Match by ID if both have non-empty IDs
+ if (t1.id === t2.id && t1.id.length > 0) return true;
+
+ // For ToPolkadotTransferResult (E->P), match by transaction hash
+ if (t1.sourceType === "ethereum" && t2.sourceType === "ethereum") {
+ const t1Casted = t1 as historyV2.ToPolkadotTransferResult;
+ const t2Casted = t2 as historyV2.ToPolkadotTransferResult;
+ if (
+ t1Casted.submitted.transactionHash ===
+ t2Casted.submitted.transactionHash &&
+ t1Casted.submitted.transactionHash.length > 0
+ ) {
+ return true;
+ }
+ }
+
+ // For ToEthereumTransferResult (P->E), match by extrinsic hash
+ if (t1.sourceType === "substrate" && t2.sourceType === "substrate") {
+ const t1Casted = t1 as historyV2.ToEthereumTransferResult;
+ const t2Casted = t2 as historyV2.ToEthereumTransferResult;
+ if (
+ t1Casted.submitted.extrinsic_hash === t2Casted.submitted.extrinsic_hash &&
+ t1Casted.submitted.extrinsic_hash.length > 0
+ ) {
+ return true;
+ }
+ }
+
+ return false;
+};
+
const getExplorerLinks = (
transfer: Transfer,
source: assetsV2.TransferLocation,
@@ -451,9 +489,8 @@ export default function History() {
const oldTransferCutoff = new Date().getTime() - 4 * 60 * 60 * 1000; // 4 hours
for (let i = 0; i < transfersPendingLocal.length; ++i) {
if (
- transferHistoryCache.find(
- (h) =>
- h.id?.toLowerCase() === transfersPendingLocal[i].id?.toLowerCase(),
+ transferHistoryCache.find((h) =>
+ isSameTransfer(h, transfersPendingLocal[i]),
) ||
new Date(transfersPendingLocal[i].info.when).getTime() <
oldTransferCutoff
@@ -479,7 +516,13 @@ export default function History() {
]);
const allTransfers: Transfer[] = [];
for (const pending of transfersPendingLocal) {
- if (transferHistoryCache.find((t) => t.id === pending.id)) {
+ // Check if this pending transfer already exists in the cache
+ // Match by ID, transaction hash, or message ID to handle V2 transfers
+ const isDuplicate = transferHistoryCache.find((t) =>
+ isSameTransfer(t, pending),
+ );
+
+ if (isDuplicate) {
continue;
}
allTransfers.push(pending);
diff --git a/app/txcomplete/page.tsx b/app/txcomplete/page.tsx
index 476124ca..2459c7d1 100644
--- a/app/txcomplete/page.tsx
+++ b/app/txcomplete/page.tsx
@@ -17,7 +17,7 @@ import { Transfer } from "@/store/transferHistory";
import base64url from "base64url";
import { LucideLoaderCircle } from "lucide-react";
import { useSearchParams } from "next/navigation";
-import { Suspense, useContext, useMemo } from "react";
+import { Suspense, useContext, useMemo, useState } from "react";
import { TransferStatusBadge } from "@/components/history/TransferStatusBadge";
import { Button } from "@/components/ui/button";
import Link from "next/link";
@@ -38,6 +38,7 @@ import { useAtom, useAtomValue } from "jotai";
import { walletTxChecker } from "@/utils/addresses";
import { NeuroWebUnwrapForm } from "@/components/transfer/NeuroWebUnwrapStep";
import { ethereumAccountAtom, ethereumAccountsAtom } from "@/store/ethereum";
+import { AddTipDialog } from "@/components/AddTipDialog";
const Loading = () => {
return (
@@ -58,6 +59,11 @@ function TxCard(props: TxCardProps) {
const { transfer, refresh, inHistory, registry } = props;
const { destination } = getEnvDetail(transfer, registry);
const links: { name: string; link: string }[] = [];
+ const [tipDialogOpen, setTipDialogOpen] = useState(false);
+ const [tipParams, setTipParams] = useState<{
+ direction: "Inbound" | "Outbound";
+ nonce: number;
+ } | null>(null);
const token =
registry.ethereumChains[registry.ethChainId].assets[
@@ -199,6 +205,51 @@ function TxCard(props: TxCardProps) {
{neuroWeb}
+
+
+ {tipParams && (
+
+ )}
+
void;
+ direction: "Inbound" | "Outbound";
+ nonce: number;
+};
+
+export const AddTipDialog: FC
= ({
+ open,
+ onOpenChange,
+ direction,
+ nonce,
+}) => {
+ const [tipAsset, setTipAsset] = useState<"DOT" | "ETH">("DOT");
+ const [tipAmount, setTipAmount] = useState("0.00");
+ const [busy, setBusy] = useState(false);
+ const [error, setError] = useState(null);
+ const [success, setSuccess] = useState<{
+ blockHash: string;
+ txHash: string;
+ } | null>(null);
+ const [estimatedFee, setEstimatedFee] = useState(null);
+
+ const [polkadotAccount, setPolkadotAccount] = useAtom(polkadotAccountAtom);
+ const polkadotAccounts = useAtomValue(polkadotAccountsAtom);
+ const setPolkadotWalletModalOpen = useSetAtom(polkadotWalletModalOpenAtom);
+ const registry = useContext(RegistryContext)!;
+ const environment = getEnvironment();
+ const [selectedAccountAddress, setSelectedAccountAddress] = useState<
+ string | null
+ >(null);
+
+ const hasWallet = polkadotAccounts && polkadotAccounts.length > 0;
+ const needsAccountSelection = hasWallet && !polkadotAccount;
+
+ const accountToUse =
+ selectedAccountAddress && polkadotAccounts
+ ? polkadotAccounts.find((acc) => acc.address === selectedAccountAddress)
+ : polkadotAccount;
+
+ useEffect(() => {
+ if (open) {
+ setError(null);
+ setSuccess(null);
+ setEstimatedFee(null);
+ if (polkadotAccount) {
+ setSelectedAccountAddress(polkadotAccount.address);
+ } else if (polkadotAccounts && polkadotAccounts.length > 0) {
+ setSelectedAccountAddress(polkadotAccounts[0].address);
+ }
+ }
+ }, [open, polkadotAccount, polkadotAccounts]);
+
+ useEffect(() => {
+ if (!open || !accountToUse) return;
+
+ const estimateFee = async () => {
+ try {
+ const wsProvider = new WsProvider(
+ environment.config.PARACHAINS[
+ environment.config.ASSET_HUB_PARAID.toString()
+ ],
+ );
+ const api = await ApiPromise.create({ provider: wsProvider });
+
+ const tipAmountBigInt = BigInt(
+ Math.floor(parseFloat(tipAmount || "0") * 1e10),
+ );
+
+ const fee = await addTip.getFee(
+ api,
+ registry,
+ {
+ direction,
+ nonce: BigInt(nonce),
+ tipAsset,
+ tipAmount: tipAmountBigInt,
+ },
+ accountToUse.address,
+ );
+
+ setEstimatedFee((Number(fee) / 1e10).toFixed(4));
+ await api.disconnect();
+ } catch (err) {
+ console.error("Fee estimation failed:", err);
+ setEstimatedFee(null);
+ }
+ };
+
+ estimateFee();
+ }, [open]);
+
+ const handleSubmit = async () => {
+ if (!hasWallet) {
+ setPolkadotWalletModalOpen(true);
+ return;
+ }
+
+ if (!accountToUse) {
+ setError("Please select a Polkadot account");
+ return;
+ }
+
+ setBusy(true);
+ setError(null);
+ setSuccess(null);
+
+ try {
+ const wsProvider = new WsProvider(
+ environment.config.PARACHAINS[
+ environment.config.ASSET_HUB_PARAID.toString()
+ ],
+ );
+ const api = await ApiPromise.create({ provider: wsProvider });
+
+ const tipAmountBigInt = BigInt(Math.floor(parseFloat(tipAmount) * 1e10));
+
+ const tipResult = await addTip.createAddTip(api, registry, {
+ direction,
+ nonce: BigInt(nonce),
+ tipAsset,
+ tipAmount: tipAmountBigInt,
+ });
+
+ if (!accountToUse.signer) {
+ throw new Error("No signer available from wallet");
+ }
+
+ const response = await addTip.signAndSend(
+ api,
+ tipResult,
+ accountToUse.address,
+ { signer: accountToUse.signer },
+ );
+
+ setSuccess(response);
+ await api.disconnect();
+ } catch (err: any) {
+ console.error("Add tip failed:", err);
+ setError(err?.message || "Failed to add tip");
+ } finally {
+ setBusy(false);
+ }
+ };
+
+ const handleClose = () => {
+ if (!busy) {
+ onOpenChange(false);
+ }
+ };
+
+ return (
+
+ );
+};
diff --git a/components/SelectedPolkadotAccount.tsx b/components/SelectedPolkadotAccount.tsx
index 12db098b..fe859527 100644
--- a/components/SelectedPolkadotAccount.tsx
+++ b/components/SelectedPolkadotAccount.tsx
@@ -19,6 +19,7 @@ type SelectedPolkadotAccountProps = {
polkadotAccount?: string;
onValueChange?: (address: string) => void;
placeholder?: string;
+ disabled?: boolean;
};
export const SelectedPolkadotAccount: FC = ({
@@ -28,13 +29,15 @@ export const SelectedPolkadotAccount: FC = ({
polkadotAccounts,
polkadotAccount,
placeholder,
+ disabled,
}) => {
return (
+ {form.formState.errors.amount && (
+
+ {form.formState.errors.amount.message}
+
+ )}
+ {form.formState.errors.token && (
+
+ {form.formState.errors.token.message}
+
+ )}
Delivery Fee:{" "}
diff --git a/hooks/useBridgeFeeInfo.ts b/hooks/useBridgeFeeInfo.ts
index 6c26f6b3..6f4d25e7 100644
--- a/hooks/useBridgeFeeInfo.ts
+++ b/hooks/useBridgeFeeInfo.ts
@@ -5,13 +5,20 @@ import {
forInterParachain,
toEthereumV2,
toPolkadotV2,
+ toEthereumSnowbridgeV2,
+ toPolkadotSnowbridgeV2,
} from "@snowbridge/api";
import { useAtomValue } from "jotai";
import useSWR from "swr";
import { FeeInfo } from "@/utils/types";
import { useContext } from "react";
import { RegistryContext } from "@/app/providers";
-import { AssetRegistry, Parachain } from "@snowbridge/base-types";
+import {
+ AssetRegistry,
+ Parachain,
+ supportsEthereumToPolkadotV2,
+ supportsPolkadotToEthereumV2,
+} from "@snowbridge/base-types";
import { inferTransferType } from "@/utils/inferTransferType";
import { getEnvironmentName } from "@/lib/snowbridge";
@@ -19,7 +26,7 @@ async function estimateExecutionFee(
context: Context,
registry: AssetRegistry,
para: Parachain,
- deliveryFee: toPolkadotV2.DeliveryFee,
+ deliveryFee: toPolkadotV2.DeliveryFee | toPolkadotSnowbridgeV2.DeliveryFee,
) {
const feeEstimateAccounts: { [env: string]: { src: string; dst: string } } = {
polkadot_mainnet: {
@@ -39,15 +46,39 @@ async function estimateExecutionFee(
if (env in feeEstimateAccounts) {
try {
const { src: sourceAccount, dst: destAccount } = feeEstimateAccounts[env];
- const testTransfer = await toPolkadotV2.createTransfer(
- registry,
- sourceAccount,
- destAccount,
- assetsV2.ETHER_TOKEN_ADDRESS,
- para.parachainId,
- 1n,
- deliveryFee,
- );
+
+ const useV2 = supportsEthereumToPolkadotV2(para);
+
+ let testTransfer;
+ if (useV2) {
+ const transferImpl =
+ toPolkadotSnowbridgeV2.createTransferImplementation(
+ para.parachainId,
+ registry,
+ assetsV2.ETHER_TOKEN_ADDRESS,
+ );
+ testTransfer = await transferImpl.createTransfer(
+ context,
+ registry,
+ para.parachainId,
+ sourceAccount,
+ destAccount,
+ assetsV2.ETHER_TOKEN_ADDRESS,
+ 1n,
+ deliveryFee as toPolkadotSnowbridgeV2.DeliveryFee,
+ );
+ } else {
+ testTransfer = await toPolkadotV2.createTransfer(
+ registry,
+ sourceAccount,
+ destAccount,
+ assetsV2.ETHER_TOKEN_ADDRESS,
+ para.parachainId,
+ 1n,
+ deliveryFee as toPolkadotV2.DeliveryFee,
+ );
+ }
+
const [estimatedGas, feeData] = await Promise.all([
context.ethereum().estimateGas(testTransfer.tx),
context.ethereum().getFeeData(),
@@ -81,16 +112,35 @@ async function fetchBridgeFeeInfo([
switch (inferTransferType(source, destination)) {
case "toPolkadotV2": {
const para = registry.parachains[destination.key];
- const fee = await toPolkadotV2.getDeliveryFee(
- {
- gateway: context.gateway(),
- assetHub: await context.assetHub(),
- destination: await context.parachain(para.parachainId),
- },
- registry,
- token,
- para.parachainId,
- );
+
+ const useV2 = supportsEthereumToPolkadotV2(para);
+
+ let fee;
+ if (useV2) {
+ const transferImpl =
+ toPolkadotSnowbridgeV2.createTransferImplementation(
+ para.parachainId,
+ registry,
+ token,
+ );
+ fee = await transferImpl.getDeliveryFee(
+ context,
+ registry,
+ token,
+ para.parachainId,
+ );
+ } else {
+ fee = await toPolkadotV2.getDeliveryFee(
+ {
+ gateway: context.gateway(),
+ assetHub: await context.assetHub(),
+ destination: await context.parachain(para.parachainId),
+ },
+ registry,
+ token,
+ para.parachainId,
+ );
+ }
return {
fee: fee.totalFeeInWei,
@@ -104,15 +154,33 @@ async function fetchBridgeFeeInfo([
};
}
case "toEthereumV2": {
- const fee = await toEthereumV2.getDeliveryFee(
- {
- assetHub: await context.assetHub(),
- source: await context.parachain(source.parachain!.parachainId),
- },
- source.parachain!.parachainId,
- registry,
- token,
- );
+ const useV2 = supportsPolkadotToEthereumV2(source.parachain!);
+
+ let fee;
+ if (useV2) {
+ const transferImpl =
+ toEthereumSnowbridgeV2.createTransferImplementation(
+ source.parachain!.parachainId,
+ registry,
+ token,
+ );
+ fee = await transferImpl.getDeliveryFee(
+ { sourceParaId: source.parachain!.parachainId, context },
+ registry,
+ token,
+ );
+ } else {
+ fee = await toEthereumV2.getDeliveryFee(
+ {
+ assetHub: await context.assetHub(),
+ source: await context.parachain(source.parachain!.parachainId),
+ },
+ source.parachain!.parachainId,
+ registry,
+ token,
+ );
+ }
+
let feeValue = fee.totalFeeInDot;
let decimals = registry.relaychain.tokenDecimals ?? 0;
let symbol = registry.relaychain.tokenSymbols ?? "";
diff --git a/hooks/useSendToken.ts b/hooks/useSendToken.ts
index b4b5915e..44b540f5 100644
--- a/hooks/useSendToken.ts
+++ b/hooks/useSendToken.ts
@@ -2,7 +2,7 @@ import { ethereumAccountAtom, ethersProviderAtom } from "@/store/ethereum";
import { polkadotAccountsAtom } from "@/store/polkadot";
import { snowbridgeContextAtom } from "@/store/snowbridge";
import {
- MessageReciept,
+ MessageReceipt,
SignerInfo,
ValidationData,
ValidationResult,
@@ -14,7 +14,13 @@ import {
toEthereumFromEVMV2,
toPolkadotV2,
forInterParachain,
+ toEthereumSnowbridgeV2,
+ toPolkadotSnowbridgeV2,
} from "@snowbridge/api";
+import {
+ supportsEthereumToPolkadotV2,
+ supportsPolkadotToEthereumV2,
+} from "@snowbridge/base-types";
import { useAtomValue } from "jotai";
import { useCallback } from "react";
@@ -163,6 +169,7 @@ async function planSend(
): Promise<
| toEthereumV2.ValidationResult
| toPolkadotV2.ValidationResult
+ | toPolkadotSnowbridgeV2.ValidationResult
| toEthereumFromEVMV2.ValidationResultEvm
| forInterParachain.ValidationResult
> {
@@ -190,39 +197,88 @@ async function planSend(
return plan;
} else if (source.type === "substrate" && destination.type === "ethereum") {
const parachain = validateSubstrateDestination(data);
- const tx = await toEthereumV2.createTransfer(
- { sourceParaId: parachain.parachainId, context },
- assetRegistry,
- formData.sourceAccount,
- formData.beneficiary,
- formData.token,
- amountInSmallestUnit,
- fee.delivery as toEthereumV2.DeliveryFee,
- );
- const plan = await toEthereumV2.validateTransfer(context, tx);
+
+ const useV2 = supportsPolkadotToEthereumV2(parachain);
+ let plan;
+ if (useV2) {
+ console.log(
+ `[planSend] Snowbridge V2: Source parachain ${parachain.parachainId} to Ethereum.`,
+ );
+ const transferImpl = toEthereumSnowbridgeV2.createTransferImplementation(
+ parachain.parachainId,
+ assetRegistry,
+ formData.token,
+ );
+ const tx = await transferImpl.createTransfer(
+ { sourceParaId: parachain.parachainId, context },
+ assetRegistry,
+ formData.sourceAccount,
+ formData.beneficiary,
+ formData.token,
+ amountInSmallestUnit,
+ fee.delivery as toEthereumV2.DeliveryFee,
+ );
+ plan = await transferImpl.validateTransfer(context, tx);
+ } else {
+ const tx = await toEthereumV2.createTransfer(
+ { sourceParaId: parachain.parachainId, context },
+ assetRegistry,
+ formData.sourceAccount,
+ formData.beneficiary,
+ formData.token,
+ amountInSmallestUnit,
+ fee.delivery as toEthereumV2.DeliveryFee,
+ );
+ plan = await toEthereumV2.validateTransfer(context, tx);
+ }
console.log(plan);
return plan;
} else if (source.type === "ethereum" && destination.type === "substrate") {
const paraInfo = validateEthereumDestination(data);
- const tx = await toPolkadotV2.createTransfer(
- assetRegistry,
- formData.sourceAccount,
- formData.beneficiary,
- formData.token,
- paraInfo.parachainId,
- amountInSmallestUnit,
- fee.delivery as toPolkadotV2.DeliveryFee,
- );
- const plan = await toPolkadotV2.validateTransfer(
- {
- assetHub: await context.assetHub(),
- bridgeHub: await context.bridgeHub(),
- ethereum: context.ethereum(),
- gateway: context.gateway(),
- destParachain: await context.parachain(paraInfo.parachainId),
- },
- tx,
- );
+
+ const useV2 = supportsEthereumToPolkadotV2(paraInfo);
+ let plan;
+ if (useV2) {
+ console.log(
+ `[planSend] Snowbridge V2: Ethereum to Destination parachain ${paraInfo.parachainId}.`,
+ );
+ const transferImpl = toPolkadotSnowbridgeV2.createTransferImplementation(
+ paraInfo.parachainId,
+ assetRegistry,
+ formData.token,
+ );
+ const tx = await transferImpl.createTransfer(
+ context,
+ assetRegistry,
+ paraInfo.parachainId,
+ formData.sourceAccount,
+ formData.beneficiary,
+ formData.token,
+ amountInSmallestUnit,
+ fee.delivery as toPolkadotSnowbridgeV2.DeliveryFee,
+ );
+ plan = await transferImpl.validateTransfer(context, tx);
+ } else {
+ const tx = await toPolkadotV2.createTransfer(
+ assetRegistry,
+ formData.sourceAccount,
+ formData.beneficiary,
+ formData.token,
+ paraInfo.parachainId,
+ amountInSmallestUnit,
+ fee.delivery as toPolkadotV2.DeliveryFee,
+ );
+ plan = await toPolkadotV2.validateTransfer(
+ {
+ assetHub: await context.assetHub(),
+ bridgeHub: await context.bridgeHub(),
+ ethereum: context.ethereum(),
+ gateway: context.gateway(),
+ destParachain: await context.parachain(paraInfo.parachainId),
+ },
+ tx,
+ );
+ }
console.log(plan);
return plan;
} else if (source.type === "substrate" && destination.type === "substrate") {
@@ -257,7 +313,7 @@ async function sendToken(
data: ValidationData,
plan: ValidationResult,
signerInfo: SignerInfo,
-): Promise {
+): Promise {
if (!plan.success) {
throw Error(`Cannot execute a failed plan.`, {
cause: plan,
@@ -302,29 +358,68 @@ async function sendToken(
signerInfo,
false,
);
- const tx = plan.transfer as toEthereumV2.Transfer;
- const result = await toEthereumV2.signAndSend(
- context,
- tx,
- polkadotAccount.address,
- {
- signer: polkadotAccount.signer! as Signer,
- withSignedTransaction: true,
- },
- );
+
+ const useV2 = supportsPolkadotToEthereumV2(paraInfo);
+ let result;
+ if (useV2) {
+ console.log(
+ `[sendToken] Snowbridge V2: Source parachain ${paraInfo.parachainId} to Ethereum.`,
+ );
+ const tx = plan.transfer as toEthereumV2.Transfer;
+ result = await toEthereumSnowbridgeV2.signAndSend(
+ context,
+ tx,
+ polkadotAccount.address,
+ {
+ signer: polkadotAccount.signer! as Signer,
+ withSignedTransaction: true,
+ },
+ );
+ } else {
+ const tx = plan.transfer as toEthereumV2.Transfer;
+ result = await toEthereumV2.signAndSend(
+ context,
+ tx,
+ polkadotAccount.address,
+ {
+ signer: polkadotAccount.signer! as Signer,
+ withSignedTransaction: true,
+ },
+ );
+ }
console.log(result);
return result;
} else if (source.type === "ethereum" && destination.type === "substrate") {
- const { signer } = await validateEthereumSigner(data, signerInfo);
- const transfer = plan.transfer as toPolkadotV2.Transfer;
- const response = await signer.sendTransaction(transfer.tx);
- const receipt = await response.wait();
- if (!receipt) {
- throw Error(`Could not fetch transaction receipt.`);
- }
- const result = await toPolkadotV2.getMessageReceipt(receipt);
- if (!result) {
- throw Error(`Could not fetch message receipt.`);
+ const { signer, paraInfo } = await validateEthereumSigner(data, signerInfo);
+
+ const useV2 = supportsEthereumToPolkadotV2(paraInfo);
+ let result;
+ if (useV2) {
+ console.log(
+ `[sendToken] Snowbridge V2: Destination parachain ${paraInfo.parachainId}`,
+ );
+ const transfer = plan.transfer as toPolkadotSnowbridgeV2.Transfer;
+ const response = await signer.sendTransaction(transfer.tx);
+ const receipt = await response.wait();
+ if (!receipt) {
+ throw Error(`Could not fetch transaction receipt.`);
+ }
+ result = await toPolkadotSnowbridgeV2.getMessageReceipt(receipt);
+ if (!result) {
+ throw Error(`Could not fetch message receipt.`);
+ }
+ result = { ...result, messageId: "", channelId: "" };
+ } else {
+ const transfer = plan.transfer as toPolkadotV2.Transfer;
+ const response = await signer.sendTransaction(transfer.tx);
+ const receipt = await response.wait();
+ if (!receipt) {
+ throw Error(`Could not fetch transaction receipt.`);
+ }
+ result = await toPolkadotV2.getMessageReceipt(receipt);
+ if (!result) {
+ throw Error(`Could not fetch message receipt.`);
+ }
}
console.log(result);
return result;
@@ -335,7 +430,7 @@ async function sendToken(
export function useSendToken(): [
(data: ValidationData) => Promise,
- (data: ValidationData, plan: ValidationResult) => Promise,
+ (data: ValidationData, plan: ValidationResult) => Promise,
] {
const context = useAtomValue(snowbridgeContextAtom);
const plan = useCallback(
diff --git a/hooks/useSendTokenKusama.ts b/hooks/useSendTokenKusama.ts
index 6c2b16b3..ac8eb0f7 100644
--- a/hooks/useSendTokenKusama.ts
+++ b/hooks/useSendTokenKusama.ts
@@ -4,7 +4,7 @@ import { snowbridgeContextAtom } from "@/store/snowbridge";
import {
AssetHub,
KusamaValidationData,
- MessageReciept,
+ MessageReceipt,
SignerInfo,
} from "@/utils/types";
import { Signer } from "@polkadot/api/types";
diff --git a/package.json b/package.json
index 322fd4de..b8bd8bd6 100644
--- a/package.json
+++ b/package.json
@@ -29,10 +29,10 @@
"@radix-ui/react-toggle": "1.1.0",
"@reown/appkit": "1.7.7",
"@reown/appkit-adapter-ethers": "1.7.7",
- "@snowbridge/api": "0.2.19",
- "@snowbridge/base-types": "0.2.19",
- "@snowbridge/contract-types": "0.2.19",
- "@snowbridge/registry": "0.2.19",
+ "@snowbridge/api": "0.2.20",
+ "@snowbridge/base-types": "0.2.20",
+ "@snowbridge/contract-types": "0.2.20",
+ "@snowbridge/registry": "0.2.20",
"@talismn/connect-components": "1.1.9",
"@talismn/connect-wallets": "1.2.8",
"@types/big.js": "6.2.2",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 17791ae6..5f530996 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -63,17 +63,17 @@ importers:
specifier: 1.7.7
version: 1.7.7(@ethersproject/sha2@5.7.0)(@types/react@18.3.12)(bufferutil@4.0.8)(ethers@6.15.0(bufferutil@4.0.8)(utf-8-validate@5.0.10))(react@18.3.1)(typescript@5.6.3)(utf-8-validate@5.0.10)(zod@3.23.8)
'@snowbridge/api':
- specifier: 0.2.19
- version: 0.2.19(bufferutil@4.0.8)(utf-8-validate@5.0.10)
+ specifier: 0.2.20
+ version: 0.2.20(bufferutil@4.0.8)(utf-8-validate@5.0.10)
'@snowbridge/base-types':
- specifier: 0.2.19
- version: 0.2.19
+ specifier: 0.2.20
+ version: 0.2.20
'@snowbridge/contract-types':
- specifier: 0.2.19
- version: 0.2.19(bufferutil@4.0.8)(utf-8-validate@5.0.10)
+ specifier: 0.2.20
+ version: 0.2.20(bufferutil@4.0.8)(utf-8-validate@5.0.10)
'@snowbridge/registry':
- specifier: 0.2.19
- version: 0.2.19
+ specifier: 0.2.20
+ version: 0.2.20
'@talismn/connect-components':
specifier: 1.1.9
version: 1.1.9(@polkadot/api@16.4.8(bufferutil@4.0.8)(utf-8-validate@5.0.10))(@polkadot/extension-inject@0.62.1(@polkadot/api@16.4.8(bufferutil@4.0.8)(utf-8-validate@5.0.10))(@polkadot/util@13.5.6)(bufferutil@4.0.8)(utf-8-validate@5.0.10))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
@@ -1336,20 +1336,20 @@ packages:
'@sinonjs/fake-timers@10.3.0':
resolution: {integrity: sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==}
- '@snowbridge/api@0.2.19':
- resolution: {integrity: sha512-w8rV33mgcIoVUqivQceOvG5/zKYOpkky0ByARRTByoHCKz0sQs8N624COiS2Rwx3D0IT9JZmBokus9JY5s0eTw==}
+ '@snowbridge/api@0.2.20':
+ resolution: {integrity: sha512-47QPFgqfcn7/19kVFP0Bnsq0JtrkySgQ4mpRT7yuscnr4Hgbxwb8a42JQGyeHRaJ+9WY5zdXTU+j0EnqzxOs9A==}
- '@snowbridge/base-types@0.2.19':
- resolution: {integrity: sha512-V6mQOtUeHyyFacFRlcJbGxIvDmXfgpsxCpNkfBhAQgGb3norzmOPk1mEjBwUu+5pmkTe3vpeCfaLZrK5SJuXbQ==}
+ '@snowbridge/base-types@0.2.20':
+ resolution: {integrity: sha512-E8bVyPgOm/wmAIgw/McTrmHPyJcRKBPIrdAKJwPE3odHCv8HVxaPgSdZi5hZF6D+ONr6VJMBgsrW4SzKSEMryQ==}
- '@snowbridge/contract-types@0.2.19':
- resolution: {integrity: sha512-J5/N4yDKleXnhvtvePUf9OIc/ivHYcdni5vLbtk2uD1kHAyya4NlZglJo5MQX8+bYPRCjSlyXatqsrzwAXO+/g==}
+ '@snowbridge/contract-types@0.2.20':
+ resolution: {integrity: sha512-gcP5ps51XgVGxtARz+bMaSE9vJfiK3E/fQCX/XKk0o5Xy72xamM7LA3FA96hnsm2njTyRBV8grKDXDPKomPGhg==}
- '@snowbridge/contracts@0.2.19':
- resolution: {integrity: sha512-cObfvVnEWJH9AICEFCT135GSmTDoTf1ndcIfV4vMjLEF4lTvnQRFHL0IjtMZrsLT8twbinznUwttaQgY9UVSzA==}
+ '@snowbridge/contracts@0.2.20':
+ resolution: {integrity: sha512-oWW/5BDAGxvsGXOK84vCQ6cvf3Mtu0fGWfWgRtbEBq7iKfPqe0jPgdAAjhO4ZJnKEJdMnIgzLUwHkjEZkcg6OQ==}
- '@snowbridge/registry@0.2.19':
- resolution: {integrity: sha512-O76SQHzXSPOEt/wOddyKMWGytp6yUct1TjdiMVJUFUh8ODJfq2cBcgHv7/i3SRCUwbfzUDmHoxFXbIPZ3vWW4g==}
+ '@snowbridge/registry@0.2.20':
+ resolution: {integrity: sha512-Z9t8e8uo8zdGn8lFTsZIl+OgyuYcDflAj4TfgLHNXpp/fPz5MCItRXJL3BEZyVe6TCvWEHSPHcdwD3Q5mfMlXg==}
'@substrate/connect-extension-protocol@2.2.1':
resolution: {integrity: sha512-GoafTgm/Jey9E4Xlj4Z5ZBt/H4drH2CNq8VrAro80rtoznrXnFDNVivLQzZN0Xaj2g8YXSn9pC9Oc9IovYZJXw==}
@@ -6098,36 +6098,36 @@ snapshots:
dependencies:
'@sinonjs/commons': 3.0.1
- '@snowbridge/api@0.2.19(bufferutil@4.0.8)(utf-8-validate@5.0.10)':
+ '@snowbridge/api@0.2.20(bufferutil@4.0.8)(utf-8-validate@5.0.10)':
dependencies:
'@polkadot/api': 16.4.8(bufferutil@4.0.8)(utf-8-validate@5.0.10)
'@polkadot/keyring': 13.5.6(@polkadot/util-crypto@13.5.6(@polkadot/util@13.5.6))(@polkadot/util@13.5.6)
'@polkadot/types': 16.4.8
'@polkadot/util': 13.5.6
'@polkadot/util-crypto': 13.5.6(@polkadot/util@13.5.6)
- '@snowbridge/base-types': 0.2.19
- '@snowbridge/contract-types': 0.2.19(bufferutil@4.0.8)(utf-8-validate@5.0.10)
+ '@snowbridge/base-types': 0.2.20
+ '@snowbridge/contract-types': 0.2.20(bufferutil@4.0.8)(utf-8-validate@5.0.10)
ethers: 6.15.0(bufferutil@4.0.8)(utf-8-validate@5.0.10)
transitivePeerDependencies:
- bufferutil
- supports-color
- utf-8-validate
- '@snowbridge/base-types@0.2.19': {}
+ '@snowbridge/base-types@0.2.20': {}
- '@snowbridge/contract-types@0.2.19(bufferutil@4.0.8)(utf-8-validate@5.0.10)':
+ '@snowbridge/contract-types@0.2.20(bufferutil@4.0.8)(utf-8-validate@5.0.10)':
dependencies:
- '@snowbridge/contracts': 0.2.19
+ '@snowbridge/contracts': 0.2.20
ethers: 6.15.0(bufferutil@4.0.8)(utf-8-validate@5.0.10)
transitivePeerDependencies:
- bufferutil
- utf-8-validate
- '@snowbridge/contracts@0.2.19': {}
+ '@snowbridge/contracts@0.2.20': {}
- '@snowbridge/registry@0.2.19':
+ '@snowbridge/registry@0.2.20':
dependencies:
- '@snowbridge/base-types': 0.2.19
+ '@snowbridge/base-types': 0.2.20
'@substrate/connect-extension-protocol@2.2.1':
optional: true
diff --git a/public/images/background_new.jpg b/public/images/background_new.jpg
new file mode 100644
index 00000000..d29797ec
Binary files /dev/null and b/public/images/background_new.jpg differ
diff --git a/styles/overrides.css b/styles/overrides.css
index 28b91946..11eebd22 100644
--- a/styles/overrides.css
+++ b/styles/overrides.css
@@ -76,7 +76,7 @@ button.action-button {
border: 1px solid transparent; /* Keep box shadow from making the form bigger */
}
-button.action-button:hover, button.action-button:focus {
+button.action-button:hover {
color: #ffffff;
background: linear-gradient(to bottom, #396f9f, #6496ad);
box-shadow: #0a3563 0 2px 2px 0;
@@ -110,7 +110,7 @@ button[role="combobox"], input, .fake-dropdown {
}
body {
- background-image: url("/images/snow5.jpg");
+ background-image: url("/images/background_new.jpg");
background-size:cover;
background-attachment: fixed;
background-position: center;
@@ -139,8 +139,8 @@ label {
}
.focus\:ring-ring:focus {
- --tw-ring-color: hsl(0deg 0% 100%);
- border-radius: 1px;
+ --tw-ring-color: transparent;
+ box-shadow: none !important;
}
.shadow-sm {
@@ -224,3 +224,45 @@ div[role="menubar"],
button.bg-primary:hover {
background-color: hsl(208, 56%, 20%, 0.9) !important;
}
+
+/* Joined input styling for amount + token */
+input.joined-input-left {
+ border-top-right-radius: 0 !important;
+ border-bottom-right-radius: 0 !important;
+ border-right: 0 !important;
+}
+
+button[role="combobox"].joined-input-right {
+ border-top-left-radius: 0 !important;
+ border-bottom-left-radius: 0 !important;
+}
+
+/* Remove all focus styles for cleaner appearance */
+input:focus,
+input:focus-visible,
+button[role="combobox"]:focus,
+button[role="combobox"]:focus-visible,
+button:focus,
+button:focus-visible,
+select:focus,
+select:focus-visible,
+textarea:focus,
+textarea:focus-visible {
+ outline: none !important;
+}
+
+/* Keep the original shadow on inputs and comboboxes */
+input:focus,
+input:focus-visible {
+ box-shadow: #86b1de 0 2px 2px 0 !important;
+}
+
+button[role="combobox"]:focus,
+button[role="combobox"]:focus-visible {
+ box-shadow: #86b1de 0 2px 2px 0 !important;
+}
+
+/* Remove focus ring styles but keep other styles */
+.focus\:ring-ring:focus {
+ --tw-ring-color: transparent !important;
+}
diff --git a/utils/types.ts b/utils/types.ts
index 8de356d2..a4ccee2f 100644
--- a/utils/types.ts
+++ b/utils/types.ts
@@ -6,6 +6,7 @@ import {
toPolkadotV2,
forKusama,
forInterParachain,
+ toPolkadotSnowbridgeV2,
} from "@snowbridge/api";
import { Struct, u128 } from "@polkadot/types";
import { AccountId32 } from "@polkadot/types/interfaces";
@@ -153,6 +154,7 @@ export type FeeInfo = {
delivery:
| toEthereumV2.DeliveryFee
| toPolkadotV2.DeliveryFee
+ | toPolkadotSnowbridgeV2.DeliveryFee
| forInterParachain.DeliveryFee;
type: assetsV2.SourceType;
};
@@ -190,13 +192,18 @@ export type ValidationResult =
| toEthereumV2.ValidationResult
| toEthereumFromEVMV2.ValidationResultEvm
| toPolkadotV2.ValidationResult
+ | toPolkadotSnowbridgeV2.ValidationResult
| forKusama.ValidationResult
| forInterParachain.ValidationResult;
-export type MessageReciept =
+export type MessageReceipt =
| toEthereumV2.MessageReceipt
| toEthereumFromEVMV2.MessageReceiptEvm
| toPolkadotV2.MessageReceipt
+ | (toPolkadotSnowbridgeV2.MessageReceipt & {
+ messageId: string;
+ channelId: string;
+ })
| forKusama.MessageReceipt
| forInterParachain.MessageReceipt;