Repository: dusk-network/rusk (language: rust)

Issue:
explorer: Build token page
#### Summary
We need to build the new token page.

#### Possible solution design or implementation
https://www.figma.com/design/INYlGpXEc8tIHCEPNlPyfd/Explorer-2.0?node-id=2443-51264&p=f&t=7ip6pDwPL49rle7f-0




Discussion / hints:

Fix patch:
diff --git a/explorer/CHANGELOG.md b/explorer/CHANGELOG.md
index 3bb71a775b..b2ff90bc25 100644
--- a/explorer/CHANGELOG.md
+++ b/explorer/CHANGELOG.md
@@ -10,6 +10,8 @@ and this project adheres to
 
 ### Added
 
+- Add Tokens page [#3415]
+
 ### Changed
 
 - Replace legacy event system with RUES [#3425]
@@ -148,6 +150,7 @@ and this project adheres to
 [#3338]: https://github.com/dusk-network/rusk/issues/3338
 [#3377]: https://github.com/dusk-network/rusk/issues/3377
 [#3425]: https://github.com/dusk-network/rusk/issues/3425
+[#3415]: https://github.com/dusk-network/rusk/issues/3415
 
 <!-- VERSIONS -->
 
diff --git a/explorer/README.md b/explorer/README.md
index 4944bd2328..aa4f92c813 100644
--- a/explorer/README.md
+++ b/explorer/README.md
@@ -35,6 +35,7 @@ VITE_REFETCH_INTERVAL=10000
 VITE_RUSK_PATH="" # Optional, set to '/rusk' for dev mode
 VITE_STATS_REFETCH_INTERVAL=1000
 VITE_TRANSACTIONS_LIST_ENTRIES=100
+VITE_FEATURE_TOKENS=true
 ```
 
 ## Environment variables and dev mode
diff --git a/explorer/src/lib/chain-info/chain-info.d.ts b/explorer/src/lib/chain-info/chain-info.d.ts
index be3924099b..97cee13ee6 100644
--- a/explorer/src/lib/chain-info/chain-info.d.ts
+++ b/explorer/src/lib/chain-info/chain-info.d.ts
@@ -35,6 +35,8 @@ type SearchResult = {
 };
 
 type Transaction = {
+  from: string | undefined;
+  to: string | undefined;
   blockhash: string;
   blockheight: number;
   date: Date;
diff --git a/explorer/src/lib/chain-info/transformTransaction.js b/explorer/src/lib/chain-info/transformTransaction.js
index 53e42b315c..00e803775e 100644
--- a/explorer/src/lib/chain-info/transformTransaction.js
+++ b/explorer/src/lib/chain-info/transformTransaction.js
@@ -6,12 +6,14 @@ const transformTransaction = (tx) => ({
   blockheight: tx.blockHeight,
   date: unixTsToDate(tx.blockTimestamp),
   feepaid: tx.gasSpent * tx.tx.gasPrice,
+  from: undefined,
   gaslimit: tx.tx.gasLimit,
   gasprice: tx.tx.gasPrice,
   gasspent: tx.gasSpent,
   memo: tx.tx.memo ?? "",
   method: tx.tx.isDeploy ? "deploy" : tx.tx.callData?.fnName ?? "transfer",
   success: tx.err === null,
+  to: undefined,
   txerror: tx.err ?? "",
   txid: tx.id,
   txtype: tx.tx.txType,
diff --git a/explorer/src/lib/components/blocks-card/BlocksCard.css b/explorer/src/lib/components/blocks-card/BlocksCard.css
deleted file mode 100644
index 69050f4372..0000000000
--- a/explorer/src/lib/components/blocks-card/BlocksCard.css
+++ /dev/null
@@ -1,4 +0,0 @@
-.blocks-card__table,
-.blocks-card__list {
-  overflow-y: auto;
-}
diff --git a/explorer/src/lib/components/blocks-card/BlocksCard.svelte b/explorer/src/lib/components/blocks-card/BlocksCard.svelte
index 8ceec159e2..633fdd454d 100644
--- a/explorer/src/lib/components/blocks-card/BlocksCard.svelte
+++ b/explorer/src/lib/components/blocks-card/BlocksCard.svelte
@@ -3,8 +3,6 @@
 <script>
   import { BlocksList, BlocksTable, DataCard } from "$lib/components";
 
-  import "./BlocksCard.css";
-
   /** @type {Block[] | null}*/
   export let blocks;
 
@@ -50,12 +48,12 @@
       }}
 >
   {#if isSmallScreen}
-    <div class="blocks-card__list">
+    <div class="data-card__list">
       {#each displayedBlocks as block (block)}
         <BlocksList data={block} />
       {/each}
     </div>
   {:else}
-    <BlocksTable data={displayedBlocks} className="blocks-card__table" />
+    <BlocksTable data={displayedBlocks} className="data-card__table" />
   {/if}
 </DataCard>
diff --git a/explorer/src/lib/components/data-card/DataCard.css b/explorer/src/lib/components/data-card/DataCard.css
index bda3591005..468f582564 100644
--- a/explorer/src/lib/components/data-card/DataCard.css
+++ b/explorer/src/lib/components/data-card/DataCard.css
@@ -37,3 +37,8 @@
 .data-card__progress-bar .dusk-progress-bar__filler {
   background-color: var(--primary-color);
 }
+
+.data-card__table,
+.data-card__list {
+  overflow-y: auto;
+}
diff --git a/explorer/src/lib/components/data-card/DataCard.svelte b/explorer/src/lib/components/data-card/DataCard.svelte
index cba1fb3bd2..129b736e3b 100644
--- a/explorer/src/lib/components/data-card/DataCard.svelte
+++ b/explorer/src/lib/components/data-card/DataCard.svelte
@@ -7,7 +7,7 @@
 
   import "./DataCard.css";
 
-  /** @type {Block[] | Transaction[] | HostProvisioner[] | Block | Transaction | null}*/
+  /** @type {Block[] | Transaction[] | HostProvisioner[] | Block | Transaction | Token[] | null}*/
   export let data;
 
   /** @type {Error | null}*/
diff --git a/explorer/src/lib/components/index.js b/explorer/src/lib/components/index.js
index f2ea2e9319..e53a7cafd5 100644
--- a/explorer/src/lib/components/index.js
+++ b/explorer/src/lib/components/index.js
@@ -25,6 +25,12 @@ export { default as TransactionsCard } from "./transactions-card/TransactionsCar
 export { default as TransactionDetails } from "./transaction-details/TransactionDetails.svelte";
 export { default as TransactionsList } from "./transactions-list/TransactionsList.svelte";
 export { default as TransactionsTable } from "./transactions-table/TransactionsTable.svelte";
+export { default as TokensTable } from "./tokens-table/TokensTable.svelte";
+export { default as TokenOverview } from "./token-overview/TokenOverview.svelte";
+export { default as TokenDetailItem } from "./token-detail-item/TokenDetailItem.svelte";
+export { default as TokenDetailsTable } from "./token-details-table/TokenDetailsTable.svelte";
+export { default as TokenListDetails } from "./token-list-details/TokenListDetails.svelte";
+export { default as TokenTransactionsList } from "./token-transactions-list/TokenTransactionsList.svelte";
 export { default as TransactionType } from "./transaction-type/TransactionType.svelte";
 export { default as TransactionStatus } from "./transaction-status/TransactionStatus.svelte";
 export { default as WorldMap } from "./world-map/WorldMap.svelte";
diff --git a/explorer/src/lib/components/navbar/Navbar.svelte b/explorer/src/lib/components/navbar/Navbar.svelte
index 2d6d41a539..c941cb321f 100644
--- a/explorer/src/lib/components/navbar/Navbar.svelte
+++ b/explorer/src/lib/components/navbar/Navbar.svelte
@@ -22,7 +22,7 @@
   /** @type {*} */
   let notificationData;
 
-  const navigation = [
+  let navigation = [
     {
       link: "/",
       title: "Chain Info",
@@ -41,6 +41,11 @@
     },
   ];
 
+  $: if (import.meta.env.VITE_FEATURE_TOKENS === "true") {
+    navigation.push({ link: "/tokens", title: "Tokens" });
+    navigation = navigation;
+  }
+
   const dispatch = createEventDispatcher();
 
   async function createEmptySpace() {
diff --git a/explorer/src/lib/components/provisioners-card/ProvisionersCard.svelte b/explorer/src/lib/components/provisioners-card/ProvisionersCard.svelte
index 4f53db6e02..167afdabcf 100644
--- a/explorer/src/lib/components/provisioners-card/ProvisionersCard.svelte
+++ b/explorer/src/lib/components/provisioners-card/ProvisionersCard.svelte
@@ -55,7 +55,7 @@
       }}
 >
   {#if isSmallScreen}
-    <div class="provisioners-card__list">
+    <div class="data-card__list">
       {#each displayedProvisioner as provisioner (provisioner)}
         <ProvisionersList data={provisioner} displayTooltips={true} />
       {/each}
@@ -63,7 +63,7 @@
   {:else}
     <ProvisionersTable
       data={displayedProvisioner}
-      className="provisioners-card__table"
+      className="data-card__table"
     />
   {/if}
 </DataCard>
diff --git a/explorer/src/lib/components/token-detail-item/TokenDetailItem.css b/explorer/src/lib/components/token-detail-item/TokenDetailItem.css
new file mode 100644
index 0000000000..33537cf902
--- /dev/null
+++ b/explorer/src/lib/components/token-detail-item/TokenDetailItem.css
@@ -0,0 +1,24 @@
+.token-overview-panel__details-item {
+  flex: 1;
+  display: flex;
+  flex-direction: column;
+  justify-content: flex-start;
+  min-width: max-content;
+  margin: 0 1rem;
+}
+
+@media (min-width: 1024px) {
+  .token-overview-panel__details-item:not(:last-child) {
+    border-right: 1px solid var(--taupe-grey);
+  }
+}
+
+.token-overview-panel__details-item-title {
+  font-size: 1.125rem;
+  font-weight: 500;
+  margin-bottom: 0.3125rem;
+}
+
+.token-overview-panel__details-item-subtitle {
+  font-size: 0.875rem;
+}
diff --git a/explorer/src/lib/components/token-detail-item/TokenDetailItem.svelte b/explorer/src/lib/components/token-detail-item/TokenDetailItem.svelte
new file mode 100644
index 0000000000..19477d1a68
--- /dev/null
+++ b/explorer/src/lib/components/token-detail-item/TokenDetailItem.svelte
@@ -0,0 +1,11 @@
+<script>
+  import "./TokenDetailItem.css";
+
+  export let title;
+  export let subtitle;
+</script>
+
+<div class="token-overview-panel__details-item">
+  <p class="token-overview-panel__details-item-title">{title}</p>
+  <p class="token-overview-panel__details-item-subtitle">{subtitle}</p>
+</div>
diff --git a/explorer/src/lib/components/token-details-table/TokenDetailsTable.svelte b/explorer/src/lib/components/token-details-table/TokenDetailsTable.svelte
new file mode 100644
index 0000000000..b02ba1758d
--- /dev/null
+++ b/explorer/src/lib/components/token-details-table/TokenDetailsTable.svelte
@@ -0,0 +1,70 @@
+<svelte:options immutable={true} />
+
+<script>
+  import {
+    Table,
+    TableBody,
+    TableCell,
+    TableHead,
+    TableRow,
+  } from "$lib/components/table";
+  import { TransactionStatus, TransactionType } from "$lib/components";
+  import { makeClassName, middleEllipsis } from "$lib/dusk/string";
+
+  /** @type {string | undefined} */
+  export let className = undefined;
+
+  /** @type {Transaction[]} */
+  export let data;
+
+  /** @type {boolean} */
+  export let displayTooltips = false;
+
+  const HASH_CHARS_LENGTH = 10;
+
+  $: classes = makeClassName(["tokens-table", className]);
+</script>
+
+<Table className={classes}>
+  <TableHead>
+    <TableRow>
+      <TableCell type="th">From</TableCell>
+      <TableCell type="th">To</TableCell>
+      <TableCell type="th">ID</TableCell>
+      <TableCell type="th">Fee (Dusk)</TableCell>
+      <TableCell type="th">Status</TableCell>
+      <TableCell type="th">Type</TableCell>
+    </TableRow>
+  </TableHead>
+  <TableBody>
+    {#each data as transaction (transaction)}
+      <TableRow>
+        <TableCell
+          >{middleEllipsis(
+            transaction.from ? transaction.from : "",
+            HASH_CHARS_LENGTH
+          )}</TableCell
+        >
+        <TableCell
+          >{middleEllipsis(
+            transaction.to ? transaction.to : "",
+            HASH_CHARS_LENGTH
+          )}</TableCell
+        >
+        <TableCell
+          >{middleEllipsis(transaction.txid, HASH_CHARS_LENGTH)}</TableCell
+        >
+        <TableCell>{transaction.gasprice}</TableCell>
+        <TableCell>
+          <TransactionStatus
+            errorMessage={transaction.txerror}
+            showErrorTooltip={false}
+          />
+        </TableCell>
+        <TableCell>
+          <TransactionType data={transaction} {displayTooltips} />
+        </TableCell>
+      </TableRow>
+    {/each}
+  </TableBody>
+</Table>
diff --git a/explorer/src/lib/components/token-list-details/TokenListDetails.svelte b/explorer/src/lib/components/token-list-details/TokenListDetails.svelte
new file mode 100644
index 0000000000..bcdb7bf5aa
--- /dev/null
+++ b/explorer/src/lib/components/token-list-details/TokenListDetails.svelte
@@ -0,0 +1,107 @@
+<svelte:options immutable={true} />
+
+<script>
+  import { AppAnchor, DetailList, ListItem } from "$lib/components";
+  import { calculateAdaptiveCharCount, middleEllipsis } from "$lib/dusk/string";
+  import { onMount } from "svelte";
+
+  /** @type {Token} */
+  export let data;
+
+  /** @type {Boolean} */
+  export let displayTooltips = false;
+
+  /** @type {number} */
+  let screenWidth = window.innerWidth;
+
+  onMount(() => {
+    const resizeObserver = new ResizeObserver((entries) => {
+      const entry = entries[0];
+
+      screenWidth = entry.contentRect.width;
+    });
+
+    resizeObserver.observe(document.body);
+
+    return () => resizeObserver.disconnect();
+  });
+
+  $: adaptiveCharCount = calculateAdaptiveCharCount(
+    screenWidth,
+    320,
+    1024,
+    4,
+    25
+  );
+  $: tokensContractID = middleEllipsis(data.contractId, adaptiveCharCount);
+</script>
+
+<DetailList>
+  <!-- TOKEN NAME -->
+  <ListItem tooltipText={displayTooltips ? "The name of the token." : ""}>
+    <svelte:fragment slot="term">Token</svelte:fragment>
+    <svelte:fragment slot="definition">
+      <AppAnchor href={`/tokens/token/${data.token}`}>{data.token}</AppAnchor>
+    </svelte:fragment>
+  </ListItem>
+
+  <!-- TOTAL CURRENT SUPPLY -->
+  <ListItem
+    tooltipText={displayTooltips
+      ? "The total amount of tokens currently in circulation."
+      : ""}
+  >
+    <svelte:fragment slot="term">Total Current Supply</svelte:fragment>
+    <svelte:fragment slot="definition">
+      {data.totalCurrentSupply}
+    </svelte:fragment>
+  </ListItem>
+
+  <!-- MAX CIRCULATING SUPPLY -->
+  <ListItem
+    tooltipText={displayTooltips
+      ? "The maximum number of tokens that can ever exist."
+      : ""}
+  >
+    <svelte:fragment slot="term">Max Circulating Supply</svelte:fragment>
+    <svelte:fragment slot="definition">
+      {data.maxCirculatingSupply}
+    </svelte:fragment>
+  </ListItem>
+
+  <!-- TICKER SYMBOL -->
+  <ListItem
+    tooltipText={displayTooltips
+      ? "The ticker symbol used to identify the token."
+      : ""}
+  >
+    <svelte:fragment slot="term">Ticker</svelte:fragment>
+    <svelte:fragment slot="definition">
+      {data.ticker}
+    </svelte:fragment>
+  </ListItem>
+
+  <!-- CONTRACT ID -->
+  <ListItem
+    tooltipText={displayTooltips
+      ? "The unique contract address of the token on the blockchain."
+      : ""}
+  >
+    <svelte:fragment slot="term">Contract ID</svelte:fragment>
+    <svelte:fragment slot="definition">
+      {tokensContractID}
+    </svelte:fragment>
+  </ListItem>
+
+  <!-- PRICE -->
+  <ListItem
+    tooltipText={displayTooltips
+      ? "The current price of the token in USD."
+      : ""}
+  >
+    <svelte:fragment slot="term">Price</svelte:fragment>
+    <svelte:fragment slot="definition">
+      {data.price}
+    </svelte:fragment>
+  </ListItem>
+</DetailList>
diff --git a/explorer/src/lib/components/token-overview/TokenOverview.css b/explorer/src/lib/components/token-overview/TokenOverview.css
new file mode 100644
index 0000000000..256d72d99b
--- /dev/null
+++ b/explorer/src/lib/components/token-overview/TokenOverview.css
@@ -0,0 +1,56 @@
+.token-overview-panel {
+  display: flex;
+  padding: 1rem 1.375rem;
+  flex-direction: column;
+  row-gap: 0.75rem;
+  border-radius: 1.5rem;
+  background-color: var(--surface-color);
+  width: 100%;
+  text-transform: uppercase;
+  position: relative;
+  margin-bottom: 1.25rem;
+}
+
+.token-overview-panel__header {
+  display: flex;
+  gap: 1.25rem;
+  flex-direction: column;
+  align-items: flex-start;
+}
+
+@media (min-width: 992px) {
+  .token-overview-panel__header {
+    flex-direction: row;
+    align-items: center;
+  }
+}
+
+.token-overview-panel__token-name {
+  font-weight: 500;
+  font-size: 1.125em;
+  margin-bottom: 0.3125rem;
+}
+
+.token-overview-panel__token-address-subheader {
+  font-size: 1.125em;
+  color: var(--text-color-secondary);
+}
+
+.token-overview-panel__token-address {
+  font-weight: 500;
+}
+
+.token-overview-panel__separator {
+  border: none;
+  border-top: 1px solid var(--taupe-grey);
+  margin: 0.5rem 0;
+}
+
+.token-overview-panel__details {
+  display: flex;
+  flex-direction: row;
+  flex-wrap: wrap;
+  justify-content: space-between;
+  gap: 1rem;
+  width: 100%;
+}
diff --git a/explorer/src/lib/components/token-overview/TokenOverview.svelte b/explorer/src/lib/components/token-overview/TokenOverview.svelte
new file mode 100644
index 0000000000..43db48739f
--- /dev/null
+++ b/explorer/src/lib/components/token-overview/TokenOverview.svelte
@@ -0,0 +1,41 @@
+<script>
+  import { Icon } from "$lib/dusk/components";
+  import { TokenDetailItem } from "$lib/components";
+  import { calculateAdaptiveCharCount, middleEllipsis } from "$lib/dusk/string";
+
+  import "./TokenOverview.css";
+
+  export let data;
+  export let iconPath;
+  export let screenWidth;
+</script>
+
+<section class="token-overview-panel">
+  <div class="token-overview-panel__header">
+    <Icon path={iconPath} size="large" />
+    <div>
+      <h2 class="token-overview-panel__token-name">{data.token}</h2>
+      <p class="token-overview-panel__token-address-subheader">
+        Address:
+        <b class="token-overview-panel__token-address">
+          {middleEllipsis(
+            data.contractId,
+            calculateAdaptiveCharCount(screenWidth, 320, 1024, 4, 30)
+          )}
+        </b>
+      </p>
+    </div>
+  </div>
+  <hr class="token-overview-panel__separator" />
+  <div class="token-overview-panel__details">
+    <TokenDetailItem
+      title="{data.totalCurrentSupply} {data.ticker}"
+      subtitle="Current Total Supply"
+    />
+    <TokenDetailItem
+      title={data.maxCirculatingSupply}
+      subtitle="Max Circulating Supply"
+    />
+    <TokenDetailItem title="${data.price}" subtitle="Price" />
+  </div>
+</section>
diff --git a/explorer/src/lib/components/token-transactions-list/TokenTransactionsList.svelte b/explorer/src/lib/components/token-transactions-list/TokenTransactionsList.svelte
new file mode 100644
index 0000000000..9ffa1231e1
--- /dev/null
+++ b/explorer/src/lib/components/token-transactions-list/TokenTransactionsList.svelte
@@ -0,0 +1,132 @@
+<svelte:options immutable={true} />
+
+<script>
+  import {
+    AppAnchor,
+    DataGuard,
+    DetailList,
+    ListItem,
+    TransactionStatus,
+    TransactionType,
+  } from "$lib/components";
+  import { createValueFormatter } from "$lib/dusk/value";
+  import { calculateAdaptiveCharCount, middleEllipsis } from "$lib/dusk/string";
+  import { RelativeTime } from "$lib/dusk/components";
+  import { luxToDusk } from "$lib/dusk/currency";
+  import { onMount } from "svelte";
+
+  /** @type {boolean} */
+  export let autoRefreshTime = false;
+
+  /** @type {Transaction}*/
+  export let data;
+
+  /** @type {Boolean} */
+  export let displayTooltips = false;
+
+  /** @type {number} */
+  let screenWidth = window.innerWidth;
+
+  const formatter = createValueFormatter("en");
+
+  onMount(() => {
+    const resizeObserver = new ResizeObserver((entries) => {
+      const entry = entries[0];
+
+      screenWidth = entry.contentRect.width;
+    });
+
+    resizeObserver.observe(document.body);
+
+    return () => resizeObserver.disconnect();
+  });
+</script>
+
+<DetailList>
+  {#if data.from}
+    <ListItem
+      tooltipText={displayTooltips ? "The sender of the transactions" : ""}
+    >
+      <svelte:fragment slot="term">From</svelte:fragment>
+      <svelte:fragment slot="definition">
+        {middleEllipsis(
+          data.from,
+          calculateAdaptiveCharCount(screenWidth, 320, 1024, 4, 25)
+        )}
+      </svelte:fragment>
+    </ListItem>
+  {/if}
+  {#if data.to}
+    <ListItem
+      tooltipText={displayTooltips ? "The recipient of the transaction" : ""}
+    >
+      <svelte:fragment slot="term">To</svelte:fragment>
+      <svelte:fragment slot="definition">
+        {middleEllipsis(
+          data.to,
+          calculateAdaptiveCharCount(screenWidth, 320, 1024, 4, 25)
+        )}
+      </svelte:fragment>
+    </ListItem>
+  {/if}
+
+  <!-- TRANSACTION ID -->
+  <ListItem tooltipText={displayTooltips ? "The ID of the transaction" : ""}>
+    <svelte:fragment slot="term">ID</svelte:fragment>
+    <svelte:fragment slot="definition">
+      <AppAnchor
+        className="transaction-details__list-link"
+        href={`/transactions/transaction?id=${data.txid}`}
+        >{middleEllipsis(
+          data.txid,
+          calculateAdaptiveCharCount(screenWidth, 320, 1024, 4, 25)
+        )}</AppAnchor
+      >
+    </svelte:fragment>
+  </ListItem>
+
+  <!-- TIMESTAMP -->
+  <ListItem
+    tooltipText={displayTooltips
+      ? "Time elapsed since the transaction was created"
+      : ""}
+  >
+    <svelte:fragment slot="term">relative time</svelte:fragment>
+    <RelativeTime
+      autoRefresh={autoRefreshTime}
+      date={data.date}
+      className="transaction-details__list-timestamp"
+      slot="definition"
+    />
+  </ListItem>
+
+  <!-- TX FEE -->
+  <ListItem tooltipText={displayTooltips ? "The transaction fee amount" : ""}>
+    <svelte:fragment slot="term">Fee</svelte:fragment>
+    <svelte:fragment slot="definition">
+      {formatter(luxToDusk(data.feepaid))} DUSK
+    </svelte:fragment>
+  </ListItem>
+
+  <!-- STATUS -->
+  <ListItem tooltipText={displayTooltips ? "The transaction status" : ""}>
+    <svelte:fragment slot="term">Status</svelte:fragment>
+    <svelte:fragment slot="definition">
+      <TransactionStatus
+        className="explorer-badge"
+        errorMessage={data.txerror}
+        showErrorTooltip={autoRefreshTime}
+      />
+    </svelte:fragment>
+  </ListItem>
+
+  <!-- TYPE -->
+  <ListItem tooltipText={displayTooltips ? "The transaction type" : ""}>
+    <svelte:fragment slot="term">Type</svelte:fragment>
+    <svelte:fragment slot="definition"
+      ><DataGuard data={data.method && data.txtype}>
+        <TransactionType {data} {displayTooltips} />
+      </DataGuard></svelte:fragment
+    >
+  </ListItem>
+</DetailList>
diff --git a/explorer/src/lib/components/tokens-table/TokensTable.svelte b/explorer/src/lib/components/tokens-table/TokensTable.svelte
new file mode 100644
index 0000000000..7a77af08dc
--- /dev/null
+++ b/explorer/src/lib/components/tokens-table/TokensTable.svelte
@@ -0,0 +1,54 @@
+<svelte:options immutable={true} />
+
+<script>
+  import {
+    Table,
+    TableBody,
+    TableCell,
+    TableHead,
+    TableRow,
+  } from "$lib/components/table";
+  import { AppAnchor } from "$lib/components";
+  import { makeClassName, middleEllipsis } from "$lib/dusk/string";
+
+  /** @type {string | undefined} */
+  export let className = undefined;
+
+  /** @type {Token[]} */
+  export let data;
+
+  const HASH_CHARS_LENGTH = 10;
+
+  $: classes = makeClassName(["tokens-table", className]);
+</script>
+
+<Table className={classes}>
+  <TableHead>
+    <TableRow>
+      <TableCell type="th">Token</TableCell>
+      <TableCell type="th">Total Current Supply</TableCell>
+      <TableCell type="th">Max Circulating Supply</TableCell>
+      <TableCell type="th">Ticker</TableCell>
+      <TableCell type="th">Contract ID</TableCell>
+      <TableCell type="th">Price ($)</TableCell>
+    </TableRow>
+  </TableHead>
+  <TableBody>
+    {#each data as token (token)}
+      <TableRow>
+        <TableCell>
+          <AppAnchor href={`/tokens/token?name=${token.token}`}
+            >{token.token}</AppAnchor
+          ></TableCell
+        >
+        <TableCell>{token.totalCurrentSupply}</TableCell>
+        <TableCell>{token.maxCirculatingSupply}</TableCell>
+        <TableCell>{token.ticker}</TableCell>
+        <TableCell
+          >{middleEllipsis(token.contractId, HASH_CHARS_LENGTH)}</TableCell
+        >
+        <TableCell>{token.price}</TableCell>
+      </TableRow>
+    {/each}
+  </TableBody>
+</Table>
diff --git a/explorer/src/lib/components/transactions-card/TransactionsCard.css b/explorer/src/lib/components/transactions-card/TransactionsCard.css
deleted file mode 100644
index a74f796afc..0000000000
--- a/explorer/src/lib/components/transactions-card/TransactionsCard.css
+++ /dev/null
@@ -1,4 +0,0 @@
-.transactions-card__table,
-.transactions-card__list {
-  overflow-y: auto;
-}
diff --git a/explorer/src/lib/components/transactions-card/TransactionsCard.svelte b/explorer/src/lib/components/transactions-card/TransactionsCard.svelte
index 1e2440f135..4d0cb0b444 100644
--- a/explorer/src/lib/components/transactions-card/TransactionsCard.svelte
+++ b/explorer/src/lib/components/transactions-card/TransactionsCard.svelte
@@ -7,8 +7,6 @@
     TransactionsTable,
   } from "$lib/components";
 
-  import "./TransactionsCard.css";
-
   /** @type {Transaction[] | null}*/
   export let txns;
 
@@ -54,14 +52,13 @@
       }}
 >
   {#if isSmallScreen}
-    <div class="transactions-card__list">
+    <div class="data-card__list">
       {#each displayedTxns as txn (txn)}
         <TransactionsList data={txn} mode="full" />
       {/each}
     </div>
   {:else}
     <TransactionsTable
-      className="transactions-card__table"
       data={displayedTxns}
       displayTooltips={true}
       mode="full"
diff --git a/explorer/src/lib/mock-data/gql-token-transactions.json b/explorer/src/lib/mock-data/gql-token-transactions.json
new file mode 100644
index 0000000000..ff16e7966b
--- /dev/null
+++ b/explorer/src/lib/mock-data/gql-token-transactions.json
@@ -0,0 +1,342 @@
+[
+  {
+    "from": "0x7ab85e62a0bf6f0010d3cbac6e3dbb504d007251",
+    "to": "0x525647a539acb0a8371d212d5412e91ee01e8f1a",
+    "blockhash": "e34e5b790158ac9c0cf1c52a86e09438e7c28daba01b3785e129927c93f63086",
+    "blockheight": 6901,
+    "date": "2025-02-04T21:10:22.373891Z",
+    "feepaid": 0.003444,
+    "gaslimit": 42542814,
+    "gasprice": 77,
+    "gasspent": 8821395,
+    "memo": "",
+    "method": "transfer",
+    "success": true,
+    "txerror": "",
+    "txid": "6706996b9984b704048e915765c37389b1bceb84381d9dc8288340982d144117",
+    "txtype": "Phoenix"
+  },
+  {
+    "from": "0x8c367a21f7f06189cce49edb924e2e42c0d5bca0",
+    "to": "0x21f3f68853668d3ec8f9d6032788a4a3458599e4",
+    "blockhash": "d9ffcb7ac66b38c5d0371534463abedec7b2c093daf31b78aab10628f67318d4",
+    "blockheight": 6413,
+    "date": "2025-02-04T21:11:22.373891Z",
+    "feepaid": 0.004467,
+    "gaslimit": 50683489,
+    "gasprice": 42,
+    "gasspent": 8701776,
+    "memo": "Mock transaction",
+    "method": "transfer",
+    "success": true,
+    "txerror": "",
+    "txid": "b84da884b05ef2e051e857364764c922223c5b4821bdb590f79e7ca14e5f99b9",
+    "txtype": "Phoenix"
+  },
+  {
+    "from": "0xc8758dc9aeee7dca0bd2d5c8dfc413ee93584d38",
+    "to": "0x3ca7359b84916e8889d0bcf0db5c381c49e05c4a",
+    "blockhash": "25226fde83f64a5789c0f4b8babaaa58b429f529fd0f00b0f124916a358c6b90",
+    "blockheight": 6413,
+    "date": "2025-02-04T21:12:22.373891Z",
+    "feepaid": 0.002862,
+    "gaslimit": 43732823,
+    "gasprice": 22,
+    "gasspent": 8895516,
+    "memo": "",
+    "method": "swap",
+    "success": false,
+    "txerror": "Out of gas",
+    "txid": "430e0f3cdda30f729e2637c32b2152950aeac9203a0b2ff7fc6c946557c48a9b",
+    "txtype": "Phoenix"
+  },
+  {
+    "from": "0xb043ef8e502c5883fcf92be17ddcb4c1533b333f",
+    "to": "0x968c95bbd2ed8ada0b342903ea5e97bfdc2fefdf",
+    "blockhash": "06a980d5640a69ad0f4d2a07ca7d32cbabb81d2bcbb9504f6940ac27eca00603",
+    "blockheight": 6744,
+    "date": "2025-02-04T21:13:22.373891Z",
+    "feepaid": 0.000661,
+    "gaslimit": 46497961,
+    "gasprice": 4,
+    "gasspent": 8525644,
+    "memo": "",
+    "method": "contractCall",
+    "success": false,
+    "txerror": "Out of gas",
+    "txid": "704e946556e6541cb33afd10599e8e7b082adf240b712c1f2ca67b1aaf0a6eae",
+    "txtype": "Phoenix"
+  },
+  {
+    "from": "0xb58b5aee843f29c2d4d57732aa7bdfc0a76e6ced",
+    "to": "0x0b9f95dc70d3a16f9134946569c9b05a1892e6cc",
+    "blockhash": "3889232fb664881c8889d68a51f83121ef85fe3e1a3d8a9545ed256a1ea1a330",
+    "blockheight": 6092,
+    "date": "2025-02-04T21:14:22.373891Z",
+    "feepaid": 0.000556,
+    "gaslimit": 49491251,
+    "gasprice": 56,
+    "gasspent": 8458173,
+    "memo": "",
+    "method": "stake",
+    "success": true,
+    "txerror": "",
+    "txid": "4d61503201c74835f26996084ec69c59d3dd0116bce18b454c92668c91ae2ea6",
+    "txtype": "Moonlight"
+  },
+  {
+    "from": "0xdb002f43e3ff2170991d039e0be0a7847085d619",
+    "to": "0xd80c0ef4d5da1c0ce0c3ebcb0b890ef5b63446bd",
+    "blockhash": "c2711dd4045427c3c8bfb71bc29146a5c9af2afdbe7b9438c2e62613fe553bc1",
+    "blockheight": 6324,
+    "date": "2025-02-04T21:15:22.373891Z",
+    "feepaid": 0.00102,
+    "gaslimit": 54489684,
+    "gasprice": 99,
+    "gasspent": 8777099,
+    "memo": "",
+    "method": "contractCall",
+    "success": true,
+    "txerror": "",
+    "txid": "c5fd9cecc49a72d7be3bf94777bde9346c54fa34451f96f7a0adffb76b3101c3",
+    "txtype": "Moonlight"
+  },
+  {
+    "from": "0x1a8bc7cc615ef12483b1cdfe7041446acd0a2739",
+    "to": "0xd4d9c9d1b00649fcf329634c38b237fda977dff4",
+    "blockhash": "ababd8b75a935930b1234b19f764920480c1d349e080cdec41ca58d45ddb59cf",
+    "blockheight": 6627,
+    "date": "2025-02-04T21:16:22.373891Z",
+    "feepaid": 0.00194,
+    "gaslimit": 49336296,
+    "gasprice": 12,
+    "gasspent": 8570851,
+    "memo": "",
+    "method": "swap",
+    "success": true,
+    "txerror": "",
+    "txid": "6792f3ad9de7008f32ef18c41a9e920cf4e4dcd12a3f310444a3894054b7fb84",
+    "txtype": "Phoenix"
+  },
+  {
+    "from": "0xc134a54c4120db46ba2e570bbae1349bcb429312",
+    "to": "0x0aad0f2dc83c810ce13dcb8660afb46f3c3b8fc9",
+    "blockhash": "3a87ac863bd53c4e1dfdcd6ecd592e1bfc05011fda2de5c7cc69fd100d2e5c05",
+    "blockheight": 6765,
+    "date": "2025-02-04T21:17:22.373891Z",
+    "feepaid": 0.002848,
+    "gaslimit": 53561274,
+    "gasprice": 70,
+    "gasspent": 8523728,
+    "memo": "Mock transaction",
+    "method": "transfer",
+    "success": false,
+    "txerror": "Out of gas",
+    "txid": "38359558762af6cd8339d1cddbf2a84e34f87393c32f8f449b2944ede1364e3f",
+    "txtype": "Phoenix"
+  },
+  {
+    "from": "0x96c5b5e79f8b8633451558d4a731894d53ab81e7",
+    "to": "0x045fbb44b85f4350ce6d486fb774849705d4d735",
+    "blockhash": "1da9637818b86541ac64631aa02f55ce0315b86d1493f9a5c333a0d8fa0fc25c",
+    "blockheight": 6855,
+    "date": "2025-02-04T21:18:22.373891Z",
+    "feepaid": 0.002643,
+    "gaslimit": 59163243,
+    "gasprice": 10,
+    "gasspent": 8277123,
+    "memo": "",
+    "method": "swap",
+    "success": false,
+    "txerror": "Out of gas",
+    "txid": "24fe8bbeb1449952cc766153fc8dac8d750871ccc77bfb84e274e97b5c7175c9",
+    "txtype": "Phoenix"
+  },
+  {
+    "from": "0x31fa849ec41353504df67d59555f6cbe4ea7aece",
+    "to": "0xe8f9e2121c53a4a404cd9398bf796266faabaceb",
+    "blockhash": "2aa052d493546c12c34ccfdd563111692249f2a7c7392a47fdb86bae9af37a9b",
+    "blockheight": 6666,
+    "date": "2025-02-04T21:19:22.373891Z",
+    "feepaid": 0.000602,
+    "gaslimit": 47095881,
+    "gasprice": 55,
+    "gasspent": 8943790,
+    "memo": "Mock transaction",
+    "method": "swap",
+    "success": false,
+    "txerror": "Out of gas",
+    "txid": "eba3c9f1210e47ca59dea09e28fcae6cb630729ea140ce6c7a337315239fdf18",
+    "txtype": "Moonlight"
+  },
+  {
+    "from": "0xd6d9deacde6553dde57972c969e9661a4dc0e503",
+    "to": "0xb6b5f138dd456265f601c6e1edb3c929078ca65c",
+    "blockhash": "764f14a8dd7de38c88743ac8bb0b16dd56caa59ffae99df0e39349bac52613a9",
+    "blockheight": 6609,
+    "date": "2025-02-04T21:20:22.373891Z",
+    "feepaid": 0.001905,
+    "gaslimit": 44394939,
+    "gasprice": 86,
+    "gasspent": 8535794,
+    "memo": "",
+    "method": "swap",
+    "success": false,
+    "txerror": "Out of gas",
+    "txid": "9b8b2fa3252d76d9a4e1603d3e327bf15d0bdd69c56720c6e8e0d8dec2791de1",
+    "txtype": "Phoenix"
+  },
+  {
+    "from": "0xf509d29122dec180b6f92488bbc4d681ad330acd",
+    "to": "0x0d517c1021042eadc0f24cb47d20bed8ad5e20c8",
+    "blockhash": "33fa79679ae3d97a3c0480a688ecc7281c7d2034b4b795164d66fd49f425e8e6",
+    "blockheight": 6452,
+    "date": "2025-02-04T21:21:22.373891Z",
+    "feepaid": 0.004348,
+    "gaslimit": 56947207,
+    "gasprice": 96,
+    "gasspent": 8363212,
+    "memo": "Mock transaction",
+    "method": "swap",
+    "success": false,
+    "txerror": "Out of gas",
+    "txid": "6a422818dfe21e1767dda3aa715a471d35254567d8f52cbaf1af5f2329dbd1df",
+    "txtype": "Phoenix"
+  },
+  {
+    "from": "0x773cc3210e83d86611056709d7a661865596d890",
+    "to": "0xe54a1a12882f849443297c41449d96579f941a35",
+    "blockhash": "21a783b6548d7cf2f3616c8ab22aab20cc0495e300ba32ff04553c2e066637a7",
+    "blockheight": 6997,
+    "date": "2025-02-04T21:22:22.373891Z",
+    "feepaid": 0.004695,
+    "gaslimit": 40303794,
+    "gasprice": 48,
+    "gasspent": 8409866,
+    "memo": "",
+    "method": "swap",
+    "success": false,
+    "txerror": "Out of gas",
+    "txid": "224709caba957308b162a75ce8b75eb47a958b30c2706bbae6312be893d586ef",
+    "txtype": "Phoenix"
+  },
+  {
+    "from": "0x6d6747f5e1b7453005c8db3fee6f382cca0b85b3",
+    "to": "0x5bb30392a743b4b0876da74f8d2c9e933ba95dd3",
+    "blockhash": "5bb5130020b7139022709647d79acbb7060a5dacb4fbac7138415c85e73d4c2f",
+    "blockheight": 6731,
+    "date": "2025-02-04T21:23:22.373891Z",
+    "feepaid": 0.004159,
+    "gaslimit": 51711058,
+    "gasprice": 52,
+    "gasspent": 8321942,
+    "memo": "",
+    "method": "transfer",
+    "success": false,
+    "txerror": "Out of gas",
+    "txid": "5bd7fe82f6e9d9183de05b7d569ab552983d71a2bd8a211098a20fd2c39170b3",
+    "txtype": "Moonlight"
+  },
+  {
+    "from": "0x1f2586890205b7ef9e609fcd075c6e23b01e2f1b",
+    "to": "0xaa5d853ce5924f16d1cd31155454c608956e07d5",
+    "blockhash": "29f0b1959f07c00282df351b17024b9de0818bc2b812b563b5fa40e059c0790c",
+    "blockheight": 6941,
+    "date": "2025-02-04T21:24:22.373891Z",
+    "feepaid": 0.003257,
+    "gaslimit": 46838518,
+    "gasprice": 74,
+    "gasspent": 8217953,
+    "memo": "",
+    "method": "stake",
+    "success": false,
+    "txerror": "Out of gas",
+    "txid": "c63512658663b8f7a57871430eb723cc27344ebebbf791aafce6d169fa0de486",
+    "txtype": "Starlight"
+  },
+  {
+    "from": "0x9c5878303ac60e0bf8da235d91a60654322670a8",
+    "to": "0xd9478996a491e529fbc8bc5028bd53c743017276",
+    "blockhash": "fb1cec29a76fcec89ccfc51efac54b2827987c6a564b67fb504da4757fb1b3e9",
+    "blockheight": 6389,
+    "date": "2025-02-04T21:25:22.373891Z",
+    "feepaid": 0.001288,
+    "gaslimit": 41856498,
+    "gasprice": 58,
+    "gasspent": 8883919,
+    "memo": "Mock transaction",
+    "method": "swap",
+    "success": true,
+    "txerror": "",
+    "txid": "aee203eeaf6262bb765d99f6c7031ec740a21e66c65960daf163913ee92a4c5f",
+    "txtype": "Moonlight"
+  },
+  {
+    "from": "0x42fed88884d246d3da4829ea2dfc577e6e246813",
+    "to": "0x3f6ced7537bd15eb7ac2ced5a19707d3745d1423",
+    "blockhash": "d280d322bf6d8b4a601876997562755ec2c179bc60d121c9749c6ff7741afd5d",
+    "blockheight": 6148,
+    "date": "2025-02-04T21:26:22.373891Z",
+    "feepaid": 0.002895,
+    "gaslimit": 40329493,
+    "gasprice": 87,
+    "gasspent": 8148241,
+    "memo": "",
+    "method": "transfer",
+    "success": true,
+    "txerror": "",
+    "txid": "271010c92a2b6779f2016d2cba45395b87b1d8883d2a1684f1162ed0afbffe9e",
+    "txtype": "Moonlight"
+  },
+  {
+    "from": "0xddea1b938286128b69a2eddc02c161d28e5191f7",
+    "to": "0xbd55f49bc47b8eb27c7a0e3ef1e04456445f13bc",
+    "blockhash": "0edc50d6c48b2e06667f5c59861682de36df9c568488a87cbcb0230c6db113e8",
+    "blockheight": 6212,
+    "date": "2025-02-04T21:27:22.373891Z",
+    "feepaid": 0.001956,
+    "gaslimit": 44839470,
+    "gasprice": 32,
+    "gasspent": 8407905,
+    "memo": "",
+    "method": "transfer",
+    "success": false,
+    "txerror": "Out of gas",
+    "txid": "fb6a9998631293c68f6f897b7cb1edd29f152b9927775de5be77c30281c59549",
+    "txtype": "Moonlight"
+  },
+  {
+    "from": "0xc79c4932b82c59f3c0ccbf48f53f6860f1c00724",
+    "to": "0x5ac786685e6bd6f7867bf7f8998831de0f079ad4",
+    "blockhash": "077ebd107d2f9e05e735480c7ea7cf2c944f43f03f76ecfd949431ba15255d5c",
+    "blockheight": 6929,
+    "date": "2025-02-04T21:28:22.373891Z",
+    "feepaid": 0.001711,
+    "gaslimit": 45063818,
+    "gasprice": 30,
+    "gasspent": 8839895,
+    "memo": "",
+    "method": "stake",
+    "success": false,
+    "txerror": "",
+    "txid": "bdc4cb7ef9248c92ee6acb9b6f5849a33faf8e029704ebc1932a5e04a64424e8",
+    "txtype": "Moonlight"
+  },
+  {
+    "from": "0x6e2099b735b30931f70148ba5715fd3644f78087",
+    "to": "0x1021c7aa45ff7e747b2da07ae3e844a769c7bfeb",
+    "blockhash": "d24c6ccbe493cf6c7916390efc8dc40f30ab262cc01f55b55dffe30b932f1d98",
+    "blockheight": 6563,
+    "date": "2025-02-04T21:29:22.373891Z",
+    "feepaid": 0.000702,
+    "gaslimit": 42038194,
+    "gasprice": 51,
+    "gasspent": 8549373,
+    "memo": "Mock transaction",
+    "method": "swap",
+    "success": true,
+    "txerror": "Out of gas",
+    "txid": "6d66f18d564d87a183b97da902863a64ce9dbfb14d27db3fb8cc34932cdb1e91",
+    "txtype": "Moonlight"
+  }
+]
diff --git a/explorer/src/lib/mock-data/index.js b/explorer/src/lib/mock-data/index.js
index 3b0a7c9c4c..aaba3dc098 100644
--- a/explorer/src/lib/mock-data/index.js
+++ b/explorer/src/lib/mock-data/index.js
@@ -12,3 +12,6 @@ export { default as gqlTransactionDetails } from "./gql-transaction-details.json
 export { default as gqlTransactions } from "./gql-transactions.json";
 export { default as hostProvisioners } from "./host-provisioners.json";
 export { default as nodeLocationsCount } from "./node-locations.count.json";
+
+export { default as tokens } from "./tokens.json";
+export { default as gqlTokenTransactions } from "./gql-token-transactions.json";
diff --git a/explorer/src/lib/mock-data/token.d.ts b/explorer/src/lib/mock-data/token.d.ts
new file mode 100644
index 0000000000..bc5a4657b5
--- /dev/null
+++ b/explorer/src/lib/mock-data/token.d.ts
@@ -0,0 +1,8 @@
+type Token = {
+  token: string;
+  totalCurrentSupply: string;
+  maxCirculatingSupply: string;
+  ticker: string;
+  contractId: string;
+  price: string;
+};
diff --git a/explorer/src/lib/mock-data/tokens.json b/explorer/src/lib/mock-data/tokens.json
new file mode 100644
index 0000000000..c4f2329ca5
--- /dev/null
+++ b/explorer/src/lib/mock-data/tokens.json
@@ -0,0 +1,66 @@
+[
+  {
+    "token": "Ethereum",
+    "totalCurrentSupply": "120,000,000",
+    "maxCirculatingSupply": "∞",
+    "ticker": "ETH",
+    "contractId": "0x123abc456def789ghi123jkl456mno789pqr012stu345vwx678yz",
+    "price": "3,200.50"
+  },
+  {
+    "token": "Bitcoin",
+    "totalCurrentSupply": "19,500,000",
+    "maxCirculatingSupply": "21,000,000",
+    "ticker": "BTC",
+    "contractId": "N/A",
+    "price": "42,500.75"
+  },
+  {
+    "token": "Solana",
+    "totalCurrentSupply": "550,000,000",
+    "maxCirculatingSupply": "∞",
+    "ticker": "SOL",
+    "contractId": "0x789ghi123jkl456mno789pqr012stu345vwx678yzabc123def456",
+    "price": "150.25"
+  },
+  {
+    "token": "USDC",
+    "totalCurrentSupply": "32,000,000,000",
+    "maxCirculatingSupply": "∞",
+    "ticker": "USDC",
+    "contractId": "0x987xyz654mno321stu456vwx789pqr012abc345def678ghi123jkl",
+    "price": "1.00"
+  },
+  {
+    "token": "Polygon",
+    "totalCurrentSupply": "9,300,000,000",
+    "maxCirculatingSupply": "10,000,000,000",
+    "ticker": "MATIC",
+    "contractId": "0x456uvw321rst852zxc147bnd963qwe258lop357mnb741poi963lkj",
+    "price": "1.75"
+  },
+  {
+    "token": "Dai",
+    "totalCurrentSupply": "5,000,000,000",
+    "maxCirculatingSupply": "∞",
+    "ticker": "DAI",
+    "contractId": "0x321abc654def789ghi123jkl456mno789pqr012stu345vwx678yz",
+    "price": "1.00"
+  },
+  {
+    "token": "Chainlink",
+    "totalCurrentSupply": "467,000,000",
+    "maxCirculatingSupply": "1,000,000,000",
+    "ticker": "LINK",
+    "contractId": "0x963qwe852rty147bnm258lop357mnb741poi963lkj654def789ghi",
+    "price": "17.60"
+  },
+  {
+    "token": "Avalanche",
+    "totalCurrentSupply": "377,000,000",
+    "maxCirculatingSupply": "720,000,000",
+    "ticker": "AVAX",
+    "contractId": "0x852zxc147bnd963qwe258lop357mnb741poi963lkj321rst456uvw",
+    "price": "35.40"
+  }
+]
diff --git a/explorer/src/routes/+page.svelte b/explorer/src/routes/+page.svelte
index c818935bc7..5b77864d63 100644
--- a/explorer/src/routes/+page.svelte
+++ b/explorer/src/routes/+page.svelte
@@ -35,9 +35,9 @@
 </script>
 
 <section class="landing">
-  <div class="chain-info">
+  <article class="chain-info">
     <StatisticsPanel />
-  </div>
+  </article>
 
   <div class="tables">
     <LatestBlocksCard
diff --git a/explorer/src/routes/tokens/+layout.js b/explorer/src/routes/tokens/+layout.js
new file mode 100644
index 0000000000..51520ef65d
--- /dev/null
+++ b/explorer/src/routes/tokens/+layout.js
@@ -0,0 +1,9 @@
+import { redirect } from "@sveltejs/kit";
+
+export const load = () => {
+  const featureTokensEnabled = import.meta.env.VITE_FEATURE_TOKENS === "true";
+
+  if (!featureTokensEnabled) {
+    throw redirect(302, "/");
+  }
+};
diff --git a/explorer/src/routes/tokens/+page.svelte b/explorer/src/routes/tokens/+page.svelte
new file mode 100644
index 0000000000..a9037e5dbc
--- /dev/null
+++ b/explorer/src/routes/tokens/+page.svelte
@@ -0,0 +1,50 @@
+<script>
+  import { DataCard, TokenListDetails, TokensTable } from "$lib/components";
+  import { appStore } from "$lib/stores";
+  import { tokens } from "$lib/mock-data";
+
+  const ITEMS_TO_DISPLAY = import.meta.env.VITE_CHAIN_INFO_ENTRIES;
+  let itemsToDisplay = ITEMS_TO_DISPLAY;
+
+  const error = null;
+  const loading = false;
+
+  const loadMoreItems = () => {
+    if (tokens && itemsToDisplay < tokens.length) {
+      itemsToDisplay += ITEMS_TO_DISPLAY;
+    }
+  };
+
+  $: ({ isSmallScreen } = $appStore);
+
+  $: displayedTokens = tokens ? tokens.slice(0, itemsToDisplay) : [];
+  $: isLoadMoreDisabled =
+    (tokens && itemsToDisplay >= tokens.length) || (loading && tokens === null);
+</script>
+
+<section>
+  <DataCard
+    on:retry
+    data={displayedTokens}
+    {error}
+    {loading}
+    title="Tokens — {displayedTokens.length} Displayed Items"
+    headerButtonDetails={error
+      ? undefined
+      : {
+          action: () => loadMoreItems(),
+          disabled: isLoadMoreDisabled,
+          label: "Show More",
+        }}
+  >
+    {#if isSmallScreen}
+      <div class="data-card__list">
+        {#each displayedTokens as token (token)}
+          <TokenListDetails data={token} />
+        {/each}
+      </div>
+    {:else}
+      <TokensTable data={tokens} />
+    {/if}
+  </DataCard>
+</section>
diff --git a/explorer/src/routes/tokens/token/+page.svelte b/explorer/src/routes/tokens/token/+page.svelte
new file mode 100644
index 0000000000..6303cdee1b
--- /dev/null
+++ b/explorer/src/routes/tokens/token/+page.svelte
@@ -0,0 +1,95 @@
+<script>
+  import { onMount } from "svelte";
+  import { page } from "$app/stores";
+  import { duskIcon } from "$lib/dusk/icons";
+  import { appStore } from "$lib/stores";
+  import {
+    DataCard,
+    TokenDetailsTable,
+    TokenOverview,
+    TokenTransactionsList,
+  } from "$lib/components";
+  import { gqlTokenTransactions, tokens } from "$lib/mock-data";
+
+  const ITEMS_TO_DISPLAY = import.meta.env.VITE_CHAIN_INFO_ENTRIES;
+  let itemsToDisplay = ITEMS_TO_DISPLAY;
+
+  const url = new URL($page.url);
+  const tokenName = url.searchParams.get("name");
+  const tokenData = tokens.find((token) => token.token === tokenName);
+
+  const transactions = gqlTokenTransactions.map((transaction) => {
+    return {
+      ...transaction,
+      date: new Date(transaction.date),
+    };
+  });
+
+  const error = null;
+  const loading = false;
+
+  const loadMoreItems = () => {
+    if (transactions && itemsToDisplay < transactions.length) {
+      itemsToDisplay += ITEMS_TO_DISPLAY;
+    }
+  };
+
+  $: ({ isSmallScreen } = $appStore);
+
+  /** @type {number} */
+  let screenWidth = window.innerWidth;
+
+  /** @type {Transaction[]}*/
+  let displayedTxns;
+  $: displayedTxns = transactions ? transactions.slice(0, itemsToDisplay) : [];
+
+  $: isLoadMoreDisabled =
+    (transactions && itemsToDisplay >= transactions.length) ||
+    (loading && transactions === null);
+
+  onMount(() => {
+    const resizeObserver = new ResizeObserver((entries) => {
+      const entry = entries[0];
+
+      screenWidth = entry.contentRect.width;
+    });
+
+    resizeObserver.observe(document.body);
+
+    return () => resizeObserver.disconnect();
+  });
+</script>
+
+<section>
+  {#if tokenData}
+    <article>
+      <TokenOverview {screenWidth} iconPath={duskIcon} data={tokenData} />
+    </article>
+    <DataCard
+      on:retry
+      data={transactions}
+      {error}
+      {loading}
+      title="{tokenData?.token.toUpperCase()} Transactions — {displayedTxns.length} Displayed Items"
+      headerButtonDetails={error
+        ? undefined
+        : {
+            action: () => loadMoreItems(),
+            disabled: isLoadMoreDisabled,
+            label: "Show More",
+          }}
+    >
+      {#if isSmallScreen}
+        <div class="data-card__list">
+          {#each displayedTxns as txn (txn)}
+            <TokenTransactionsList data={txn} />
+          {/each}
+        </div>
+      {:else}
+        <TokenDetailsTable data={displayedTxns} />
+      {/if}
+    </DataCard>
+  {:else}
+    <p>Token not found</p>
+  {/if}
+</section>
diff --git a/explorer/vite.config.js b/explorer/vite.config.js
index 87bbe102de..0210486541 100644
--- a/explorer/vite.config.js
+++ b/explorer/vite.config.js
@@ -27,6 +27,7 @@ export default defineConfig(({ mode }) => {
         API_ENDPOINT: env.VITE_API_ENDPOINT,
         VITE_BLOCKS_LIST_ENTRIES: env.VITE_BLOCKS_LIST_ENTRIES,
         VITE_CHAIN_INFO_ENTRIES: env.VITE_CHAIN_INFO_ENTRIES,
+        VITE_FEATURE_TOKENS: env.VITE_FEATURE_TOKENS,
         VITE_MARKET_DATA_REFETCH_INTERVAL:
           env.VITE_MARKET_DATA_REFETCH_INTERVAL,
         VITE_NODE_URL: env.VITE_NODE_URL,
@@ -59,6 +60,7 @@ export default defineConfig(({ mode }) => {
         VITE_API_ENDPOINT: "https://api.dusk.network/v1",
         VITE_BLOCKS_LIST_ENTRIES: "100",
         VITE_CHAIN_INFO_ENTRIES: "15",
+        VITE_FEATURE_TOKENS: "true",
         VITE_MARKET_DATA_REFETCH_INTERVAL: "120000",
         VITE_NODE_URL: "https://nodes.dusk.network",
         VITE_PROVISIONERS_REFETCH_INTERVAL: "30000",


Test patch:
diff --git a/explorer/src/lib/chain-info/__tests__/transformBlock.spec.js b/explorer/src/lib/chain-info/__tests__/transformBlock.spec.js
index a9440f3a30..cd15e216d0 100644
--- a/explorer/src/lib/chain-info/__tests__/transformBlock.spec.js
+++ b/explorer/src/lib/chain-info/__tests__/transformBlock.spec.js
@@ -30,12 +30,14 @@ describe("transformBlock", () => {
           blockheight: 495868,
           date: new Date(blockData.transactions[0].blockTimestamp * 1000),
           feepaid: 290866,
+          from: undefined,
           gaslimit: 500000000,
           gasprice: 1,
           gasspent: 290866,
           memo: blockData.transactions[0].tx.memo,
           method: "transfer",
           success: true,
+          to: undefined,
           txerror: "",
           txid: "3a3f6f90a1012ae751b4448bcb8e98def0ba2b18170239bd69fcf8e2e37f0602",
           txtype: "Moonlight",
@@ -46,12 +48,14 @@ describe("transformBlock", () => {
           blockheight: 495868,
           date: new Date(blockData.transactions[1].blockTimestamp * 1000),
           feepaid: 289852,
+          from: undefined,
           gaslimit: 500000000,
           gasprice: 1,
           gasspent: 289852,
           memo: blockData.transactions[1].tx.memo,
           method: "transfer",
           success: true,
+          to: undefined,
           txerror: "",
           txid: "07bfabea1d94c16f2dc3697fa642f6cecea6e81bf76b9644efbb6e2723b76d00",
           txtype: "Phoenix",
diff --git a/explorer/src/lib/chain-info/__tests__/transformTransaction.spec.js b/explorer/src/lib/chain-info/__tests__/transformTransaction.spec.js
index 9c5c185c98..c5bc250f72 100644
--- a/explorer/src/lib/chain-info/__tests__/transformTransaction.spec.js
+++ b/explorer/src/lib/chain-info/__tests__/transformTransaction.spec.js
@@ -12,12 +12,14 @@ describe("transformTransaction", () => {
     blockheight: 487166,
     date: new Date(txData.blockTimestamp * 1000),
     feepaid: 290766,
+    from: undefined,
     gaslimit: 500000000,
     gasprice: 1,
     gasspent: 290766,
     memo: gqlTransaction.tx.tx.memo,
     method: "transfer",
     success: true,
+    to: undefined,
     txerror: "",
     txid: "4877687c2dbf154248d3ddee9ba0d81e3431f39056f82a46819da041d4ac0e04",
     txtype: "Moonlight",
diff --git a/explorer/src/lib/components/__tests__/__snapshots__/BlocksCard.spec.js.snap b/explorer/src/lib/components/__tests__/__snapshots__/BlocksCard.spec.js.snap
index 6c588edde8..5d15072faa 100644
--- a/explorer/src/lib/components/__tests__/__snapshots__/BlocksCard.spec.js.snap
+++ b/explorer/src/lib/components/__tests__/__snapshots__/BlocksCard.spec.js.snap
@@ -31,7 +31,7 @@ exports[`Blocks Card > should disable the \`Show More\` button if there is no mo
     class="data-card__content"
   >
     <div
-      class="table-container blocks-table blocks-card__table"
+      class="table-container blocks-table data-card__table"
     >
       <table
         class="table"
diff --git a/explorer/src/lib/components/__tests__/__snapshots__/Navbar.spec.js.snap b/explorer/src/lib/components/__tests__/__snapshots__/Navbar.spec.js.snap
index 8bb3b74c9f..a40fe14dc3 100644
--- a/explorer/src/lib/components/__tests__/__snapshots__/Navbar.spec.js.snap
+++ b/explorer/src/lib/components/__tests__/__snapshots__/Navbar.spec.js.snap
@@ -78,6 +78,13 @@ exports[`Navbar > renders the Navbar component 1`] = `
       >
         Provisioners
       </a>
+      
+      <a
+        class="dusk-anchor dusk-anchor--off-surface dusk-nav-list__link"
+        href="/some-base-path/tokens"
+      >
+        Tokens
+      </a>
     </nav>
     
      
diff --git a/explorer/src/lib/components/__tests__/__snapshots__/ProvisionersCard.spec.js.snap b/explorer/src/lib/components/__tests__/__snapshots__/ProvisionersCard.spec.js.snap
index 4b52ce1989..3d671c2e8d 100644
--- a/explorer/src/lib/components/__tests__/__snapshots__/ProvisionersCard.spec.js.snap
+++ b/explorer/src/lib/components/__tests__/__snapshots__/ProvisionersCard.spec.js.snap
@@ -31,7 +31,7 @@ exports[`Provisioners Card > should disable the \`Show More\` button if there is
     class="data-card__content"
   >
     <div
-      class="table-container provisioners-table provisioners-card__table"
+      class="table-container provisioners-table data-card__table"
     >
       <table
         class="table"
diff --git a/explorer/src/lib/components/__tests__/__snapshots__/TransactionsCard.spec.js.snap b/explorer/src/lib/components/__tests__/__snapshots__/TransactionsCard.spec.js.snap
index 1cc0bc877d..d2e2a166a4 100644
--- a/explorer/src/lib/components/__tests__/__snapshots__/TransactionsCard.spec.js.snap
+++ b/explorer/src/lib/components/__tests__/__snapshots__/TransactionsCard.spec.js.snap
@@ -72,7 +72,7 @@ exports[`Transactions Card > should disable the \`Show More\` button if there is
     class="data-card__content"
   >
     <div
-      class="table-container transactions-table transactions-card__table"
+      class="table-container transactions-table"
     >
       <table
         class="table"
diff --git a/explorer/src/routes/__tests__/__snapshots__/layout.spec.js.snap b/explorer/src/routes/__tests__/__snapshots__/layout.spec.js.snap
index 53364f5883..5dbafe9482 100644
--- a/explorer/src/routes/__tests__/__snapshots__/layout.spec.js.snap
+++ b/explorer/src/routes/__tests__/__snapshots__/layout.spec.js.snap
@@ -80,6 +80,13 @@ exports[`Main layout > should render the app's main layout 1`] = `
           >
             Provisioners
           </a>
+          
+          <a
+            class="dusk-anchor dusk-anchor--off-surface dusk-nav-list__link"
+            href="/some-base-path/tokens"
+          >
+            Tokens
+          </a>
         </nav>
         
          
diff --git a/explorer/src/routes/__tests__/__snapshots__/page.spec.js.snap b/explorer/src/routes/__tests__/__snapshots__/page.spec.js.snap
index 368960b2e0..483036db91 100644
--- a/explorer/src/routes/__tests__/__snapshots__/page.spec.js.snap
+++ b/explorer/src/routes/__tests__/__snapshots__/page.spec.js.snap
@@ -4,7 +4,7 @@ exports[`home page > should render the home page, start polling for the latest c
 <section
   class="landing"
 >
-  <div
+  <article
     class="chain-info"
   >
     <div
@@ -388,7 +388,7 @@ exports[`home page > should render the home page, start polling for the latest c
         </div>
       </div>
     </div>
-  </div>
+  </article>
    
   <div
     class="tables"
@@ -1428,7 +1428,7 @@ exports[`home page > should render the home page, start polling for the latest c
 <section
   class="landing"
 >
-  <div
+  <article
     class="chain-info"
   >
     <div
@@ -1810,7 +1810,7 @@ exports[`home page > should render the home page, start polling for the latest c
         </div>
       </div>
     </div>
-  </div>
+  </article>
    
   <div
     class="tables"
diff --git a/explorer/src/routes/blocks/__tests__/__snapshots__/page.spec.js.snap b/explorer/src/routes/blocks/__tests__/__snapshots__/page.spec.js.snap
index 876ab329cb..7b16d8e034 100644
--- a/explorer/src/routes/blocks/__tests__/__snapshots__/page.spec.js.snap
+++ b/explorer/src/routes/blocks/__tests__/__snapshots__/page.spec.js.snap
@@ -31,7 +31,7 @@ exports[`Blocks page > should render the Blocks page with the mobile layout 1`]
       class="data-card__content"
     >
       <div
-        class="blocks-card__list"
+        class="data-card__list"
       >
         
         <dl
@@ -2023,7 +2023,7 @@ exports[`Blocks page > should render the Blocks page, start polling for blocks a
       class="data-card__content"
     >
       <div
-        class="table-container blocks-table blocks-card__table"
+        class="table-container blocks-table data-card__table"
       >
         <table
           class="table"
diff --git a/explorer/src/routes/provisioners/__tests__/__snapshots__/page.spec.js.snap b/explorer/src/routes/provisioners/__tests__/__snapshots__/page.spec.js.snap
index 410d4552f4..b0aae127eb 100644
--- a/explorer/src/routes/provisioners/__tests__/__snapshots__/page.spec.js.snap
+++ b/explorer/src/routes/provisioners/__tests__/__snapshots__/page.spec.js.snap
@@ -31,7 +31,7 @@ exports[`Provisioners page > should render the Provisioners page with the mobile
       class="data-card__content"
     >
       <div
-        class="provisioners-card__list"
+        class="data-card__list"
       >
         
         <dl
@@ -4130,7 +4130,7 @@ exports[`Provisioners page > should render the Provisioners page, start polling
       class="data-card__content"
     >
       <div
-        class="table-container provisioners-table provisioners-card__table"
+        class="table-container provisioners-table data-card__table"
       >
         <table
           class="table"
diff --git a/explorer/src/routes/transactions/__tests__/__snapshots__/page.spec.js.snap b/explorer/src/routes/transactions/__tests__/__snapshots__/page.spec.js.snap
index d6b0684468..a1b1062b58 100644
--- a/explorer/src/routes/transactions/__tests__/__snapshots__/page.spec.js.snap
+++ b/explorer/src/routes/transactions/__tests__/__snapshots__/page.spec.js.snap
@@ -31,7 +31,7 @@ exports[`Transactions page > should render the Transactions page with the mobile
       class="data-card__content"
     >
       <div
-        class="transactions-card__list"
+        class="data-card__list"
       >
         
         <dl
@@ -2578,7 +2578,7 @@ exports[`Transactions page > should render the Transactions page, start polling
       class="data-card__content"
     >
       <div
-        class="table-container transactions-table transactions-card__table"
+        class="table-container transactions-table"
       >
         <table
           class="table"