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>