diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9c2e635e9..b8be846d5 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -39,6 +39,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Features
+- [#1009](https://github.com/alleslabs/celatone-frontend/pull/1009) Add Sequencer for account details page
+- [#1006](https://github.com/alleslabs/celatone-frontend/pull/1006) Add Sequencer for past txs
+- [#994](https://github.com/alleslabs/celatone-frontend/pull/994) Add Sequencer, Mesa tier and TierSwitcher component
- [#1005](https://github.com/alleslabs/celatone-frontend/pull/1005) Support recent blocks, block details in sequencer tier
### Improvements
diff --git a/src/lib/app-provider/env.ts b/src/lib/app-provider/env.ts
index b6f2207ab..4d94d752e 100644
--- a/src/lib/app-provider/env.ts
+++ b/src/lib/app-provider/env.ts
@@ -100,6 +100,7 @@ export enum CELATONE_QUERY_KEYS {
TXS_BY_ADDRESS = "CELATONE_QUERY_TXS_BY_ADDRESS",
TXS_COUNT_BY_ADDRESS = "CELATONE_QUERY_TXS_COUNT_BY_ADDRESS",
TXS_BY_ADDRESS_LCD = "CELATONE_QUERY_TXS_BY_ADDRESS_LCD",
+ TXS_BY_ADDRESS_SEQUENCER = "CELATONE_QUERY_TXS_BY_ADDRESS_SEQUENCER",
TXS_BY_ACCOUNT_ADDRESS_LCD = "CELATONE_QUERY_TXS_BY_ACCOUNT_ADDRESS_LCD",
TXS_BY_CONTRACT_ADDRESS_LCD = "CELATONE_QUERY_TXS_BY_CONTRACT_ADDRESS_LCD",
TXS = "CELATONE_QUERY_TXS",
diff --git a/src/lib/components/action-msg/SingleMsg.tsx b/src/lib/components/action-msg/SingleMsg.tsx
index 6c83c3f20..3d9a84859 100644
--- a/src/lib/components/action-msg/SingleMsg.tsx
+++ b/src/lib/components/action-msg/SingleMsg.tsx
@@ -48,7 +48,15 @@ export const SingleMsg = ({
token={token}
// TODO: add `ampCopierSection` later
/>
- {index < tokens.length - 2 ? , : and}
+ {tokens.length > 1 && (
+ <>
+ {index < tokens.length - 2 ? (
+ ,
+ ) : (
+ and
+ )}
+ >
+ )}
))}
{/* Tags */}
diff --git a/src/lib/pages/account-details/components/tables/txs/Full.tsx b/src/lib/pages/account-details/components/tables/txs/full.tsx
similarity index 100%
rename from src/lib/pages/account-details/components/tables/txs/Full.tsx
rename to src/lib/pages/account-details/components/tables/txs/full.tsx
diff --git a/src/lib/pages/account-details/components/tables/txs/index.tsx b/src/lib/pages/account-details/components/tables/txs/index.tsx
index 8d9b59f06..dce33e3b3 100644
--- a/src/lib/pages/account-details/components/tables/txs/index.tsx
+++ b/src/lib/pages/account-details/components/tables/txs/index.tsx
@@ -1,12 +1,14 @@
import { TierSwitcher } from "lib/components/TierSwitcher";
-import { TxsTableFull } from "./Full";
-import { TxsTableLite } from "./Lite";
+import { TxsTableFull } from "./full";
+import { TxsTableLite } from "./lite";
+import { TxsTableSequencer } from "./sequencer";
import type { TxsTableProps } from "./types";
export const TxsTable = (props: TxsTableProps) => (
}
+ sequencer={}
lite={}
/>
);
diff --git a/src/lib/pages/account-details/components/tables/txs/Lite.tsx b/src/lib/pages/account-details/components/tables/txs/lite.tsx
similarity index 100%
rename from src/lib/pages/account-details/components/tables/txs/Lite.tsx
rename to src/lib/pages/account-details/components/tables/txs/lite.tsx
diff --git a/src/lib/pages/account-details/components/tables/txs/sequencer.tsx b/src/lib/pages/account-details/components/tables/txs/sequencer.tsx
new file mode 100644
index 000000000..10b5b4e3f
--- /dev/null
+++ b/src/lib/pages/account-details/components/tables/txs/sequencer.tsx
@@ -0,0 +1,83 @@
+import { Box, Flex } from "@chakra-ui/react";
+
+import { useMobile, useTierConfig } from "lib/app-provider";
+import { LoadNext } from "lib/components/LoadNext";
+import { EmptyState, ErrorFetching } from "lib/components/state";
+import {
+ MobileTitle,
+ TableTitle,
+ TransactionsTable,
+ ViewMore,
+} from "lib/components/table";
+import { useTxsByAddressSequencer } from "lib/services/tx";
+import type { BechAddr20 } from "lib/types";
+
+import type { TxsTableProps } from "./types";
+
+export const TxsTableSequencer = ({ address, onViewMore }: TxsTableProps) => {
+ const isMobile = useMobile();
+ const { isFullTier } = useTierConfig();
+
+ const {
+ data,
+ error,
+ fetchNextPage,
+ hasNextPage,
+ isLoading,
+ isFetchingNextPage,
+ } = useTxsByAddressSequencer(
+ address as BechAddr20,
+ undefined,
+ onViewMore ? 5 : 10
+ );
+
+ const isMobileOverview = isMobile && !!onViewMore;
+
+ return (
+
+ {isMobileOverview ? (
+
+ ) : (
+
+
+ {!isMobileOverview && (
+
+ ) : (
+
+ )
+ }
+ showRelations
+ />
+ )}
+ {hasNextPage && (
+ <>
+ {onViewMore ? (
+
+ ) : (
+
+ )}
+ >
+ )}
+
+ )}
+
+ );
+};
diff --git a/src/lib/pages/past-txs/index.tsx b/src/lib/pages/past-txs/index.tsx
index 9c366a582..6ee1e4c45 100644
--- a/src/lib/pages/past-txs/index.tsx
+++ b/src/lib/pages/past-txs/index.tsx
@@ -6,6 +6,7 @@ import { TierSwitcher } from "lib/components/TierSwitcher";
import { PastTxsFull } from "./full";
import { PastTxsLite } from "./lite";
+import { PastTxsSequencer } from "./sequencer";
const PastTxs = () => {
const router = useRouter();
@@ -14,7 +15,13 @@ const PastTxs = () => {
if (router.isReady) track(AmpEvent.TO_PAST_TXS);
}, [router.isReady]);
- return } lite={} />;
+ return (
+ }
+ sequencer={}
+ lite={}
+ />
+ );
};
export default PastTxs;
diff --git a/src/lib/pages/past-txs/lite.tsx b/src/lib/pages/past-txs/lite.tsx
index ab7a1cbf0..1bf4e3a05 100644
--- a/src/lib/pages/past-txs/lite.tsx
+++ b/src/lib/pages/past-txs/lite.tsx
@@ -2,7 +2,7 @@ import { Flex, Heading } from "@chakra-ui/react";
import type { ChangeEvent } from "react";
import { useEffect, useState } from "react";
-import { useCurrentChain, useWasmConfig } from "lib/app-provider";
+import { useCurrentChain } from "lib/app-provider";
import InputWithIcon from "lib/components/InputWithIcon";
import PageContainer from "lib/components/PageContainer";
import { Pagination } from "lib/components/pagination";
@@ -16,22 +16,18 @@ import { useTxsByAddressLcd } from "lib/services/tx";
interface PastTxsLiteTransactionsTableWithWalletEmptyStateProps {
search: string;
- isWasmEnabled: boolean;
error: unknown;
}
const PastTxsLiteTransactionsTableWithWalletEmptyState = ({
search,
- isWasmEnabled,
error,
}: PastTxsLiteTransactionsTableWithWalletEmptyStateProps) => {
if (search.trim().length > 0)
return (
);
@@ -48,7 +44,6 @@ const PastTxsLiteTransactionsTableWithWalletEmptyState = ({
};
export const PastTxsLite = () => {
- const isWasmEnabled = useWasmConfig({ shouldRedirect: false }).enabled;
const {
address,
chain: { chain_id: chainId },
@@ -113,9 +108,7 @@ export const PastTxsLite = () => {
{
emptyState={
}
diff --git a/src/lib/pages/past-txs/sequencer.tsx b/src/lib/pages/past-txs/sequencer.tsx
new file mode 100644
index 000000000..07c75b62c
--- /dev/null
+++ b/src/lib/pages/past-txs/sequencer.tsx
@@ -0,0 +1,111 @@
+import { Flex, Heading } from "@chakra-ui/react";
+import { useEffect, useState } from "react";
+
+import { useCurrentChain } from "lib/app-provider";
+import InputWithIcon from "lib/components/InputWithIcon";
+import { LoadNext } from "lib/components/LoadNext";
+import PageContainer from "lib/components/PageContainer";
+import { CelatoneSeo } from "lib/components/Seo";
+import { EmptyState, ErrorFetching } from "lib/components/state";
+import { TransactionsTableWithWallet } from "lib/components/table";
+import { UserDocsLink } from "lib/components/UserDocsLink";
+import { useDebounce } from "lib/hooks";
+import { useTxsByAddressSequencer } from "lib/services/tx";
+
+interface PastTxsSequencerTransactionsTableWithWalletEmptyStateProps {
+ search: string;
+ error: unknown;
+}
+
+const PastTxsSequencerTransactionsTableWithWalletEmptyState = ({
+ search,
+ error,
+}: PastTxsSequencerTransactionsTableWithWalletEmptyStateProps) => {
+ if (search.trim().length > 0)
+ return (
+
+ );
+
+ if (error) return ;
+
+ return (
+
+ );
+};
+
+export const PastTxsSequencer = () => {
+ const {
+ address,
+ chain: { chain_id: chainId },
+ } = useCurrentChain();
+
+ const [search, setSearch] = useState("");
+ const debouncedSearch = useDebounce(search);
+
+ const {
+ data,
+ error,
+ fetchNextPage,
+ hasNextPage,
+ isLoading,
+ isFetchingNextPage,
+ } = useTxsByAddressSequencer(address, debouncedSearch);
+
+ useEffect(() => {
+ setSearch("");
+ }, [chainId, address]);
+
+ return (
+
+
+
+
+ Past Transactions
+
+
+
+
+ setSearch(e.target.value)}
+ size={{ base: "md", md: "lg" }}
+ amptrackSection="past-txs-search"
+ />
+
+
+ }
+ showActions={false}
+ showRelations
+ />
+ {hasNextPage && (
+
+ )}
+
+ );
+};
diff --git a/src/lib/services/tx/index.ts b/src/lib/services/tx/index.ts
index 0501092c1..9f0894958 100644
--- a/src/lib/services/tx/index.ts
+++ b/src/lib/services/tx/index.ts
@@ -17,7 +17,6 @@ import {
useLcdEndpoint,
useMoveConfig,
useTierConfig,
- useValidateAddress,
useWasmConfig,
} from "lib/app-provider";
import { createQueryFnWithTimeout } from "lib/query-utils";
@@ -49,7 +48,11 @@ import {
getTxsByContractAddressLcd,
getTxsByHashLcd,
} from "./lcd";
-import { getTxsByBlockHeightSequencer } from "./sequencer";
+import {
+ getTxsByAccountAddressSequencer,
+ getTxsByBlockHeightSequencer,
+ getTxsByHashSequencer,
+} from "./sequencer";
export const useTxData = (
txHash: Option,
@@ -291,23 +294,33 @@ export const useTxsByAddressLcd = (
options: UseQueryOptions = {}
) => {
const endpoint = useLcdEndpoint();
- const { validateContractAddress } = useValidateAddress();
const {
chain: { bech32_prefix: prefix },
} = useCurrentChain();
+ // eslint-disable-next-line sonarjs/cognitive-complexity
const queryfn = useCallback(async () => {
const txs = await (async () => {
- if (search && isTxHash(search)) return getTxsByHashLcd(endpoint, search);
+ if (search && isTxHash(search)) {
+ const txsByHash = await getTxsByHashLcd(endpoint, search);
- if (search && !validateContractAddress(search))
- return getTxsByContractAddressLcd(
- endpoint,
- search as BechAddr32,
- limit,
- offset
+ if (txsByHash.total === 0)
+ throw new Error("transaction not found (getTxsByHashLcd)");
+
+ const tx = txsByHash.items[0];
+ const sender = convertAccountPubkeyToAccountAddress(
+ tx.signerPubkey,
+ prefix
);
+ if (address === sender) return txsByHash;
+
+ throw new Error("address is not equal to sender (getTxsByHashLcd)");
+ }
+
+ if (search && !isTxHash(search))
+ throw new Error("search is not a tx hash (useTxsByAddressLcd)");
+
if (!address)
throw new Error("address is undefined (useTxsByAddressLcd)");
return getTxsByAccountAddressLcd(endpoint, address, limit, offset);
@@ -320,15 +333,7 @@ export const useTxsByAddressLcd = (
})),
total: txs.total,
};
- }, [
- address,
- endpoint,
- limit,
- offset,
- prefix,
- search,
- validateContractAddress,
- ]);
+ }, [address, endpoint, limit, offset, prefix, search]);
return useQuery(
[
@@ -344,6 +349,96 @@ export const useTxsByAddressLcd = (
);
};
+export const useTxsByAddressSequencer = (
+ address: Option,
+ search: Option,
+ limit = 10
+) => {
+ const endpoint = useLcdEndpoint();
+ const {
+ chain: { bech32_prefix: prefix },
+ } = useCurrentChain();
+
+ const queryfn = useCallback(
+ // eslint-disable-next-line sonarjs/cognitive-complexity
+ async (pageParam: Option) => {
+ return (async () => {
+ if (search && isTxHash(search)) {
+ const txsByHash = await getTxsByHashSequencer(endpoint, search);
+
+ if (txsByHash.pagination.total === 0)
+ throw new Error("transaction not found (getTxsByHashSequencer)");
+
+ const tx = txsByHash.items[0];
+ const sender = convertAccountPubkeyToAccountAddress(
+ tx.signerPubkey,
+ prefix
+ );
+
+ if (address === sender) return txsByHash;
+
+ const findAddressFromEvents = tx.events?.some((event) =>
+ event.attributes.some((attr) => attr.value === address)
+ );
+
+ if (findAddressFromEvents) return txsByHash;
+
+ throw new Error(
+ "transaction is not related (useTxsByAddressSequncer)"
+ );
+ }
+
+ if (search && !isTxHash(search))
+ throw new Error("search is not a tx hash (useTxsByAddressSequncer)");
+
+ if (!address)
+ throw new Error("address is undefined (useTxsByAddressSequncer)");
+
+ return getTxsByAccountAddressSequencer(
+ endpoint,
+ address,
+ pageParam,
+ limit
+ );
+ })();
+ },
+ [address, endpoint, prefix, search, limit]
+ );
+
+ const { data, ...rest } = useInfiniteQuery(
+ [
+ CELATONE_QUERY_KEYS.TXS_BY_ADDRESS_SEQUENCER,
+ endpoint,
+ address,
+ search,
+ limit,
+ ],
+ ({ pageParam }) => queryfn(pageParam),
+ {
+ getNextPageParam: (lastPage) => lastPage.pagination.nextKey ?? undefined,
+ refetchOnWindowFocus: false,
+ }
+ );
+
+ return {
+ ...rest,
+ data: data?.pages.flatMap((page) =>
+ page.items.map((item) => {
+ const sender = convertAccountPubkeyToAccountAddress(
+ item.signerPubkey,
+ prefix
+ );
+
+ return {
+ ...item,
+ sender,
+ isSigner: sender === address,
+ };
+ })
+ ),
+ };
+};
+
export const useTxsByBlockHeightSequencer = (height: number) => {
const endpoint = useLcdEndpoint();
const {
diff --git a/src/lib/services/tx/lcd.ts b/src/lib/services/tx/lcd.ts
index ca5b19a47..4a75b2d83 100644
--- a/src/lib/services/tx/lcd.ts
+++ b/src/lib/services/tx/lcd.ts
@@ -20,21 +20,19 @@ export const getTxsByHashLcd = async (endpoint: string, txHash: string) =>
export const getTxsByContractAddressLcd = async (
endpoint: string,
- address: BechAddr32,
+ contractAddress: BechAddr32,
limit: number,
offset: number
) =>
axios
- .get(
- `${endpoint}/cosmos/tx/v1beta1/txs?events=wasm._contract_address=%27${encodeURI(address)}%27`,
- {
- params: {
- order_by: 2,
- limit,
- page: offset / limit + 1,
- },
- }
- )
+ .get(`${endpoint}/cosmos/tx/v1beta1/txs`, {
+ params: {
+ order_by: 2,
+ limit,
+ page: offset / limit + 1,
+ events: `wasm._contract_address='${encodeURI(contractAddress)}'`,
+ },
+ })
.then(({ data }) => parseWithError(zTxsByAddressResponseLcd, data));
export const getTxsByAccountAddressLcd = async (
diff --git a/src/lib/services/tx/sequencer.ts b/src/lib/services/tx/sequencer.ts
index 31bfbf7e1..12990fec7 100644
--- a/src/lib/services/tx/sequencer.ts
+++ b/src/lib/services/tx/sequencer.ts
@@ -1,9 +1,34 @@
import axios from "axios";
-import { zBlockTxsResponseSequencer } from "../types";
-import type { Option } from "lib/types";
+import {
+ zBlockTxsResponseSequencer,
+ zTxsByAddressResponseSequencer,
+ zTxsByHashResponseSequencer,
+} from "../types";
+import type { BechAddr20, Option } from "lib/types";
import { parseWithError } from "lib/utils";
+export const getTxsByAccountAddressSequencer = async (
+ endpoint: string,
+ address: BechAddr20,
+ paginationKey: Option,
+ limit = 10
+) =>
+ axios
+ .get(`${endpoint}/indexer/tx/v1/txs/by_account/${encodeURI(address)}`, {
+ params: {
+ "pagination.limit": limit,
+ "pagination.reverse": true,
+ "pagination.key": paginationKey,
+ },
+ })
+ .then(({ data }) => parseWithError(zTxsByAddressResponseSequencer, data));
+
+export const getTxsByHashSequencer = async (endpoint: string, txHash: string) =>
+ axios
+ .get(`${endpoint}/indexer/tx/v1/txs/${encodeURI(txHash)}`)
+ .then(({ data }) => parseWithError(zTxsByHashResponseSequencer, data));
+
export const getTxsByBlockHeightSequencer = async (
endpoint: string,
height: number,
diff --git a/src/lib/services/types/tx.ts b/src/lib/services/types/tx.ts
index 2a8dbea36..d1984d52c 100644
--- a/src/lib/services/types/tx.ts
+++ b/src/lib/services/types/tx.ts
@@ -175,6 +175,7 @@ export const zTxsResponseItemFromLcd =
isIbc: false,
isOpinit: false,
isInstantiate: false,
+ events: val.events,
};
});
@@ -189,6 +190,19 @@ export const zTxsByAddressResponseLcd = z
}));
export type TxsByAddressResponseLcd = z.infer;
+export const zTxsByAddressResponseSequencer = z
+ .object({
+ txs: z.array(zTxsResponseItemFromLcd),
+ pagination: zPagination,
+ })
+ .transform((val) => ({
+ items: val.txs,
+ pagination: val.pagination,
+ }));
+export type TxsByAddressResponseSequencer = z.infer<
+ typeof zTxsByAddressResponseSequencer
+>;
+
export const zTxsByHashResponseLcd = z
.object({
tx_response: zTxsResponseItemFromLcd,
@@ -198,6 +212,21 @@ export const zTxsByHashResponseLcd = z
total: 1,
}));
+export const zTxsByHashResponseSequencer = z
+ .object({
+ tx: zTxsResponseItemFromLcd,
+ })
+ .transform((val) => ({
+ items: [val.tx],
+ pagination: {
+ total: val.tx ? 1 : 0,
+ nextKey: null,
+ },
+ }));
+export type TxsByHashResponseSequencer = z.infer<
+ typeof zTxsByHashResponseSequencer
+>;
+
export const zTxByHashResponseLcd = z
.object({
tx_response: zTxResponse,
diff --git a/src/lib/services/types/wasm/contract.ts b/src/lib/services/types/wasm/contract.ts
index fe7a8fea7..ac1fbaa0f 100644
--- a/src/lib/services/types/wasm/contract.ts
+++ b/src/lib/services/types/wasm/contract.ts
@@ -222,14 +222,16 @@ export const zInstantiatedContractsLcd = z
export const zContractCw2InfoLcd = z
.object({
- data: z.string(),
+ data: z.string().nullable(),
})
- .transform((val) => JSON.parse(decode(val.data)))
+ .transform((val) => (val.data ? JSON.parse(decode(val.data)) : null))
.pipe(
- z.object({
- contract: z.string(),
- version: z.string(),
- })
+ z
+ .object({
+ contract: z.string(),
+ version: z.string(),
+ })
+ .nullable()
);
export type ContractCw2InfoLcd = z.infer;
diff --git a/src/lib/types/tx/transaction.ts b/src/lib/types/tx/transaction.ts
index f40801df4..4a5b9e5a6 100644
--- a/src/lib/types/tx/transaction.ts
+++ b/src/lib/types/tx/transaction.ts
@@ -1,6 +1,7 @@
import type { Log } from "@cosmjs/stargate/build/logs";
import { z } from "zod";
+import type { Event } from "lib/services/types";
import type { BechAddr, Option, Pubkey } from "lib/types";
export enum ActionMsgType {
@@ -39,6 +40,7 @@ export interface Transaction {
isIbc: boolean;
isInstantiate: boolean;
isOpinit: boolean;
+ events?: Event[];
}
export type TransactionWithSignerPubkey = Omit & {