Skip to content
Open
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
48 changes: 40 additions & 8 deletions apps/main/src/api/xcm.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { formatSourceChainAddress } from "@galacticcouncil/utils"
import { createXcContext } from "@galacticcouncil/xc"
import { chainsMap } from "@galacticcouncil/xc-cfg"
import { AnyChain, AssetAmount } from "@galacticcouncil/xc-core"
import { AnyChain, AssetAmount, ConfigBuilder } from "@galacticcouncil/xc-core"
import { Transfer, TransferBuilder, Wallet } from "@galacticcouncil/xc-sdk"
import {
keepPreviousData,
Expand All @@ -14,6 +14,7 @@ import {
import { secondsToMilliseconds } from "date-fns"
import { useEffect, useRef, useState } from "react"

import { getSupplementalBridgeRoutes } from "@/modules/xcm/transfer/utils/bridge-routes"
import { TProviderContext, useRpcProvider } from "@/providers/rpcProvider"

export const useCrossChainConfig = () => {
Expand Down Expand Up @@ -135,6 +136,7 @@ export type XcmTransferArgs = {
readonly destAddress: string
readonly destAsset: string
readonly destChain: string
readonly bridgeTag?: string
}

export const xcmTransferQuery = (
Expand All @@ -146,6 +148,7 @@ export const xcmTransferQuery = (
destAddress,
destChain,
destAsset,
bridgeTag,
}: XcmTransferArgs,
options?: UseQueryOptions<Transfer>,
) => {
Expand All @@ -162,17 +165,46 @@ export const xcmTransferQuery = (
destAsset,
srcChain,
destChain,
bridgeTag,
],
queryFn: () =>
TransferBuilder(wallet)
queryFn: async () => {
const builder = TransferBuilder(wallet)
.withAsset(srcAsset)
.withSource(srcChain)
.withDestination(destChain)
.build({
srcAddress: srcAddress,
dstAddress: destAddress,
dstAsset: destAsset,
}),

if (bridgeTag) {
const selectedRoute =
builder.routes.find((r) => r.tags?.includes(bridgeTag)) ??
getSupplementalBridgeRoutes(srcChain, destChain, srcAsset).find((r) =>
r.tags?.includes(bridgeTag),
)

if (selectedRoute) {
const configs = ConfigBuilder(wallet.config)
.assets()
.asset(srcAsset)
.source(srcChain)
.destination(destChain)
.build(destAsset)

return wallet.getTransferData(
{
origin: { chain: configs.origin.chain, route: selectedRoute },
reverse: configs.reverse,
},
srcAddress,
destAddress,
)
}
}

return builder.build({
srcAddress: srcAddress,
dstAddress: destAddress,
dstAsset: destAsset,
})
},
enabled:
!!srcAddress &&
!!destAddress &&
Expand Down
6 changes: 6 additions & 0 deletions apps/main/src/i18n/locales/en/xcm.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,12 @@
"approve.title": "Approve spending cap",
"approve.toast.submitted": "Approving {{ amount, number }} {{ symbol }} spending cap on {{ srcChain }}",
"approve.toast.success": "Approved {{ amount, number }} {{ symbol }} spending cap on {{ srcChain }}",
"approve.pending.title": "Approval Pending",
"approve.pending.description": "Your approval transaction is being confirmed on the blockchain.",
"bridge.wormhole": "Wormhole",
"bridge.snowbridge": "Snowbridge",
"bridge.basejump": "Basejump",
"bridge.selector.label": "Via",
"chainAssetSelect.button.selectAssetChain": "Select asset & chain",
"chainAssetSelect.emptyState.noAssets": "No assets found",
"chainAssetSelect.modal.title": "Chain & asset",
Expand Down Expand Up @@ -45,6 +49,8 @@
"report.destFee.insufficientBalance": "You need to have at least {{ amount, number }} {{ symbol }} on {{ chain }}",
"report.asset.frozen": "Your account on {{ chain }} has frozen balance for {{ symbol }}",
"report.account.insufficientDeposit": "You need to have {{ amount, number }} {{ symbol }} on {{ chain }} for existential deposit",
"journey.fastDelivery": "fast delivery",
"journey.delivered": "Delivered",
"journey.status.sent": "In Progress",
"journey.status.pending": "In Progress",
"journey.status.received": "Completed",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import {
Modal,
ModalBody,
ModalFooter,
ModalHeader,
Stepper,
} from "@galacticcouncil/ui/components"
import { useEffect, useState } from "react"
import React, { useEffect, useState } from "react"
import { useTranslation } from "react-i18next"
import { isFunction, omit } from "remeda"

Expand Down Expand Up @@ -41,6 +42,7 @@ export const ReviewMultiTransaction: React.FC<ReviewMultiTransactionProps> = ({
const [resolvedTx, setResolvedTx] = useState<AnyTransaction | null>(null)
const [resolvedConfig, setResolvedConfig] =
useState<TransactionCommon | null>(null)
const [isPendingResolution, setIsPendingResolution] = useState(false)
const [isLoading, setIsLoading] = useState(false)
const [isLastSubmitted, setIsLastSubmitted] = useState(false)
const [hasUserClosedModal, setHasUserClosedModal] = useState(false)
Expand All @@ -63,12 +65,15 @@ export const ReviewMultiTransaction: React.FC<ReviewMultiTransactionProps> = ({
const tx = currentBaseConfig.tx

if (isFunction(tx)) {
setIsPendingResolution(true)
const previousResults = transactionResults.slice(0, currentIndex)
Promise.resolve(tx(previousResults)).then((resolved) => {
setIsPendingResolution(false)
setResolvedTx(resolved.tx)
setResolvedConfig(omit(resolved, ["tx"]))
})
} else {
setIsPendingResolution(false)
setResolvedTx(tx)
setResolvedConfig(null)
}
Expand Down Expand Up @@ -138,6 +143,9 @@ export const ReviewMultiTransaction: React.FC<ReviewMultiTransactionProps> = ({

const { title, description } = currentConfig

const PendingComponent =
isPendingResolution && currentBaseConfig?.pendingComponent

return (
<TransactionProvider key={currentConfig.id} transaction={currentConfig}>
<Modal
Expand Down Expand Up @@ -169,7 +177,13 @@ export const ReviewMultiTransaction: React.FC<ReviewMultiTransactionProps> = ({
title={title ?? t("transaction.title")}
description={description ?? t("transaction.description")}
/>
<ReviewTransactionContent />
{PendingComponent ? (
<ModalBody>
<PendingComponent />
</ModalBody>
) : (
<ReviewTransactionContent />
)}
<ModalFooter justify="space-between">
<ReviewTransactionFooter closable={isClosable} />
</ModalFooter>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {
Stack,
SummaryRow,
} from "@galacticcouncil/ui/components"
import { HYDRATION_CHAIN_KEY } from "@galacticcouncil/utils"
import { HYDRATION_CHAIN_KEY, isValidBigSource } from "@galacticcouncil/utils"
import { chainsMap } from "@galacticcouncil/xc-cfg"
import { ChainEcosystem } from "@galacticcouncil/xc-core"
import Big from "big.js"
Expand Down Expand Up @@ -70,19 +70,26 @@ const XcmSummary = () => {
const srcChain = chainsMap.get(meta.srcChainKey)

const isPolkadotEcosystem = srcChain?.ecosystem === ChainEcosystem.Polkadot

return (
<Stack
separated
separator={<RowSeparator />}
sx={{ mb: "var(--modal-content-inset)" }}
>
<SummaryRow
label={t("transaction.summary.srcFee.label")}
content={t("currency", {
value: meta.srcChainFee,
symbol: meta.srcChainFeeSymbol,
})}
/>
{!!meta.srcChainFee && (
<SummaryRow
label={t("transaction.summary.srcFee.label")}
content={
isValidBigSource(meta.srcChainFee)
? t("currency", {
value: meta.srcChainFee,
symbol: meta.srcChainFeeSymbol,
})
: meta.srcChainFee
}
/>
)}
{Big(meta.dstChainFee || "0").gt(0) && (
<SummaryRow
label={t("transaction.summary.destFee.label")}
Expand Down
11 changes: 5 additions & 6 deletions apps/main/src/modules/xcm/history/XcScanJourneyList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,12 @@ export const XcScanJourneyList = ({ data, pageSize = 10 }: Props) => {
{paginatedData.map((journey) => (
<XcJourneyCard key={journey.correlationId} {...journey} />
))}
<Pagination
totalPages={totalPages}
currentPage={currentPage}
onPageChange={handlePageChange}
/>
</Stack>

<Pagination
totalPages={totalPages}
currentPage={currentPage}
onPageChange={handlePageChange}
/>
</Stack>
)
}
27 changes: 27 additions & 0 deletions apps/main/src/modules/xcm/history/hooks/useXcmBridgeTxStore.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { create } from "zustand"
import { persist } from "zustand/middleware"

export type XcmBridgeTxEntry = {
bridgeProvider: string
/** Intended destination chain URN (may differ from xc-scan's tracked destination) */
destUrn?: string
}

type XcmBridgeTxStore = {
/** Maps originTxPrimary (txHash on source chain) → entry */
entries: Record<string, XcmBridgeTxEntry>
addEntry: (txHash: string, entry: XcmBridgeTxEntry) => void
}

export const useXcmBridgeTxStore = create<XcmBridgeTxStore>()(
persist(
(set) => ({
entries: {},
addEntry: (txHash, entry) =>
set((state) => ({
entries: { ...state.entries, [txHash]: entry },
})),
}),
{ name: "xcm-bridge-tx-store", version: 2 },
),
)
2 changes: 1 addition & 1 deletion apps/main/src/modules/xcm/history/utils/optimistic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export function isOptimisticJourney(journey: XcJourney): boolean {
return journey.correlationId.startsWith(OPTIMISTIC_JOURNEY_PREFIX)
}

function chainToUrn(chain: AnyChain): string {
export function chainToUrn(chain: AnyChain): string {
const ecosystem = chain.ecosystem
if (!ecosystem) return ""
return `urn:ocn:${ecosystem.toLowerCase()}:${getChainId(chain)}`
Expand Down
4 changes: 4 additions & 0 deletions apps/main/src/modules/xcm/history/utils/protocols.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ import { ThemeToken } from "@galacticcouncil/ui/theme"

const XC_SCAN_PROTOCOLS: Record<string, { label: string; color: ThemeToken }> =
{
basejump: {
label: "Basejump 🪂",
color: "colors.skyBlue.600",
},
xcm: {
label: "XCM",
color: "colors.coral.400",
Expand Down
20 changes: 20 additions & 0 deletions apps/main/src/modules/xcm/transfer/XcmForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,13 @@ import { useFormContext } from "react-hook-form"
import { useTranslation } from "react-i18next"

import { useCrossChainBalance } from "@/api/xcm"
import { useXcmBridgeTxStore } from "@/modules/xcm/history/hooks/useXcmBridgeTxStore"
import {
chainToUrn,
insertOptimisticJourney,
removeOptimisticJourney,
} from "@/modules/xcm/history/utils/optimistic"
import { BridgeSelector } from "@/modules/xcm/transfer/components/BridgeSelector"
import { ChainAssetSelectModalSelectionChange } from "@/modules/xcm/transfer/components/ChainAssetSelect"
import { ChainSwitch } from "@/modules/xcm/transfer/components/ChainSwitch"
import { ConnectButton } from "@/modules/xcm/transfer/components/ConnectButton"
Expand Down Expand Up @@ -51,6 +54,7 @@ export const XcmForm = () => {
dryRunError,
sourceChainAssetPairs,
destChainAssetPairs,
availableBridgeRoutes,
isLoading,
isLoadingCall,
isLoadingTransfer,
Expand Down Expand Up @@ -81,6 +85,8 @@ export const XcmForm = () => {

const queryClient = useQueryClient()

const { addEntry: addBridgeTxEntry } = useXcmBridgeTxStore()

const submit = useSubmitXcmTransfer({
onTransferSubmitted: (txHash, values, transfer) => {
if (account) {
Expand All @@ -92,6 +98,12 @@ export const XcmForm = () => {
transfer,
)
}
if (values.bridgeProvider) {
addBridgeTxEntry(txHash, {
bridgeProvider: values.bridgeProvider,
destUrn: values.destChain ? chainToUrn(values.destChain) : undefined,
})
}
resetAmounts()
},
onTransferError: (txHash) => {
Expand Down Expand Up @@ -278,6 +290,14 @@ export const XcmForm = () => {
/>
</Flex>
</Stack>
{availableBridgeRoutes.length > 1 && (
<>
<Separator />
<Stack px={["l", "xl"]} py="m">
<BridgeSelector routes={availableBridgeRoutes} />
</Stack>
</>
)}
<XcmSummary />
<Separator />
<Box p={["l", "xl"]}>
Expand Down
Loading
Loading