From 7661752dc3517a05eee616514e9a604389f25092 Mon Sep 17 00:00:00 2001 From: cor <cor@pruijs.dev> Date: Fri, 24 Jan 2025 14:51:41 +0000 Subject: [PATCH 1/5] feat(app): introduce Address component --- app/src/lib/components/address.svelte | 33 ++++++++++++++ .../lib/components/transfer-details.svelte | 45 ++----------------- 2 files changed, 36 insertions(+), 42 deletions(-) create mode 100644 app/src/lib/components/address.svelte diff --git a/app/src/lib/components/address.svelte b/app/src/lib/components/address.svelte new file mode 100644 index 0000000000..446b09df07 --- /dev/null +++ b/app/src/lib/components/address.svelte @@ -0,0 +1,33 @@ +<script lang="ts"> +import type { Chain } from "$lib/types" +import { toDisplayName } from "$lib/utilities/chains" +import { fromHex, isHex, type Address } from "viem" +import ArrowLeftIcon from "virtual:icons/lucide/arrow-left" + +export let chains: Array<Chain> +export let chainId: string +export let address: string | null + +const chain = chains.find(c => c.chain_id === chainId) ?? null +const parsedAddress = + chain?.rpc_type === "cosmos" && isHex(address) ? fromHex(address, "string") : address +const explorer = chain?.explorers?.at(0)?.address_url ?? null +</script> + +<div class="flex gap-1 items-center text-xs"> +{#if parsedAddress} + {#if !chain} + invalid chain {chainId} + {:else} + {#if !explorer} + {parsedAddress} + {:else} + <a class="underline" href={`${explorer}/${parsedAddress}`}>{parsedAddress}</a> + <!-- svelte-ignore missing-declaration --> + {/if}<ArrowLeftIcon />{toDisplayName( + chainId, + chains, + )} + {/if} +{/if} +</div> diff --git a/app/src/lib/components/transfer-details.svelte b/app/src/lib/components/transfer-details.svelte index f3a2bd515a..0f1d8d6c9e 100644 --- a/app/src/lib/components/transfer-details.svelte +++ b/app/src/lib/components/transfer-details.svelte @@ -18,6 +18,7 @@ import Truncate from "$lib/components/truncate.svelte" import { formatUnits } from "viem" import PacketPath from "./packet-path.svelte" import Token from "./token.svelte" +import Address from "./address.svelte" // prefix a source with 0x if not there for cosmos tx hashes const source = $page.params.source.startsWith("0x") @@ -122,51 +123,11 @@ let processedTransfers = derived( <section class="flex flex-col lg:flex-row gap-8"> <div class="flex-col text-muted-foreground"> <DetailsHeading>Sender</DetailsHeading> - {#if sourceExplorer !== undefined} - <a - href={`/explorer/address/${transfer.sender}`} - class="block text-sm underline break-words" - ><Truncate - class="underline" - value={transfer.sender} - type="address" - /> - </a>{:else}<p class="text-sm break-words"> - <Truncate value={transfer.sender} type="address" /> - </p>{/if} - <p - class={cn( - "text-[10px] break-words", - "normalized_sender" in transfer && - transfer.normalized_sender - ? "text-black dark:text-muted-foreground" - : "text-transparent", - )} - ></p> + <Address address={transfer.sender} {chains} chainId={transfer.source_chain_id} /> </div> <div class="flex-1 lg:text-right flex-col text-muted-foreground"> <DetailsHeading>Receiver</DetailsHeading> - {#if destinationExplorer !== undefined} - <a - href={`/explorer/address/${transfer.receiver}`} - class="block text-sm underline break-words" - ><Truncate - class="underline" - value={transfer.receiver} - type="address" - /> - </a>{:else}<p class="text-sm break-words"> - <Truncate value={transfer.receiver} type="address" /> - </p>{/if} - <p - class={cn( - "text-[10px] break-words", - "normalized_receiver" in transfer && - transfer.normalized_receiver - ? "text-black dark:text-muted-foreground" - : "text-transparent", - )} - ></p> + <Address address={transfer.receiver} {chains} chainId={transfer.destination_chain_id} /> </div> </section> </Card.Content> From 6b8f7c471f4152bfe7ddadd5b726b4c5d4b7cf9f Mon Sep 17 00:00:00 2001 From: cor <cor@pruijs.dev> Date: Fri, 24 Jan 2025 16:22:48 +0000 Subject: [PATCH 2/5] feat(app): show raw and bech addresses --- .../lib/components/TransferFrom/index.svelte | 3 ++ app/src/lib/components/address.svelte | 41 ++++++++++++------- .../table-cells/cell-origin-transfer.svelte | 10 +---- app/src/lib/graphql/fragments/transfers.ts | 2 + app/src/lib/queries/transfers.ts | 4 +- 5 files changed, 35 insertions(+), 25 deletions(-) diff --git a/app/src/lib/components/TransferFrom/index.svelte b/app/src/lib/components/TransferFrom/index.svelte index af250fc50d..19fb4e7aa8 100644 --- a/app/src/lib/components/TransferFrom/index.svelte +++ b/app/src/lib/components/TransferFrom/index.svelte @@ -44,6 +44,9 @@ rawIntents.subscribe(async () => { const chain = chains.find(c => c.chain_id === $rawIntents.source) if (!chain) return null + const destChain = chains.find(c => c.chain_id === $rawIntents.destination) + if (!destChain) return null + // decode from hex if cosmos to assert proper quote token prediction. let baseToken = chain.rpc_type === "cosmos" && isHex($rawIntents.asset) diff --git a/app/src/lib/components/address.svelte b/app/src/lib/components/address.svelte index 446b09df07..25ef10e901 100644 --- a/app/src/lib/components/address.svelte +++ b/app/src/lib/components/address.svelte @@ -1,6 +1,8 @@ <script lang="ts"> import type { Chain } from "$lib/types" import { toDisplayName } from "$lib/utilities/chains" +import { toBech32 } from "@cosmjs/encoding" +import { hexAddressToBech32 } from "@unionlabs/client" import { fromHex, isHex, type Address } from "viem" import ArrowLeftIcon from "virtual:icons/lucide/arrow-left" @@ -10,24 +12,33 @@ export let address: string | null const chain = chains.find(c => c.chain_id === chainId) ?? null const parsedAddress = - chain?.rpc_type === "cosmos" && isHex(address) ? fromHex(address, "string") : address + chain?.rpc_type === "cosmos" && isHex(address) + ? hexAddressToBech32({ address, bech32Prefix: chain.addr_prefix }) + : address const explorer = chain?.explorers?.at(0)?.address_url ?? null </script> -<div class="flex gap-1 items-center text-xs"> -{#if parsedAddress} - {#if !chain} - invalid chain {chainId} - {:else} - {#if !explorer} - {parsedAddress} +<div class="flex flex-col text-xs"> + <div class="flex gap-1 items-center"> + {#if parsedAddress} + {#if !chain} + invalid chain {chainId} {:else} - <a class="underline" href={`${explorer}/${parsedAddress}`}>{parsedAddress}</a> - <!-- svelte-ignore missing-declaration --> - {/if}<ArrowLeftIcon />{toDisplayName( - chainId, - chains, - )} + {#if !explorer} + {parsedAddress} + {:else} + <a class="underline" href={`${explorer}/${parsedAddress}`}>{parsedAddress}</a> + <!-- svelte-ignore missing-declaration --> + {/if}<ArrowLeftIcon />{toDisplayName( + chainId, + chains, + )} + {/if} {/if} -{/if} + </div> + {#if address} + <div class="text-muted-foreground"> + RAW: {address} + </div> + {/if} </div> diff --git a/app/src/lib/components/table-cells/cell-origin-transfer.svelte b/app/src/lib/components/table-cells/cell-origin-transfer.svelte index 4cc5743d53..cfae4b5e00 100644 --- a/app/src/lib/components/table-cells/cell-origin-transfer.svelte +++ b/app/src/lib/components/table-cells/cell-origin-transfer.svelte @@ -3,6 +3,7 @@ import { cn } from "$lib/utilities/shadcn.ts" import { truncate } from "$lib/utilities/format" import * as Tooltip from "$lib/components/ui/tooltip" import type { Chain } from "$lib/types" +import Address from "../address.svelte" export let chains: Array<Chain> export let value: { @@ -20,12 +21,5 @@ const chainDisplayName = <div class="font-bold"> {chainDisplayName} </div> - <Tooltip.Root> - <Tooltip.Trigger> - <div>{truncate(value.address, 8)}</div> - </Tooltip.Trigger> - <Tooltip.Content> - <p>{value.address}</p> - </Tooltip.Content> - </Tooltip.Root> + <Address address={value.address} chainId={value.chainId} {chains}/> </div> diff --git a/app/src/lib/graphql/fragments/transfers.ts b/app/src/lib/graphql/fragments/transfers.ts index 9246042945..b6170d5deb 100644 --- a/app/src/lib/graphql/fragments/transfers.ts +++ b/app/src/lib/graphql/fragments/transfers.ts @@ -3,10 +3,12 @@ import { graphql } from "../index.ts" export const transferListDataFragment = graphql(` fragment TransferListData on v1_ibc_union_fungible_asset_orders { sender + sender_normalized source_chain_id packet_send_timestamp packet_send_transaction_hash receiver + receiver_normalized destination_chain_id packet_recv_timestamp packet_recv_transaction_hash diff --git a/app/src/lib/queries/transfers.ts b/app/src/lib/queries/transfers.ts index 82afc622ce..63a21e3d96 100644 --- a/app/src/lib/queries/transfers.ts +++ b/app/src/lib/queries/transfers.ts @@ -22,12 +22,12 @@ const transferTransform = (tx: FragmentOf<typeof transferListDataFragment>) => { source: { hash: transfer.packet_send_transaction_hash || "unknown", chainId: transfer.source_chain_id ?? raise("source_chain_id is null"), - address: transfer.sender || "unknown" + address: transfer.sender_normalized || "unknown" }, destination: { hash: transfer.packet_recv_transaction_hash || "unknown", chainId: transfer.destination_chain_id ?? raise("destination_chain_id is null"), - address: transfer.receiver || "unknown" + address: transfer.receiver_normalized || "unknown" }, baseToken: transfer.base_token, baseAmount: transfer.base_amount, From 967c57b275be3939244ae5501a5a6fd2ee25d718 Mon Sep 17 00:00:00 2001 From: cor <cor@pruijs.dev> Date: Fri, 24 Jan 2025 16:28:46 +0000 Subject: [PATCH 3/5] feat(app): improve partial intent feedback --- .../components/Cube/faces/Intent.svelte | 33 ++++++++++--------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/app/src/lib/components/TransferFrom/components/Cube/faces/Intent.svelte b/app/src/lib/components/TransferFrom/components/Cube/faces/Intent.svelte index 0af299af68..325de04923 100644 --- a/app/src/lib/components/TransferFrom/components/Cube/faces/Intent.svelte +++ b/app/src/lib/components/TransferFrom/components/Cube/faces/Intent.svelte @@ -98,24 +98,25 @@ let { rawIntents, intents, validation } = stores {#if !$channel} <div>No recommended UCS03 channel to go from {toDisplayName($rawIntents.source, chains)} to {toDisplayName($rawIntents.destination, chains)}</div> {:else} - {#if !$rawIntents.asset} - Select an asset - {:else} - <div class="flex flex-col gap-1"> - {#if !transferArgs} - <LoadingDots/> + <div class="flex flex-col gap-1 justify-end items-center"> + <div class="flex gap-4 text-muted-foreground text-xs">{$channel?.source_connection_id} | {$channel?.source_channel_id} <ArrowRightIcon />{$channel?.destination_connection_id} | {$channel?.destination_channel_id}</div> + {#if !$rawIntents.asset} + Select an asset {:else} - <div class="flex-1 flex flex-col items-center text-xs"> - <div class="flex gap-4 text-muted-foreground">{$channel?.source_connection_id} | {$channel?.source_channel_id} <ArrowRightIcon />{$channel?.destination_connection_id} | {$channel?.destination_channel_id}</div> - <Token amount={$rawIntents.amount} chainId={$rawIntents.destination} denom={transferArgs.quoteToken} {chains}/> - </div> - <Button - disabled={!$validation.isValid} - on:click={() => rotateTo("verifyFace")}>Transfer - </Button> - {/if} + {#if !transferArgs} + <LoadingDots/> + {:else} + <div class="flex-1 flex flex-col items-center text-xs"> + <Token amount={$rawIntents.amount} chainId={$rawIntents.destination} denom={transferArgs.quoteToken} {chains}/> + </div> + <Button + class="w-full mt-2" + disabled={!$validation.isValid} + on:click={() => rotateTo("verifyFace")}>Transfer + </Button> + {/if} + {/if} </div> - {/if} {/if} </div> </div> From 47cf2a681767ec499ec5bbf405595c34a0621733 Mon Sep 17 00:00:00 2001 From: cor <cor@pruijs.dev> Date: Fri, 24 Jan 2025 16:36:40 +0000 Subject: [PATCH 4/5] feat(app): bech32 decode receiver --- app/src/lib/components/TransferFrom/index.svelte | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/app/src/lib/components/TransferFrom/index.svelte b/app/src/lib/components/TransferFrom/index.svelte index 19fb4e7aa8..a81b20c402 100644 --- a/app/src/lib/components/TransferFrom/index.svelte +++ b/app/src/lib/components/TransferFrom/index.svelte @@ -12,7 +12,12 @@ import { userBalancesQuery } from "$lib/queries/balance" import { userAddress, balanceStore } from "$lib/components/TransferFrom/transfer/balances.ts" import { createRawIntentsStore } from "./transfer/raw-intents.ts" import { derived, writable, type Writable } from "svelte/store" -import { getChannelInfo, getQuoteToken } from "@unionlabs/client" +import { + bech32AddressToHex, + getChannelInfo, + getQuoteToken, + isValidBech32Address +} from "@unionlabs/client" import { fromHex, isHex, toHex } from "viem" import { subscribe } from "graphql" @@ -61,6 +66,11 @@ rawIntents.subscribe(async () => { return null } + const receiver = + destChain.rpc_type === "cosmos" && isValidBech32Address($rawIntents.receiver) + ? bech32AddressToHex({ address: $rawIntents.receiver }) + : $rawIntents.receiver + let ucs03address = chain.rpc_type === "cosmos" ? fromHex(`0x${$channel.source_port_id}`, "string") @@ -73,7 +83,7 @@ rawIntents.subscribe(async () => { baseAmount: BigInt($rawIntents.amount), quoteToken: quoteToken.value.quote_token, quoteAmount: BigInt($rawIntents.amount), - receiver: $rawIntents.receiver, + receiver, sourceChannelId: $channel.source_channel_id, ucs03address }) From 67894a21e9384e603c4d38f8693d20bbac77853a Mon Sep 17 00:00:00 2001 From: cor <cor@pruijs.dev> Date: Fri, 24 Jan 2025 16:46:36 +0000 Subject: [PATCH 5/5] feat(app): bech32 decode/encode cosmos receive address --- .../components/Cube/faces/Intent.svelte | 4 ++++ app/src/lib/components/address.svelte | 18 +++++++++--------- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/app/src/lib/components/TransferFrom/components/Cube/faces/Intent.svelte b/app/src/lib/components/TransferFrom/components/Cube/faces/Intent.svelte index 325de04923..95e12d5497 100644 --- a/app/src/lib/components/TransferFrom/components/Cube/faces/Intent.svelte +++ b/app/src/lib/components/TransferFrom/components/Cube/faces/Intent.svelte @@ -13,6 +13,7 @@ import Token from "$lib/components/token.svelte" import type { Chain, Ucs03Channel } from "$lib/types" import ArrowRightIcon from "virtual:icons/lucide/arrow-right" import { toDisplayName } from "$lib/utilities/chains" +import Address from "$lib/components/address.svelte" interface Props { stores: { @@ -109,6 +110,9 @@ let { rawIntents, intents, validation } = stores <div class="flex-1 flex flex-col items-center text-xs"> <Token amount={$rawIntents.amount} chainId={$rawIntents.destination} denom={transferArgs.quoteToken} {chains}/> </div> + {#if $validation.isValid} + <Address address={transferArgs.receiver} {chains} chainId={$channel.destination_chain_id}/> + {/if} <Button class="w-full mt-2" disabled={!$validation.isValid} diff --git a/app/src/lib/components/address.svelte b/app/src/lib/components/address.svelte index 25ef10e901..b37cbad641 100644 --- a/app/src/lib/components/address.svelte +++ b/app/src/lib/components/address.svelte @@ -1,14 +1,15 @@ <script lang="ts"> import type { Chain } from "$lib/types" import { toDisplayName } from "$lib/utilities/chains" -import { toBech32 } from "@cosmjs/encoding" import { hexAddressToBech32 } from "@unionlabs/client" -import { fromHex, isHex, type Address } from "viem" +import { isHex } from "viem" import ArrowLeftIcon from "virtual:icons/lucide/arrow-left" export let chains: Array<Chain> export let chainId: string export let address: string | null +export let showChain = false +export let showRaw = false const chain = chains.find(c => c.chain_id === chainId) ?? null const parsedAddress = @@ -28,17 +29,16 @@ const explorer = chain?.explorers?.at(0)?.address_url ?? null {parsedAddress} {:else} <a class="underline" href={`${explorer}/${parsedAddress}`}>{parsedAddress}</a> - <!-- svelte-ignore missing-declaration --> - {/if}<ArrowLeftIcon />{toDisplayName( + {/if}{#if showChain}<ArrowLeftIcon />{toDisplayName( chainId, chains, - )} + )}{/if} {/if} {/if} </div> - {#if address} - <div class="text-muted-foreground"> - RAW: {address} - </div> + {#if address && showRaw} + <div class="text-muted-foreground"> + RAW: {address} + </div> {/if} </div>