feat: support hollar transfers between assethub and hydration (#178)
* feat: support hollar transfers between assethub and hydration * feat: add "Use max" button
This commit is contained in:
@@ -5,7 +5,7 @@ import {
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select"
|
||||
import { ChangeEvent, FC, useEffect, useRef, useState } from "react"
|
||||
import { ChangeEvent, FC, ReactNode, useEffect, useRef, useState } from "react"
|
||||
|
||||
type TokenUnit = "token" | "planck"
|
||||
|
||||
@@ -16,7 +16,8 @@ export const TokenInput: FC<{
|
||||
decimals: number
|
||||
symbol?: string
|
||||
}
|
||||
}> = ({ value, onValueChange, token }) => {
|
||||
inputAction?: ReactNode
|
||||
}> = ({ value, onValueChange, token, inputAction }) => {
|
||||
const [unit, setUnit] = useState<TokenUnit>(() =>
|
||||
token ? "token" : "planck",
|
||||
)
|
||||
@@ -95,6 +96,11 @@ export const TokenInput: FC<{
|
||||
onChange={handleChange}
|
||||
className="h-10 min-w-0 flex-1 rounded-l-md rounded-r-none border border-border border-r-0 bg-background px-3 py-2 font-mono text-sm outline-none disabled:cursor-not-allowed disabled:opacity-50"
|
||||
/>
|
||||
{inputAction ? (
|
||||
<div className="flex h-10 shrink-0 items-center border-y border-border bg-background pr-1">
|
||||
{inputAction}
|
||||
</div>
|
||||
) : null}
|
||||
<Select
|
||||
value={unit}
|
||||
onValueChange={(value) => handleUnitChange(value as TokenUnit)}
|
||||
|
||||
@@ -25,8 +25,9 @@ import {
|
||||
UserRound,
|
||||
Wallet,
|
||||
} from "lucide-react"
|
||||
import { PolkadotClient } from "polkadot-api"
|
||||
import { jsonSerialize } from "polkadot-api/utils"
|
||||
import { AddressInput } from "polkahub"
|
||||
import { Account, AddressInput } from "polkahub"
|
||||
import { FC, PropsWithChildren, ReactNode } from "react"
|
||||
import {
|
||||
catchError,
|
||||
@@ -176,14 +177,60 @@ const AssetPicker = () => {
|
||||
)
|
||||
}
|
||||
|
||||
// We add the HOLLAR destination in Hydration
|
||||
const getParaspellBuilder = (
|
||||
client: PolkadotClient,
|
||||
cfg: {
|
||||
origin: TChain
|
||||
dest: TChain
|
||||
asset: TAssetInfo
|
||||
amount: bigint
|
||||
recipient: string
|
||||
account: Account
|
||||
},
|
||||
) => {
|
||||
return Builder({
|
||||
apiOverrides: { [cfg.origin]: client },
|
||||
customAssets: {
|
||||
Hydration: [
|
||||
{
|
||||
symbol: "HOLLAR",
|
||||
assetId: "222",
|
||||
decimals: 18,
|
||||
existentialDeposit: "20000000000000000",
|
||||
location: {
|
||||
parents: 1,
|
||||
interior: {
|
||||
X2: [{ Parachain: 2034 }, { GeneralIndex: 222 }],
|
||||
},
|
||||
},
|
||||
forceOverride: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
.from(cfg.origin)
|
||||
.to(cfg.dest)
|
||||
.currency({ location: cfg.asset.location, amount: cfg.amount })
|
||||
.recipient(cfg.recipient)
|
||||
.sender(cfg.account.address)
|
||||
}
|
||||
|
||||
const supportedDestinations$ = state(
|
||||
combineLatest([origin$, selectedAsset$]).pipe(
|
||||
map(([origin, selectedAsset]) => {
|
||||
if (!origin || !selectedAsset) return []
|
||||
|
||||
return getSupportedDestinations(origin, {
|
||||
const destinations = getSupportedDestinations(origin, {
|
||||
location: selectedAsset.location,
|
||||
})
|
||||
if (origin === "AssetHubPolkadot" && selectedAsset.symbol === "HOLLAR") {
|
||||
return [...destinations, "Hydration" as const]
|
||||
}
|
||||
if (origin === "Hydration" && selectedAsset.symbol === "HOLLAR") {
|
||||
return [...destinations, "AssetHubPolkadot" as const]
|
||||
}
|
||||
return destinations
|
||||
}),
|
||||
),
|
||||
)
|
||||
@@ -199,6 +246,7 @@ const selectedDest$ = state(
|
||||
tap((destination) => setHashParams({ destination })),
|
||||
),
|
||||
)
|
||||
|
||||
const selectedDestError$ = selectedDest$.pipeState(
|
||||
withLatestFrom(client$, origin$, selectedAsset$, selectedAccount$),
|
||||
switchMap(([dest, client, origin, asset, account]) =>
|
||||
@@ -207,13 +255,14 @@ const selectedDestError$ = selectedDest$.pipeState(
|
||||
|
||||
const tryWithRecipient = (address: string) =>
|
||||
from(
|
||||
Builder(client)
|
||||
.from(origin)
|
||||
.to(dest)
|
||||
.currency({ location: asset.location, amount: 0n })
|
||||
.recipient(address)
|
||||
.sender(account.address)
|
||||
.getTransferInfo(),
|
||||
getParaspellBuilder(client, {
|
||||
origin,
|
||||
dest,
|
||||
asset,
|
||||
amount: 0n,
|
||||
recipient: address,
|
||||
account,
|
||||
}).getTransferInfo(),
|
||||
).pipe(map(() => null))
|
||||
const mapError = catchError<string | null, ObservableInput<null>>(
|
||||
(ex) => [ex.message ?? "Uknown error"],
|
||||
@@ -293,6 +342,7 @@ const amount$ = state(
|
||||
const AmountPicker = () => {
|
||||
const amount = useStateObservable(amount$)
|
||||
const selectedAsset = useStateObservable(selectedAsset$)
|
||||
const balance = useStateObservable(balance$)
|
||||
const token =
|
||||
selectedAsset && typeof selectedAsset.decimals === "number"
|
||||
? {
|
||||
@@ -302,7 +352,24 @@ const AmountPicker = () => {
|
||||
: undefined
|
||||
|
||||
return (
|
||||
<TokenInput value={amount} onValueChange={changeAmount} token={token} />
|
||||
<TokenInput
|
||||
value={amount}
|
||||
onValueChange={changeAmount}
|
||||
token={token}
|
||||
inputAction={
|
||||
balance == null ? undefined : (
|
||||
<button
|
||||
type="button"
|
||||
className="h-7 rounded px-2 text-xs font-medium text-polkadot transition-colors hover:bg-polkadot/10 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-polkadot/50"
|
||||
onClick={() => changeAmount(balance)}
|
||||
aria-label="Use full available balance"
|
||||
title="Use full available balance"
|
||||
>
|
||||
Use max
|
||||
</button>
|
||||
)
|
||||
}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -351,21 +418,7 @@ export const setupConfig$ = state(
|
||||
)
|
||||
export const paraspellBuilder$ = setupConfig$.pipeState(
|
||||
withLatestFrom(client$),
|
||||
map(([v, client]) =>
|
||||
v
|
||||
? Builder({
|
||||
abstractDecimals: false,
|
||||
apiOverrides: {
|
||||
[v.origin]: client,
|
||||
},
|
||||
})
|
||||
.from(v.origin)
|
||||
.to(v.dest)
|
||||
.currency({ location: v.asset.location, amount: v.amount })
|
||||
.recipient(v.recipient)
|
||||
.sender(v.account.address)
|
||||
: null,
|
||||
),
|
||||
map(([v, client]) => (v ? getParaspellBuilder(client, v) : null)),
|
||||
)
|
||||
|
||||
const SetupCard: FC<
|
||||
|
||||
Reference in New Issue
Block a user