diff --git a/packages/app/.env.production b/packages/app/.env.production
index e4dbc4235..6453c6456 100644
--- a/packages/app/.env.production
+++ b/packages/app/.env.production
@@ -1,2 +1,2 @@
-FUEL_PROVIDER_URL=http://beta-4.fuel.network/graphql
+FUEL_PROVIDER_URL=http://beta-3.fuel.network/graphql
diff --git a/packages/app/next.config.mjs b/packages/app/next.config.mjs
index 27f06f715..b75ce30f0 100644
--- a/packages/app/next.config.mjs
+++ b/packages/app/next.config.mjs
@@ -7,15 +7,17 @@ const config = {
externalDir: true,
serverComponentsExternalPackages: [
'bcryptjs',
+ 'ws',
+ 'isomorphic-ws',
'@graphql-tools/delegate',
'@graphql-tools/load',
+ '@graphql-tools/load-files',
'@graphql-tools/schema',
'@graphql-tools/stitch',
'@graphql-tools/url-loader',
'@graphql-tools/utils',
],
serverActions: true,
- esmExternals: true,
},
/** We run eslint as a separate task in CI */
eslint: {
diff --git a/packages/app/src/app/globals.css b/packages/app/src/app/globals.css
index f13043b57..2571f86be 100644
--- a/packages/app/src/app/globals.css
+++ b/packages/app/src/app/globals.css
@@ -14,6 +14,7 @@
h5,
h6 {
font-weight: 600;
+ letter-spacing: -0.025em;
}
kbd {
font-size: 0.875rem;
diff --git a/packages/app/src/app/page.tsx b/packages/app/src/app/page.tsx
index cfbf560d9..7945a7f76 100644
--- a/packages/app/src/app/page.tsx
+++ b/packages/app/src/app/page.tsx
@@ -4,13 +4,17 @@ import { getLastTxs } from '~/systems/Transaction/actions/get-last-txs';
import { TxList } from '~/systems/Transaction/component/TxList/TxList';
export default async function Home() {
- const transactions = await getLastTxs({});
+ const transactions = await getLastTxs({ last: 30 });
return (
-
+
Recent Transactions
-
+
);
}
diff --git a/packages/app/src/app/tx/[id]/page.tsx b/packages/app/src/app/tx/[id]/page.tsx
new file mode 100644
index 000000000..aa444851d
--- /dev/null
+++ b/packages/app/src/app/tx/[id]/page.tsx
@@ -0,0 +1,25 @@
+import { Layout } from '~/systems/Core/components/Layout/Layout';
+import { getTx } from '~/systems/Transaction/actions/get-tx';
+import { TxScreen } from '~/systems/Transaction/screens/TxScreen/TxScreen';
+
+type TransactionProps = {
+ params: {
+ id: string;
+ };
+};
+
+export default async function Transaction({ params }: TransactionProps) {
+ const id = params.id;
+ const tx = await getTx({ id });
+ if (!tx) {
+ throw new Error('Transaction not found');
+ }
+ return (
+
+
+
+ );
+}
+
+// Revalidate cache every 10 seconds
+export const revalidate = 10;
diff --git a/packages/app/src/systems/Core/utils/address.ts b/packages/app/src/systems/Core/utils/address.ts
deleted file mode 100644
index 4e4d7a0a3..000000000
--- a/packages/app/src/systems/Core/utils/address.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-export function shortAddress(address: string = '') {
- return address.length > 10
- ? `${address.slice(0, 6)}...${address.slice(-4)}`
- : address;
-}
diff --git a/packages/app/src/systems/Core/utils/sdk.ts b/packages/app/src/systems/Core/utils/sdk.ts
index 3194445e8..65f0b26e8 100644
--- a/packages/app/src/systems/Core/utils/sdk.ts
+++ b/packages/app/src/systems/Core/utils/sdk.ts
@@ -12,5 +12,5 @@ const getBaseUrl = () => {
};
const API_URL = resolve(getBaseUrl(), '/api/graphql');
-const client = new GraphQLClient(API_URL);
+const client = new GraphQLClient(API_URL, { fetch });
export const sdk = getSdk(client);
diff --git a/packages/app/src/systems/Transaction/actions/get-last-txs.ts b/packages/app/src/systems/Transaction/actions/get-last-txs.ts
index cdb531d9b..060ff96fe 100644
--- a/packages/app/src/systems/Transaction/actions/get-last-txs.ts
+++ b/packages/app/src/systems/Transaction/actions/get-last-txs.ts
@@ -5,12 +5,11 @@ import { act } from '~/systems/Core/utils/act-server';
import { sdk } from '~/systems/Core/utils/sdk';
const schema = z.object({
- last: z.number().default(12).optional(),
+ first: z.number().optional().nullable(),
+ last: z.number().optional().nullable(),
});
-export const getLastTxs = act(schema, async ({ last = 12 }) => {
- const { data } = await sdk.getLastTransactions({ last }).catch(() => ({
- data: { transactions: { nodes: [] } },
- }));
- return data.transactions.nodes;
+export const getLastTxs = act(schema, async (input) => {
+ const { data } = await sdk.getLastTransactions(input);
+ return data.transactions;
});
diff --git a/packages/app/src/systems/Transaction/actions/get-tx.ts b/packages/app/src/systems/Transaction/actions/get-tx.ts
new file mode 100644
index 000000000..1e4a6178f
--- /dev/null
+++ b/packages/app/src/systems/Transaction/actions/get-tx.ts
@@ -0,0 +1,14 @@
+'use server';
+
+import { z } from 'zod';
+import { act } from '~/systems/Core/utils/act-server';
+import { sdk } from '~/systems/Core/utils/sdk';
+
+const schema = z.object({
+ id: z.string(),
+});
+
+export const getTx = act(schema, async (input) => {
+ const { data } = await sdk.getTransaction(input);
+ return data.transaction;
+});
diff --git a/packages/app/src/systems/Transaction/component/TxAccountItem/TxAccountItem.tsx b/packages/app/src/systems/Transaction/component/TxAccountItem/TxAccountItem.tsx
index 4dd98aec7..f1fca6044 100644
--- a/packages/app/src/systems/Transaction/component/TxAccountItem/TxAccountItem.tsx
+++ b/packages/app/src/systems/Transaction/component/TxAccountItem/TxAccountItem.tsx
@@ -10,7 +10,7 @@ import { TxIcon } from '../TxIcon/TxIcon';
export type TxAccountItemProps = CardProps & {
type: TxAccountType;
id: string;
- spent: BN;
+ spent?: BN;
};
const COLOR_MAP = {
@@ -33,9 +33,11 @@ export function TxAccountItem({
-
- Spent: {bn(spent).format({ units: 3 })}
-
+ {spent && (
+
+ Spent: {bn(spent).format()}
+
+ )}
diff --git a/packages/app/src/systems/Transaction/component/TxAssetItem/TxAssetItem.tsx b/packages/app/src/systems/Transaction/component/TxAssetItem/TxAssetItem.tsx
index 2c457cb17..88ec6c048 100644
--- a/packages/app/src/systems/Transaction/component/TxAssetItem/TxAssetItem.tsx
+++ b/packages/app/src/systems/Transaction/component/TxAssetItem/TxAssetItem.tsx
@@ -7,6 +7,8 @@ import type { BN } from 'fuels';
import Image from 'next/image';
import { useMemo } from 'react';
+import { TxIcon } from '../TxIcon/TxIcon';
+
export type TxAssetItemProps = CardProps & {
assetId: string;
amountIn: BN;
@@ -27,21 +29,24 @@ export function TxAssetItem({
() => ASSET_LIST.find((i) => i.assetId === assetId),
[assetId],
);
- if (!asset) {
- throw new Error(`Asset not found: ${assetId}`);
- }
+ const assetName = asset?.name ?? 'Unknown';
+ const assetSymbol = asset?.symbol ?? null;
return (
-
+ {asset?.icon ? (
+
+ ) : (
+
+ )}
-
+
- {bn(amountIn).format({ units: 4 })} {asset.symbol}
+ {bn(amountIn).format()} {assetSymbol}
- {bn(amountOut).format({ units: 4 })} {asset.symbol}
+ {bn(amountOut).format()} {assetSymbol}
diff --git a/packages/app/src/systems/Transaction/component/TxBreadcrumb/TxBreadcrumb.tsx b/packages/app/src/systems/Transaction/component/TxBreadcrumb/TxBreadcrumb.tsx
new file mode 100644
index 000000000..4d7ee05ef
--- /dev/null
+++ b/packages/app/src/systems/Transaction/component/TxBreadcrumb/TxBreadcrumb.tsx
@@ -0,0 +1,34 @@
+'use client';
+
+import type { BreadcrumbProps } from '@fuels/ui';
+import {
+ Breadcrumb,
+ BreadcrumbItem,
+ BreadcrumbLink,
+ Copyable,
+ Icon,
+ shortAddress,
+} from '@fuels/ui';
+import { IconHome } from '@tabler/icons-react';
+import Link from 'next/link';
+
+import type { TransactionNode } from '../../types';
+
+type TxBreadcrumbProps = BreadcrumbProps & {
+ transaction: TransactionNode;
+};
+
+export function TxBreadcrumb({ transaction: tx, ...props }: TxBreadcrumbProps) {
+ return (
+
+
+
+
+
+
+
+ {shortAddress(tx.id)}
+
+
+ );
+}
diff --git a/packages/app/src/systems/Transaction/component/TxCard/TxCard.tsx b/packages/app/src/systems/Transaction/component/TxCard/TxCard.tsx
index 5a936ab3e..03ebae8dd 100644
--- a/packages/app/src/systems/Transaction/component/TxCard/TxCard.tsx
+++ b/packages/app/src/systems/Transaction/component/TxCard/TxCard.tsx
@@ -9,6 +9,7 @@ import {
IconTransfer,
IconUsers,
} from '@tabler/icons-react';
+import Link from 'next/link';
import { tv } from 'tailwind-variants';
import type { TransactionNode, TxStatus } from '../../types';
@@ -22,35 +23,37 @@ export function TxCard({ transaction: tx, className, ...props }: TxCardProps) {
const classes = styles();
const title = tx.title as string;
return (
-
-
-
-
-
-
-
-
-
-
-
- {tx.totalAccounts} accounts
-
-
- {tx.totalOperations} operations
-
-
- {tx.statusType}
-
-
-
-
- {tx.totalAssets} assets
-
- {bn(tx.gasUsed).format({ units: 3 })} ETH
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+ {tx.totalAccounts} accounts
+
+
+ {tx.totalOperations} operations
+
+
+ {tx.statusType}
+
+
+
+
+ {tx.totalAssets} assets
+
+ {bn(tx.gasUsed).format({ precision: 5 })} ETH
+
+
+
+
+
);
}
diff --git a/packages/app/src/systems/Transaction/component/TxInput/TxInput.tsx b/packages/app/src/systems/Transaction/component/TxInput/TxInput.tsx
index e2d37a5a2..b8a6e7205 100644
--- a/packages/app/src/systems/Transaction/component/TxInput/TxInput.tsx
+++ b/packages/app/src/systems/Transaction/component/TxInput/TxInput.tsx
@@ -85,7 +85,7 @@ const TxInputCoin = createComponent({
{amount && (
- {bn(amount).format({ units: 3 })} {asset.symbol}
+ {bn(amount).format({ precision: 3 })} {asset.symbol}
)}
({
{inputs?.map((input: InputCoin) => (
- {input.utxoId}
+
+ {shortAddress(input.utxoId, 14, 14)}
+
- {bn(input.amount).format({ units: 3 })}
+ {bn(input.amount).format({ precision: 3 })} {asset.symbol}
))}
@@ -221,7 +226,7 @@ export function TxInput({ input, ...props }: TxInputProps) {
const styles = tv({
slots: {
header: 'group flex flex-row gap-4 justify-between items-center',
- icon: 'transition-transform group-hover:rotate-180 group-data-[state=open]:rotate-180',
+ icon: 'transition-transform group-data-[state=closed]:hover:rotate-180 group-data-[state=open]:rotate-180',
utxos: 'bg-gray-2 mx-4 py-3 px-4 rounded',
},
});
diff --git a/packages/app/src/systems/Transaction/component/TxList/TxList.tsx b/packages/app/src/systems/Transaction/component/TxList/TxList.tsx
index 6004fd15c..870fc380a 100644
--- a/packages/app/src/systems/Transaction/component/TxList/TxList.tsx
+++ b/packages/app/src/systems/Transaction/component/TxList/TxList.tsx
@@ -1,17 +1,17 @@
+import type { GetLastTransactionsQuery } from '@fuel-explorer/graphql';
import { Grid } from '@fuels/ui';
-import type { TransactionNode } from '../../types';
import { TxCard } from '../TxCard/TxCard';
type TxListProps = {
- transactions: TransactionNode[];
+ transactions: GetLastTransactionsQuery['transactions']['edges'];
};
export function TxList({ transactions = [] }: TxListProps) {
return (
{transactions.map((transaction) => (
-
+
))}
);
diff --git a/packages/app/src/systems/Transaction/component/TxSummary/TxSummary.tsx b/packages/app/src/systems/Transaction/component/TxSummary/TxSummary.tsx
index 56c94636e..9f36cd05f 100644
--- a/packages/app/src/systems/Transaction/component/TxSummary/TxSummary.tsx
+++ b/packages/app/src/systems/Transaction/component/TxSummary/TxSummary.tsx
@@ -1,3 +1,5 @@
+'use client';
+
import { bn } from '@fuel-ts/math';
import type { BaseProps, CardProps, BoxProps } from '@fuels/ui';
import {
@@ -101,7 +103,7 @@ export const TxSummaryDetails = createComponent<
iconSize={24}
leftIcon={IconGasStation}
>
- {bn(tx.gasUsed).format({ units: 3 })} ETH
+ {bn(tx.gasUsed).format()} ETH
@@ -144,7 +146,7 @@ export const TxSummary = withNamespace(TxSummaryRoot, {
const styles = tv({
slots: {
- root: 'grid grid-cols-[2fr,1fr]',
+ root: 'grid grid-cols-[2fr,1fr] gap-6',
details: 'p-6',
params: 'p-6 fuel-[Text]:text-lg',
row: 'grid grid-cols-[100px,1fr] gap-8 items-center',
diff --git a/packages/app/src/systems/Transaction/screens/TxScreen/TxScreen.tsx b/packages/app/src/systems/Transaction/screens/TxScreen/TxScreen.tsx
new file mode 100644
index 000000000..8921a9275
--- /dev/null
+++ b/packages/app/src/systems/Transaction/screens/TxScreen/TxScreen.tsx
@@ -0,0 +1,80 @@
+'use client';
+
+import type { GroupedInput, Maybe } from '@fuel-explorer/graphql';
+import { bn } from '@fuel-ts/math';
+import { Grid, Heading, VStack } from '@fuels/ui';
+
+import { TxAccountItem } from '../../component/TxAccountItem/TxAccountItem';
+import { TxAssetItem } from '../../component/TxAssetItem/TxAssetItem';
+import { TxBreadcrumb } from '../../component/TxBreadcrumb/TxBreadcrumb';
+import { TxInput } from '../../component/TxInput/TxInput';
+import { TxSummary } from '../../component/TxSummary/TxSummary';
+import type { TransactionNode, TxAccountType } from '../../types';
+
+type TxScreenProps = {
+ transaction: TransactionNode;
+};
+
+export function TxScreen({ transaction: tx }: TxScreenProps) {
+ console.log(tx.inputContracts);
+ return (
+
+
+ Transaction Details
+
+
+
+
+
+
+ Assets
+
+
+ {tx.inputAssetIds?.map((assetId) => (
+
+ ))}
+
+
+
+
+ Accounts
+
+
+ {tx.accountsInvolved?.map((acc) => (
+
+ ))}
+
+
+
+
+
+ Inputs
+
+ {tx.groupedInputs?.map((input) => (
+
+ ))}
+
+
+
+ );
+}
+
+function getInputId(input?: Maybe) {
+ if (!input) return 0;
+ if (input.type === 'InputCoin') return input.assetId;
+ if (input.type === 'InputContract') return input.contractId;
+ return input.sender;
+}
diff --git a/packages/ui/src/components/Breadcrumb/Breadcrumb.tsx b/packages/ui/src/components/Breadcrumb/Breadcrumb.tsx
index e5d3424c0..9920621dc 100644
--- a/packages/ui/src/components/Breadcrumb/Breadcrumb.tsx
+++ b/packages/ui/src/components/Breadcrumb/Breadcrumb.tsx
@@ -3,20 +3,18 @@ import { Children } from 'react';
import { createComponent, withNamespace } from '../../utils/component';
import type { PropsOf } from '../../utils/types';
-import { HStack } from '../Box';
-import type { HStackProps } from '../Box';
import { Icon } from '../Icon/Icon';
import { Link } from '../Link/Link';
import type { LinkProps } from '../Link/Link';
-export type BreadcrumbProps = PropsOf<'ul'> & Omit;
+export type BreadcrumbProps = PropsOf<'ul'>;
export type BreadcrumbItemProps = PropsOf<'li'>;
export type BreadcrumbLinkProps = LinkProps;
export const BreadcrumbRoot = createComponent({
id: 'Breadcrumb',
- baseElement: 'ul',
- render: (Comp, { children, ...props }) => {
+ className: 'flex gap-4 items-stretch',
+ render: (_, { children, ...props }) => {
const newChildren = Children.toArray(children).flatMap((child, index) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const id = (child as any)?.type?.id;
@@ -37,11 +35,7 @@ export const BreadcrumbRoot = createComponent({
}
return child;
});
- return (
-
- {newChildren}
-
- );
+ return ;
},
});
@@ -53,6 +47,7 @@ export const BreadcrumbItem = createComponent({
export const BreadcrumbLink = createComponent(
{
id: 'BreadcrumbLink',
+ className: 'inline-flex',
baseElement: Link,
},
);
diff --git a/packages/ui/src/theme/tailwind-preset.ts b/packages/ui/src/theme/tailwind-preset.ts
index a6264c5af..adaa87195 100644
--- a/packages/ui/src/theme/tailwind-preset.ts
+++ b/packages/ui/src/theme/tailwind-preset.ts
@@ -108,8 +108,12 @@ const preset: Config = {
plugin(function ({ addVariant, matchVariant }) {
// Add a `third` variant, ie. `third:pb-0`
addVariant('not-first', '& ~ &');
- addVariant('not-disabled', '&:not([aria-disabled=true])');
addVariant('not-first-last', '&:not(:first-of-type,:last-of-type)');
+ addVariant('not-disabled', '&:not([aria-disabled=true],:disabled)');
+ addVariant(
+ 'not-disabled-hover',
+ '&:not([aria-disabled=true],:disabled):hover',
+ );
addVariant('first-type', '&:first-of-type');
addVariant('last-type', '&:last-of-type');
addVariant('dark-theme', ['.dark &', '.dark-theme &']);
diff --git a/packages/ui/src/utils/helpers.ts b/packages/ui/src/utils/helpers.ts
index 1c14fd826..1fc7c9a95 100644
--- a/packages/ui/src/utils/helpers.ts
+++ b/packages/ui/src/utils/helpers.ts
@@ -51,8 +51,8 @@ export function toCamelCase(str: string): string {
});
}
-export function shortAddress(address: string = '') {
+export function shortAddress(address: string = '', first = 6, last = 4) {
return address.length > 10
- ? `${address.slice(0, 6)}...${address.slice(-4)}`
+ ? `${address.slice(0, first)}...${address.slice(-last)}`
: address;
}