diff --git a/app/api/account/[chainId]/[address]/route.ts b/app/api/account/[chainId]/[address]/route.ts
new file mode 100644
index 0000000..e5b2de0
--- /dev/null
+++ b/app/api/account/[chainId]/[address]/route.ts
@@ -0,0 +1,74 @@
+import { getDaimoAccountHistory } from '@/app/utils/accountHistory/getDaimoAccountHistory';
+import { resolveAccountForAddress } from '@/app/utils/profiles';
+import { AddressProfile } from '@/app/utils/types';
+import { createViemClient } from '@/app/utils/viem/client';
+import { Address, Hex } from 'viem';
+
+// TransferLog type from Daimo API.
+// TODO: add type when Daimo API is updated.
+type TransferLog = {
+ type: string;
+ status: string;
+ timestamp: number;
+ from: string;
+ to: string;
+ amount: number;
+ blockNumber: number;
+ blockHash: string;
+ txHash: string;
+ logIndex: number;
+ nonceMetadata: string;
+ opHash: string;
+ requestStatus: string | null;
+ feeAmount: number;
+};
+
+export type TransferHistoryEntry = {
+ transferLog: TransferLog;
+ otherAccountProfile: AddressProfile;
+};
+
+/**
+ * Handle GET requests to /api/account/[chainId]/[address]
+ *
+ * @param {Object} params - The request parameters.
+ * @param {string} params.chainId - The chain ID of the desired account.
+ * @param {string} params.address - The address of the desired account.
+ * @returns {Object} The account profile in the form: { account: AccountProfile }.
+ */
+export async function GET(
+ req: Request,
+ { params }: { params: { chainId: string; address: string } },
+) {
+ const publicClient = createViemClient(params.chainId);
+
+ const accountHistory = await getDaimoAccountHistory(params.address as Address);
+ const accountProfile: AddressProfile = await resolveAccountForAddress(
+ params.address as Hex,
+ publicClient,
+ );
+
+ // Filter out non-transfer logs.
+ const accountTransfers: TransferLog[] = accountHistory
+ ? accountHistory.transferLogs.filter((log: TransferLog) => log.type === 'transfer')
+ : [];
+
+ // Get other account profile for each transfer.
+ const transfers: TransferHistoryEntry[] = await Promise.all(
+ accountTransfers.map(async (transferLog) => {
+ const otherAccountAddress =
+ params.address == transferLog.from ? transferLog.to : transferLog.from;
+ const otherAccountProfile: AddressProfile = await resolveAccountForAddress(
+ otherAccountAddress as Address,
+ publicClient,
+ );
+
+ return { transferLog: transferLog, otherAccountProfile: otherAccountProfile };
+ }),
+ );
+
+ return Response.json({
+ accountProfile: accountProfile,
+ accountTransferHistory: transfers,
+ });
+}
diff --git a/app/components/AddressBubble.tsx b/app/components/AddressBubble.tsx
index 49c03bd..3e0b4d8 100644
--- a/app/components/AddressBubble.tsx
+++ b/app/components/AddressBubble.tsx
@@ -5,6 +5,7 @@ import { TextInitial } from './typography';
import { AccountIcon } from '@/public/profileIcons/profileIcons';
import { AccountAddress, AccountName } from './typography';
import { getProfileLink } from '../utils/profiles/getProfileLink';
+import CopyText from './minorComponents/CopyText';
/** Header-value React component field for Address Bubble */
function AddressField(props: { name: string; address: string; accountType: AccountTypeStr }) {
@@ -18,18 +19,23 @@ function AddressField(props: { name: string; address: string; accountType: Accou
+
{children}
); @@ -98,3 +98,48 @@ export function LinkFooter({ href, children }: { href: string; children: React.R ); } + +/** Typography for profile search */ + +// Header for account activity on profile. +export function HeaderProfileActivity({ children }: { children: React.ReactNode }) { + return ( ++ {children} +
+ ); +} + +// Row header for profile table. +export function RowHeaderProfileActivity({ children }: { children: React.ReactNode }) { + return ( ++ {children} +
+ ); +} + +// Transfers made to/from a specific account. +export function RowValueProfileActivity({ children }: { children: React.ReactNode }) { + return ( ++ {children} +
+ ); +} + +export function RowTimeProfileActivity({ children }: { children: React.ReactNode }) { + return ( ++ {children} +
+ ); +} + +export function RowAccountProfileInitial({ children }: { children: React.ReactNode }) { + return ( ++ {children} +
+ ); +} diff --git a/app/env.ts b/app/env.ts index 42077e1..08e187a 100644 --- a/app/env.ts +++ b/app/env.ts @@ -1,3 +1,5 @@ import { DaimoChain, getChainConfig } from './utils/viem/chainConfig'; export const chainConfig = getChainConfig((process.env.DAIMO_CHAIN || 'baseSepolia') as DaimoChain); + +export const apiUrl = process.env.ETH_RECEIPTS_DOMAIN || 'http://localhost:3000'; diff --git a/app/l/[chainId]/[blockNumber]/[logIndex]/page.tsx b/app/l/[chainId]/[blockNumber]/[logIndex]/page.tsx index c97a62a..e6638d8 100644 --- a/app/l/[chainId]/[blockNumber]/[logIndex]/page.tsx +++ b/app/l/[chainId]/[blockNumber]/[logIndex]/page.tsx @@ -2,8 +2,7 @@ import Footer from '@/app/components/Footer'; import LogNotFound from '@/app/components/LogNotFound'; import TransferCard from '@/app/components/TransferCard'; import { Header } from '@/app/components/typography'; - -const apiUrl = process.env.ETH_RECEIPTS_DOMAIN || 'http://localhost:3000'; +import { apiUrl } from '@/app/env'; /** * Fetch log data from API. @@ -58,7 +57,6 @@ export default async function Page({ ) : (