Skip to content

Commit

Permalink
[explorer] Rebuild transactions for address and object (MystenLabs#9852)
Browse files Browse the repository at this point in the history
## Description 

On object and address pages, the transaction table used a deprecated
method and required calling multiGetTransactions to populate. This
caused it to fail for big objects / transactions. Rewriting onto native
queryTransactions. For now, this has the limitation of not being able to
paginate, because we need to interleave two result sets. But once the
API is updated to support this filter we can pretty easily change that.

## Test Plan 

Ran locally.
  • Loading branch information
Jordan-Mysten authored Mar 27, 2023
1 parent d6f9aee commit b1c8042
Show file tree
Hide file tree
Showing 11 changed files with 87 additions and 225 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// Copyright (c) Mysten Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

import { useRpcClient } from '@mysten/core';
import { useQuery } from '@tanstack/react-query';

import { genTableDataFromTxData } from './TxCardUtils';

import { Banner } from '~/ui/Banner';
import { LoadingSpinner } from '~/ui/LoadingSpinner';
import { TableCard } from '~/ui/TableCard';

interface Props {
address: string;
type: 'object' | 'address';
}

export function TransactionsForAddress({ address, type }: Props) {
const rpc = useRpcClient();

const { data, isLoading, isError } = useQuery(
['transactions-for-address', address, type],
async () => {
const filters =
type === 'object'
? [{ InputObject: address }, { ChangedObject: address }]
: [{ ToAddress: address }, { FromAddress: address }];

const results = await Promise.all(
filters.map((filter) =>
rpc.queryTransactions({
filter,
order: 'descending',
limit: 100,
options: {
showEffects: true,
showBalanceChanges: true,
showInput: true,
},
})
)
);

return [...results[0].data, ...results[1].data].sort(
(a, b) => (b.timestampMs ?? 0) - (a.timestampMs ?? 0)
);
}
);

if (isLoading) {
return (
<div>
<LoadingSpinner />
</div>
);
}

if (isError) {
return (
<Banner variant="error" fullWidth>
Transactions could not be extracted on the following specified
address: {address}
</Banner>
);
}

const tableData = genTableDataFromTxData(data);

return (
<div data-testid="tx">
<TableCard data={tableData.data} columns={tableData.columns} />
</div>
);
}
3 changes: 1 addition & 2 deletions apps/explorer/src/components/transactions/TxCardUtils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import {
getExecutionStatusType,
getTotalGasUsed,
getTransactionSender,
type GetTxnDigestsResponse,
type JsonRpcProvider,
SUI_TYPE_ARG,
type SuiTransactionResponse,
Expand Down Expand Up @@ -149,7 +148,7 @@ const dedupe = (arr: string[]) => Array.from(new Set(arr));

export const getDataOnTxDigests = (
rpc: JsonRpcProvider,
transactions: GetTxnDigestsResponse
transactions: string[]
) =>
rpc
.multiGetTransactions({
Expand Down
112 changes: 0 additions & 112 deletions apps/explorer/src/components/transactions/TxForID.tsx

This file was deleted.

22 changes: 5 additions & 17 deletions apps/explorer/src/pages/address-result/AddressResult.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,11 @@ import { useParams } from 'react-router-dom';

import { ErrorBoundary } from '../../components/error-boundary/ErrorBoundary';
import OwnedObjects from '../../components/ownedobjects/OwnedObjects';
import TxForID from '../../components/transactions/TxForID';
import { TransactionsForAddress } from '../../components/transactions/TransactionsForAddress';

import { Heading } from '~/ui/Heading';
import { PageHeader } from '~/ui/PageHeader';

type DataType = {
id: string;
objects: ResponseType;
loadState?: 'loaded' | 'pending' | 'fail';
};

type ResponseType = {
objectId: string;
}[];

function instanceOfDataType(object: any): object is DataType {
return object !== undefined && ['id', 'objects'].every((x) => x in object);
}

function AddressResult() {
const { id: addressID } = useParams();

Expand All @@ -50,7 +36,10 @@ function AddressResult() {
</div>
<ErrorBoundary>
<div className="mt-2">
<TxForID id={addressID!} category="address" />
<TransactionsForAddress
address={addressID!}
type="address"
/>
</div>
</ErrorBoundary>
</div>
Expand All @@ -59,4 +48,3 @@ function AddressResult() {
}

export default AddressResult;
export { instanceOfDataType };
4 changes: 0 additions & 4 deletions apps/explorer/src/pages/object-result/ObjectResultType.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,6 @@ export type DataType = {
display?: Record<string, string>;
};

export function instanceOfDataType(object: any): object is DataType {
return object && ['id', 'version', 'objType'].every((x) => x in object);
}

/**
* Translate the SDK response to the existing data format
* TODO: We should redesign the rendering logic and data model
Expand Down
7 changes: 5 additions & 2 deletions apps/explorer/src/pages/object-result/views/PkgView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { getTransactionSender } from '@mysten/sui.js';

import { ErrorBoundary } from '../../../components/error-boundary/ErrorBoundary';
import PkgModulesWrapper from '../../../components/module/PkgModulesWrapper';
import TxForID from '../../../components/transactions/TxForID';
import { TransactionsForAddress } from '../../../components/transactions/TransactionsForAddress';
import { useGetTransaction } from '../../../hooks/useGetTransaction';
import { getOwnerStr } from '../../../utils/objectUtils';
import { trimStdLibPrefix } from '../../../utils/stringUtils';
Expand Down Expand Up @@ -108,7 +108,10 @@ function PkgView({ data }: { data: DataType }) {
<div className={styles.txsection}>
<h2 className={styles.header}>Transactions</h2>
<ErrorBoundary>
<TxForID id={viewedData.id} category="object" />
<TransactionsForAddress
address={viewedData.id}
type="object"
/>
</ErrorBoundary>
</div>
</div>
Expand Down
4 changes: 2 additions & 2 deletions apps/explorer/src/pages/object-result/views/TokenView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import { type DataType } from '../ObjectResultType';

import styles from './ObjectView.module.css';

import TxForID from '~/components/transactions/TxForID';
import { TransactionsForAddress } from '~/components/transactions/TransactionsForAddress';
import { DescriptionList, DescriptionItem } from '~/ui/DescriptionList';
import { Heading } from '~/ui/Heading';
import { AddressLink, ObjectLink, TransactionLink } from '~/ui/InternalLink';
Expand Down Expand Up @@ -268,7 +268,7 @@ export function TokenView({ data }: { data: DataType }) {
</div>
<div>
<h2 className={styles.header}>Transactions</h2>
<TxForID id={data.id} category="object" />
<TransactionsForAddress address={data.id} type="object" />
</div>
</div>
);
Expand Down
57 changes: 0 additions & 57 deletions sdk/typescript/src/providers/json-rpc-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
import { ErrorResponse, HttpHeaders, JsonRpcClient } from '../rpc/client';
import {
ExecuteTransactionRequestType,
GetTxnDigestsResponse,
ObjectId,
PaginatedTransactionResponse,
SubscriptionId,
Expand Down Expand Up @@ -474,62 +473,6 @@ export class JsonRpcProvider {
);
}

/**
* @deprecated this method will be removed by April 2023.
* Use `queryTransactions` instead
*/
async queryTransactionsForObjectDeprecated(
objectID: ObjectId,
descendingOrder: boolean = true,
): Promise<GetTxnDigestsResponse> {
const filters = [{ InputObject: objectID }, { ChangedObject: objectID }];
if (!objectID || !isValidSuiObjectId(normalizeSuiObjectId(objectID))) {
throw new Error('Invalid Sui Object id');
}
const results = await Promise.all(
filters.map((filter) =>
this.client.requestWithType(
'suix_queryTransactions',
[{ filter }, null, null, descendingOrder],
PaginatedTransactionResponse,
this.options.skipDataValidation,
),
),
);
return [
...results[0].data.map((r) => r.digest),
...results[1].data.map((r) => r.digest),
];
}

/**
* @deprecated this method will be removed by April 2023.
* Use `queryTransactions` instead
*/
async queryTransactionsForAddressDeprecated(
addressID: SuiAddress,
descendingOrder: boolean = true,
): Promise<GetTxnDigestsResponse> {
const filters = [{ ToAddress: addressID }, { FromAddress: addressID }];
if (!addressID || !isValidSuiAddress(normalizeSuiAddress(addressID))) {
throw new Error('Invalid Sui address');
}
const results = await Promise.all(
filters.map((filter) =>
this.client.requestWithType(
'suix_queryTransactions',
[{ filter }, null, null, descendingOrder],
PaginatedTransactionResponse,
this.options.skipDataValidation,
),
),
);
return [
...results[0].data.map((r) => r.digest),
...results[1].data.map((r) => r.digest),
];
}

async getTransaction(input: {
digest: TransactionDigest;
options?: SuiTransactionResponseOptions;
Expand Down
3 changes: 0 additions & 3 deletions sdk/typescript/src/types/transactions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -267,9 +267,6 @@ export const DevInspectResults = object({
});
export type DevInspectResults = Infer<typeof DevInspectResults>;

export const GetTxnDigestsResponse = array(TransactionDigest);
export type GetTxnDigestsResponse = Infer<typeof GetTxnDigestsResponse>;

export type SuiTransactionResponseQuery = {
filter?: TransactionFilter;
options?: SuiTransactionResponseOptions;
Expand Down
Loading

0 comments on commit b1c8042

Please sign in to comment.