mirror of
https://github.com/aljazceru/mutiny-web.git
synced 2025-12-30 04:14:25 +01:00
543 lines
17 KiB
TypeScript
543 lines
17 KiB
TypeScript
import {
|
|
createEffect,
|
|
createMemo,
|
|
createResource,
|
|
createSignal,
|
|
For,
|
|
Match,
|
|
onMount,
|
|
ParentComponent,
|
|
Show,
|
|
Suspense,
|
|
Switch,
|
|
} from "solid-js"
|
|
import {
|
|
CENTER_COLUMN,
|
|
MISSING_LABEL,
|
|
REDSHIFT_LABEL,
|
|
RIGHT_COLUMN,
|
|
THREE_COLUMNS,
|
|
UtxoItem,
|
|
} from "~/components/Activity"
|
|
import {
|
|
Card,
|
|
DefaultMain,
|
|
LargeHeader,
|
|
LoadingSpinner,
|
|
NiceP,
|
|
MutinyWalletGuard,
|
|
SafeArea,
|
|
SmallAmount,
|
|
SmallHeader,
|
|
VStack,
|
|
} from "~/components/layout"
|
|
import { BackLink } from "~/components/layout/BackLink"
|
|
import { StyledRadioGroup } from "~/components/layout/Radio"
|
|
import NavBar from "~/components/NavBar"
|
|
import { useMegaStore } from "~/state/megaStore"
|
|
import wave from "~/assets/wave.gif"
|
|
import utxoIcon from "~/assets/icons/coin.svg"
|
|
import { Button } from "~/components/layout/Button"
|
|
import { ProgressBar } from "~/components/layout/ProgressBar"
|
|
import { MutinyChannel } from "@mutinywallet/mutiny-wasm"
|
|
import mempoolTxUrl from "~/utils/mempoolTxUrl"
|
|
import { Amount } from "~/components/Amount"
|
|
import { getRedshifted, setRedshifted } from "~/utils/fakeLabels"
|
|
import { Network } from "~/logic/mutinyWalletSetup"
|
|
|
|
type ShiftOption = "utxo" | "lightning"
|
|
|
|
type ShiftStage = "choose" | "observe" | "success" | "failure"
|
|
|
|
type OutPoint = string // Replace with the actual TypeScript type for OutPoint
|
|
type RedshiftStatus = string // Replace with the actual TypeScript type for RedshiftStatus
|
|
type RedshiftRecipient = unknown // Replace with the actual TypeScript type for RedshiftRecipient
|
|
type PublicKey = unknown // Replace with the actual TypeScript type for PublicKey
|
|
|
|
interface RedshiftResult {
|
|
id: string
|
|
input_utxo: OutPoint
|
|
status: RedshiftStatus
|
|
recipient: RedshiftRecipient
|
|
output_utxo?: OutPoint
|
|
introduction_channel?: OutPoint
|
|
output_channel?: OutPoint
|
|
introduction_node: PublicKey
|
|
amount_sats: bigint
|
|
change_amt?: bigint
|
|
fees_paid: bigint
|
|
}
|
|
|
|
const dummyRedshift: RedshiftResult = {
|
|
id: "44036599c37d590899e8d5d920860286",
|
|
input_utxo:
|
|
"44036599c37d590899e8d5d92086028695d2c2966fdc354ce1da9a9eac610a53:1",
|
|
status: "Completed", // Replace with a dummy value for RedshiftStatus
|
|
recipient: {}, // Replace with a dummy value for RedshiftRecipient
|
|
output_utxo:
|
|
"44036599c37d590899e8d5d92086028695d2c2966fdc354ce1da9a9eac610a53:1",
|
|
introduction_channel:
|
|
"a7773e57f8595848a635e9af105927cac9ecaf292d71a76456ae0455bd3c9c64:0",
|
|
output_channel:
|
|
"a7773e57f8595848a635e9af105927cac9ecaf292d71a76456ae0455bd3c9c64:0",
|
|
introduction_node: {}, // Replace with a dummy value for PublicKey
|
|
amount_sats: BigInt(1000000),
|
|
change_amt: BigInt(12345),
|
|
fees_paid: BigInt(2500),
|
|
}
|
|
|
|
function RedshiftReport(props: { redshift: RedshiftResult; utxo: UtxoItem }) {
|
|
const [state, _actions] = useMegaStore()
|
|
|
|
const getUtXos = async () => {
|
|
console.log("Getting utxos")
|
|
return (await state.mutiny_wallet?.list_utxos()) as UtxoItem[]
|
|
}
|
|
|
|
// function findUtxoByOutpoint(
|
|
// outpoint?: string,
|
|
// utxos: UtxoItem[] = []
|
|
// ): UtxoItem | undefined {
|
|
// if (!outpoint) return undefined
|
|
// return utxos.find((utxo) => utxo.outpoint === outpoint)
|
|
// }
|
|
|
|
const [_utxos, { refetch: _refetchUtxos }] = createResource(getUtXos)
|
|
|
|
// const inputUtxo = createMemo(() => {
|
|
// console.log(utxos())
|
|
// const foundUtxo = findUtxoByOutpoint(props.redshift.input_utxo, utxos())
|
|
// console.log("Found utxo:", foundUtxo)
|
|
// return foundUtxo
|
|
// })
|
|
|
|
const [redshiftResource, { refetch: _refetchRedshift }] = createResource(
|
|
|
|
async () => {
|
|
console.log("Checking redshift", props.redshift.id)
|
|
const redshift = await state.mutiny_wallet?.get_redshift(props.redshift.id)
|
|
console.log(redshift)
|
|
return redshift
|
|
}
|
|
)
|
|
onMount(() => {
|
|
// const interval = setInterval(() => {
|
|
// if (redshiftResource()) refetch()
|
|
// // if (sentAmount() === 200000) {
|
|
// // clearInterval(interval)
|
|
// // props.setShiftStage("success");
|
|
// // // setSentAmount((0))
|
|
|
|
// // } else {
|
|
// // setSentAmount((sentAmount() + 50000))
|
|
// // }
|
|
// }, 1000)
|
|
})
|
|
|
|
|
|
// const outputUtxo = createMemo(() => {
|
|
// return findUtxoByOutpoint(redshiftResource()?.output_utxo, utxos())
|
|
// })
|
|
|
|
createEffect(() => {
|
|
setRedshifted(true, redshiftResource()?.output_utxo)
|
|
})
|
|
|
|
const network = state.mutiny_wallet?.get_network() as Network
|
|
|
|
return (
|
|
<VStack biggap>
|
|
{/* <VStack>
|
|
<NiceP>We did it. Here's your new UTXO:</NiceP>
|
|
<Show when={utxos() && outputUtxo()}>
|
|
<Card>
|
|
<Utxo item={outputUtxo()!} />
|
|
</Card>
|
|
</Show>
|
|
</VStack> */}
|
|
<VStack>
|
|
<NiceP>What happened?</NiceP>
|
|
<Show when={redshiftResource()}>
|
|
<Card>
|
|
<VStack biggap>
|
|
{/* <KV key="Input utxo">
|
|
<Show when={utxos() && inputUtxo()}>
|
|
<Utxo item={inputUtxo()!} />
|
|
</Show>
|
|
</KV> */}
|
|
<KV key="Starting amount">
|
|
<Amount amountSats={redshiftResource()!.amount_sats} />
|
|
</KV>
|
|
<KV key="Fees paid">
|
|
<Amount amountSats={redshiftResource()!.fees_paid} />
|
|
</KV>
|
|
<KV key="Change">
|
|
<Amount amountSats={redshiftResource()!.change_amt} />
|
|
</KV>
|
|
<KV key="Outbound channel">
|
|
<VStack>
|
|
<pre class="whitespace-pre-wrap break-all">
|
|
{redshiftResource()!.introduction_channel}
|
|
</pre>
|
|
<a
|
|
class=""
|
|
href={mempoolTxUrl(
|
|
redshiftResource()!.introduction_channel?.split(":")[0],
|
|
network
|
|
)}
|
|
target="_blank"
|
|
rel="noreferrer"
|
|
>
|
|
View on mempool
|
|
</a>
|
|
</VStack>
|
|
</KV>
|
|
<Show when={redshiftResource()!.output_channel}>
|
|
<KV key="Return channel">
|
|
<VStack>
|
|
<pre class="whitespace-pre-wrap break-all">
|
|
{redshiftResource()!.output_channel}
|
|
</pre>
|
|
<a
|
|
class=""
|
|
href={mempoolTxUrl(
|
|
redshiftResource()!.output_channel?.split(":")[0],
|
|
network
|
|
)}
|
|
target="_blank"
|
|
rel="noreferrer"
|
|
>
|
|
View on mempool
|
|
</a>
|
|
</VStack>
|
|
</KV>
|
|
</Show>
|
|
</VStack>
|
|
</Card>
|
|
</Show>
|
|
</VStack>
|
|
</VStack>
|
|
)
|
|
}
|
|
|
|
const SHIFT_OPTIONS = [
|
|
{ value: "utxo", label: "UTXO", caption: "Trade your UTXO for a fresh UTXO" },
|
|
{
|
|
value: "lightning",
|
|
label: "Lightning",
|
|
caption: "Convert your UTXO into Lightning",
|
|
},
|
|
]
|
|
|
|
export function Utxo(props: { item: UtxoItem; onClick?: () => void }) {
|
|
const redshifted = createMemo(() => getRedshifted(props.item.outpoint))
|
|
return (
|
|
<>
|
|
<div class={THREE_COLUMNS} onClick={() => props.onClick && props.onClick()}>
|
|
<div class="flex items-center">
|
|
<img src={utxoIcon} alt="coin" />
|
|
</div>
|
|
<div class={CENTER_COLUMN}>
|
|
<div class="flex gap-2">
|
|
<Show
|
|
when={redshifted()}
|
|
fallback={<h2 class={MISSING_LABEL}>Unknown</h2>}
|
|
>
|
|
<h2 class={REDSHIFT_LABEL}>Redshift</h2>
|
|
</Show>
|
|
</div>
|
|
<SmallAmount amount={props.item.txout.value} />
|
|
</div>
|
|
<div class={RIGHT_COLUMN}>
|
|
<SmallHeader
|
|
class={props.item?.is_spent ? "text-m-red" : "text-m-green"}
|
|
>
|
|
{/* {props.item?.is_spent ? "SPENT" : "UNSPENT"} */}
|
|
</SmallHeader>
|
|
</div>
|
|
</div>
|
|
</>
|
|
)
|
|
}
|
|
|
|
const FAKE_STATES = [
|
|
"Creating a new node",
|
|
"Opening a channel",
|
|
"Sending funds through",
|
|
"Closing the channel",
|
|
"Redshift complete",
|
|
]
|
|
|
|
function ShiftObserver(props: {
|
|
setShiftStage: (stage: ShiftStage) => void
|
|
redshiftId: string
|
|
}) {
|
|
const [_state, _actions] = useMegaStore()
|
|
|
|
const [fakeStage, _setFakeStage] = createSignal(2)
|
|
|
|
const [sentAmount, setSentAmount] = createSignal(0)
|
|
|
|
onMount(() => {
|
|
const interval = setInterval(() => {
|
|
if (sentAmount() === 200000) {
|
|
clearInterval(interval)
|
|
props.setShiftStage("success")
|
|
// setSentAmount((0))
|
|
} else {
|
|
setSentAmount(sentAmount() + 50000)
|
|
}
|
|
}, 1000)
|
|
})
|
|
|
|
// async function checkRedshift(id: string) {
|
|
// console.log("Checking redshift", id)
|
|
// const redshift = await state.mutiny_wallet?.get_redshift(id)
|
|
// console.log(redshift)
|
|
// return redshift
|
|
// }
|
|
|
|
// const [redshiftResource, { refetch }] = createResource(
|
|
// props.redshiftId,
|
|
// checkRedshift
|
|
// )
|
|
|
|
// onMount(() => {
|
|
// const interval = setInterval(() => {
|
|
// if (redshiftResource()) refetch();
|
|
// // if (sentAmount() === 200000) {
|
|
// // clearInterval(interval)
|
|
// // props.setShiftStage("success");
|
|
// // // setSentAmount((0))
|
|
|
|
// // } else {
|
|
// // setSentAmount((sentAmount() + 50000))
|
|
// // }
|
|
// }, 1000)
|
|
// })
|
|
|
|
// createEffect(() => {
|
|
// const interval = setInterval(() => {
|
|
// if (chosenUtxo()) refetch();
|
|
// }, 1000); // Poll every second
|
|
// onCleanup(() => {
|
|
// clearInterval(interval);
|
|
// });
|
|
// });
|
|
|
|
return (
|
|
<>
|
|
<NiceP>Watch it go!</NiceP>
|
|
<Card>
|
|
<VStack>
|
|
<pre class="self-center">{FAKE_STATES[fakeStage()]}</pre>
|
|
<ProgressBar value={sentAmount()} max={200000} />
|
|
<img src={wave} class="h-4 self-center" alt="sine wave" />
|
|
</VStack>
|
|
</Card>
|
|
</>
|
|
)
|
|
}
|
|
|
|
const KV: ParentComponent<{ key: string }> = (props) => {
|
|
return (
|
|
<div class="flex flex-col gap-2">
|
|
<p class="text-sm font-semibold uppercase">{props.key}</p>
|
|
{props.children}
|
|
</div>
|
|
)
|
|
}
|
|
|
|
export default function Redshift() {
|
|
const [state, _actions] = useMegaStore()
|
|
|
|
const [shiftStage, setShiftStage] = createSignal<ShiftStage>("choose")
|
|
const [shiftType, setShiftType] = createSignal<ShiftOption>("utxo")
|
|
|
|
const [chosenUtxo, setChosenUtxo] = createSignal<UtxoItem>()
|
|
|
|
const getUtXos = async () => {
|
|
console.log("Getting utxos")
|
|
return (await state.mutiny_wallet?.list_utxos()) as UtxoItem[]
|
|
}
|
|
|
|
const getChannels = async () => {
|
|
console.log("Getting channels")
|
|
await state.mutiny_wallet?.sync()
|
|
const channels = (await state.mutiny_wallet?.list_channels()) as Promise<
|
|
MutinyChannel[]
|
|
>
|
|
console.log(channels)
|
|
return channels
|
|
}
|
|
|
|
const [utxos, { refetch: _refetchUtxos }] = createResource(getUtXos)
|
|
const [_channels, { refetch: _refetchChannels }] = createResource(getChannels)
|
|
|
|
const redshiftedUtxos = createMemo(() => {
|
|
return utxos()?.filter((utxo) => getRedshifted(utxo.outpoint))
|
|
})
|
|
|
|
const unredshiftedUtxos = createMemo(() => {
|
|
return utxos()?.filter((utxo) => !getRedshifted(utxo.outpoint))
|
|
})
|
|
|
|
function resetState() {
|
|
setShiftStage("choose")
|
|
setShiftType("utxo")
|
|
setChosenUtxo(undefined)
|
|
}
|
|
|
|
async function redshiftUtxo(utxo: UtxoItem) {
|
|
console.log("Redshifting utxo", utxo.outpoint)
|
|
const redshift = await state.mutiny_wallet?.init_redshift(utxo.outpoint)
|
|
console.log("Redshift initialized:")
|
|
console.log(redshift)
|
|
return redshift
|
|
}
|
|
|
|
const [initializedRedshift, { refetch: _refetchRedshift }] = createResource(
|
|
chosenUtxo,
|
|
redshiftUtxo
|
|
)
|
|
|
|
createEffect(() => {
|
|
if (chosenUtxo() && initializedRedshift()) {
|
|
// window.location.href = "/"
|
|
setShiftStage("observe")
|
|
}
|
|
})
|
|
|
|
return (
|
|
<MutinyWalletGuard>
|
|
<SafeArea>
|
|
<DefaultMain>
|
|
<BackLink />
|
|
<LargeHeader>Redshift (coming soon)</LargeHeader>
|
|
<div class="relative filter grayscale pointer-events-none opacity-75">
|
|
<VStack biggap>
|
|
{/* <pre>{JSON.stringify(redshiftResource(), null, 2)}</pre> */}
|
|
<Switch>
|
|
<Match when={shiftStage() === "choose"}>
|
|
<VStack>
|
|
<NiceP>Where is this going?</NiceP>
|
|
<StyledRadioGroup
|
|
accent="red"
|
|
value={shiftType()}
|
|
onValueChange={(newValue) =>
|
|
setShiftType(newValue as ShiftOption)
|
|
}
|
|
choices={SHIFT_OPTIONS}
|
|
/>
|
|
</VStack>
|
|
<VStack>
|
|
<NiceP>
|
|
Choose your{" "}
|
|
<span class="inline-block">
|
|
<img class="h-4" src={wave} alt="sine wave" />
|
|
</span>{" "}
|
|
UTXO to begin
|
|
</NiceP>
|
|
<Suspense>
|
|
<Card title="Unshifted UTXOs">
|
|
<Switch>
|
|
<Match when={utxos.loading}>
|
|
<LoadingSpinner wide />
|
|
</Match>
|
|
<Match
|
|
when={
|
|
utxos.state === "ready" &&
|
|
unredshiftedUtxos()?.length === 0
|
|
}
|
|
>
|
|
<code>No utxos (empty state)</code>
|
|
</Match>
|
|
<Match
|
|
when={
|
|
utxos.state === "ready" &&
|
|
unredshiftedUtxos() &&
|
|
unredshiftedUtxos()!.length >= 0
|
|
}
|
|
>
|
|
<For each={unredshiftedUtxos()}>
|
|
{(utxo) => (
|
|
<Utxo
|
|
item={utxo}
|
|
onClick={() => setChosenUtxo(utxo)}
|
|
/>
|
|
)}
|
|
</For>
|
|
</Match>
|
|
</Switch>
|
|
</Card>
|
|
</Suspense>
|
|
<Suspense>
|
|
<Card
|
|
titleElement={
|
|
<SmallHeader>
|
|
<span class="text-m-red">Redshifted </span>UTXOs
|
|
</SmallHeader>
|
|
}
|
|
>
|
|
<Switch>
|
|
<Match when={utxos.loading}>
|
|
<LoadingSpinner wide />
|
|
</Match>
|
|
<Match
|
|
when={
|
|
utxos.state === "ready" &&
|
|
redshiftedUtxos()?.length === 0
|
|
}
|
|
>
|
|
<code>No utxos (empty state)</code>
|
|
</Match>
|
|
<Match
|
|
when={
|
|
utxos.state === "ready" &&
|
|
redshiftedUtxos() &&
|
|
redshiftedUtxos()!.length >= 0
|
|
}
|
|
>
|
|
<For each={redshiftedUtxos()}>
|
|
{(utxo) => <Utxo item={utxo} />}
|
|
</For>
|
|
</Match>
|
|
</Switch>
|
|
</Card>
|
|
</Suspense>
|
|
</VStack>
|
|
</Match>
|
|
<Match when={shiftStage() === "observe" && chosenUtxo()}>
|
|
<ShiftObserver
|
|
setShiftStage={setShiftStage}
|
|
redshiftId="dummy-redshift"
|
|
/>
|
|
</Match>
|
|
<Match when={shiftStage() === "success" && chosenUtxo()}>
|
|
<VStack biggap>
|
|
<RedshiftReport
|
|
redshift={dummyRedshift}
|
|
utxo={chosenUtxo()!}
|
|
/>
|
|
<Button intent="red" onClick={resetState}>
|
|
Nice
|
|
</Button>
|
|
</VStack>
|
|
</Match>
|
|
<Match when={shiftStage() === "failure"}>
|
|
<NiceP>Oh dear</NiceP>
|
|
<NiceP>Here's what happened:</NiceP>
|
|
<Button intent="red" onClick={resetState}>
|
|
Dangit
|
|
</Button>
|
|
</Match>
|
|
</Switch>
|
|
</VStack>
|
|
</div>
|
|
</DefaultMain>
|
|
<NavBar activeTab="redshift" />
|
|
</SafeArea>
|
|
</MutinyWalletGuard>
|
|
)
|
|
}
|