Skip to content

Commit a9b7633

Browse files
[SDK] Show past transactions in transactions modal screen (#6607)
1 parent 38e089a commit a9b7633

File tree

7 files changed

+124
-20
lines changed

7 files changed

+124
-20
lines changed

.changeset/fruity-cooks-know.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"thirdweb": patch
3+
---
4+
5+
Show past transactions in transactions modal screen

apps/playground-web/src/app/insight/[blueprint_slug]/blueprint-playground.client.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,10 @@ export function BlueprintPlaygroundUI(props: {
160160
for (const param of parameters) {
161161
if (param.schema && "type" in param.schema && param.schema.default) {
162162
values[param.name] = param.schema.default;
163+
} else if (param.name === "filter_block_timestamp_gte") {
164+
values[param.name] = Math.floor(
165+
(Date.now() - 3 * 30 * 24 * 60 * 60 * 1000) / 1000,
166+
);
163167
} else {
164168
values[param.name] = "";
165169
}

packages/thirdweb/src/react/core/hooks/contract/useWaitForReceipt.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@ import {
1717
* @transaction
1818
*/
1919
export function useWaitForReceipt(
20-
options: WaitForReceiptOptions | undefined,
20+
options:
21+
| (WaitForReceiptOptions & { queryOptions?: { enabled?: boolean } })
22+
| undefined,
2123
): UseQueryResult<TransactionReceipt> {
2224
return useQuery({
2325
queryKey: [
@@ -32,7 +34,8 @@ export function useWaitForReceipt(
3234
}
3335
return waitForReceipt(options);
3436
},
35-
enabled: !!options?.transactionHash,
37+
enabled:
38+
!!options?.transactionHash && (options?.queryOptions?.enabled ?? true),
3639
retry: false,
3740
});
3841
}

packages/thirdweb/src/react/web/ui/ConnectWallet/screens/WalletTransactionHistory.tsx

Lines changed: 51 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
11
"use client";
22
import styled from "@emotion/styled";
33
import { CheckIcon, CrossCircledIcon } from "@radix-ui/react-icons";
4+
import { useQuery } from "@tanstack/react-query";
45
import { useSyncExternalStore } from "react";
6+
import { ethereum } from "../../../../../chains/chain-definitions/ethereum.js";
57
import { getCachedChain } from "../../../../../chains/utils.js";
68
import type { ThirdwebClient } from "../../../../../client/client.js";
7-
import { getTransactionStore } from "../../../../../transaction/transaction-store.js";
9+
import {
10+
type StoredTransaction,
11+
getPastTransactions,
12+
getTransactionStore,
13+
} from "../../../../../transaction/transaction-store.js";
814
import { shortenHex } from "../../../../../utils/address.js";
9-
import type { Hex } from "../../../../../utils/encoding/hex.js";
1015
import { formatExplorerTxUrl } from "../../../../../utils/url.js";
1116
import { useCustomTheme } from "../../../../core/design-system/CustomThemeProvider.js";
1217
import { iconSize, spacing } from "../../../../core/design-system/index.js";
@@ -38,8 +43,20 @@ export function WalletTransactionHistory(props: {
3843
transactionStore.subscribe,
3944
transactionStore.getValue,
4045
);
41-
const transactions = [...reverseChronologicalTransactions].reverse();
42-
46+
const historicalTxQuery = useQuery({
47+
queryKey: ["transactions", props.address, activeChain],
48+
queryFn: () =>
49+
getPastTransactions({
50+
walletAddress: props.address,
51+
chain: activeChain || ethereum,
52+
client: props.client,
53+
}),
54+
enabled: !!activeChain,
55+
});
56+
const transactions = [
57+
...[...reverseChronologicalTransactions].reverse(),
58+
...(historicalTxQuery.data || []),
59+
];
4360
return (
4461
<Container
4562
scrollY
@@ -52,7 +69,22 @@ export function WalletTransactionHistory(props: {
5269
}}
5370
>
5471
<Container flex="column" gap="xs" expand>
55-
{transactions.length === 0 ? (
72+
{historicalTxQuery.isLoading && (
73+
<Container
74+
flex="column"
75+
gap="md"
76+
center="both"
77+
color="secondaryText"
78+
style={{
79+
flex: "1",
80+
minHeight: "250px",
81+
}}
82+
>
83+
<Spinner color={"secondaryText"} size={"md"} />
84+
<Text>Loading recent transactions...</Text>
85+
</Container>
86+
)}
87+
{!historicalTxQuery.isLoading && transactions.length === 0 ? (
5688
<Container
5789
flex="column"
5890
gap="md"
@@ -79,8 +111,7 @@ export function WalletTransactionHistory(props: {
79111
key={tx.transactionHash}
80112
explorerUrl={chainExplorers.explorers[0]?.url}
81113
client={props.client}
82-
hash={tx.transactionHash}
83-
chainId={tx.chainId}
114+
tx={tx}
84115
/>
85116
);
86117
})}
@@ -92,21 +123,24 @@ export function WalletTransactionHistory(props: {
92123
}
93124

94125
function TransactionButton(props: {
95-
hash: string;
126+
tx: StoredTransaction;
96127
client: ThirdwebClient;
97-
chainId: number;
98128
explorerUrl?: string;
99129
}) {
100130
const {
101-
data: receipt,
131+
data: fetchedReceipt,
102132
isLoading,
103133
error,
104134
} = useWaitForReceipt({
105-
transactionHash: props.hash as Hex,
106-
chain: getCachedChain(props.chainId),
135+
transactionHash: props.tx.transactionHash,
136+
chain: getCachedChain(props.tx.chainId),
107137
client: props.client,
138+
queryOptions: {
139+
enabled: props.tx.receipt === undefined,
140+
},
108141
});
109-
const chainIconQuery = useChainIconUrl(getCachedChain(props.chainId));
142+
const chainIconQuery = useChainIconUrl(getCachedChain(props.tx.chainId));
143+
const receipt = props.tx.receipt ?? fetchedReceipt;
110144

111145
const content = (
112146
<TxButton
@@ -148,8 +182,8 @@ function TransactionButton(props: {
148182
>
149183
<Text size="sm" color="primaryText">
150184
{receipt?.to
151-
? `Interacted with ${shortenHex(receipt.to, 4)}`
152-
: `Hash: ${shortenHex(props.hash, 4)}`}
185+
? `Interacted with ${shortenHex(receipt?.to, 4)}`
186+
: `Hash: ${shortenHex(props.tx.transactionHash, 4)}`}
153187
</Text>
154188
</Container>
155189

@@ -165,7 +199,7 @@ function TransactionButton(props: {
165199
}}
166200
>
167201
<ChainName
168-
chain={getCachedChain(props.chainId)}
202+
chain={getCachedChain(props.tx.chainId)}
169203
size="xs"
170204
client={props.client}
171205
/>
@@ -193,7 +227,7 @@ function TransactionButton(props: {
193227
if (props.explorerUrl) {
194228
return (
195229
<a
196-
href={formatExplorerTxUrl(props.explorerUrl, props.hash)}
230+
href={formatExplorerTxUrl(props.explorerUrl, props.tx.transactionHash)}
197231
target="_blank"
198232
rel="noreferrer"
199233
>

packages/thirdweb/src/transaction/transaction-store.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,17 @@
1+
import type { Chain } from "../chains/types.js";
2+
import type { ThirdwebClient } from "../client/client.js";
13
import { type Store, createStore } from "../reactive/store.js";
4+
import { getThirdwebDomains } from "../utils/domains.js";
25
import type { Hex } from "../utils/encoding/hex.js";
6+
import { getClientFetch } from "../utils/fetch.js";
37

48
export type StoredTransaction = {
59
transactionHash: Hex;
610
chainId: number;
11+
receipt?: {
12+
status: "success" | "failed";
13+
to: string;
14+
};
715
};
816

917
const transactionsByAddress = new Map<string, Store<StoredTransaction[]>>();
@@ -33,6 +41,7 @@ export function getTransactionStore(
3341

3442
const newStore = createStore<StoredTransaction[]>([]);
3543
transactionsByAddress.set(address, newStore);
44+
3645
return newStore;
3746
}
3847

@@ -54,3 +63,44 @@ export function addTransactionToStore(options: {
5463

5564
transactionsByAddress.set(address, tranasctionStore);
5665
}
66+
67+
/**
68+
* @internal for now
69+
*/
70+
export async function getPastTransactions(options: {
71+
walletAddress: string;
72+
chain: Chain;
73+
client: ThirdwebClient;
74+
}): Promise<StoredTransaction[]> {
75+
const { walletAddress, chain, client } = options;
76+
const oneMonthsAgoInSeconds = Math.floor(
77+
(Date.now() - 1 * 30 * 24 * 60 * 60 * 1000) / 1000,
78+
);
79+
const url = new URL(
80+
`https://${getThirdwebDomains().insight}/v1/wallets/${walletAddress}/transactions`,
81+
);
82+
url.searchParams.set("limit", "10");
83+
url.searchParams.set("chain", chain.id.toString());
84+
url.searchParams.set(
85+
"filter_block_timestamp_gte",
86+
oneMonthsAgoInSeconds.toString(),
87+
);
88+
const clientFetch = getClientFetch(client);
89+
const result = await clientFetch(url.toString());
90+
const json = (await result.json()) as {
91+
data: {
92+
chain_id: number;
93+
hash: string;
94+
status: number;
95+
to_address: string;
96+
}[];
97+
};
98+
return json.data.map((tx) => ({
99+
transactionHash: tx.hash as Hex,
100+
chainId: tx.chain_id,
101+
receipt: {
102+
status: tx.status === 1 ? "success" : "failed",
103+
to: tx.to_address,
104+
},
105+
}));
106+
}

packages/thirdweb/src/utils/domain.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ describe("Thirdweb Domains", () => {
1515
storage: "storage.thirdweb.com",
1616
bundler: "bundler.thirdweb.com",
1717
analytics: "c.thirdweb.com",
18+
insight: "insight.thirdweb.com",
1819
};
1920

2021
beforeEach(() => {

packages/thirdweb/src/utils/domains.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,11 @@ type DomainOverrides = {
3434
* @default "c.thirdweb.com"
3535
*/
3636
analytics?: string;
37+
/**
38+
* The base URL for the insight server.
39+
* @default "insight.thirdweb.com"
40+
*/
41+
insight?: string;
3742
};
3843

3944
export const DEFAULT_RPC_URL = "rpc.thirdweb.com";
@@ -43,7 +48,7 @@ const DEFAULT_PAY_URL = "pay.thirdweb.com";
4348
const DEFAULT_STORAGE_URL = "storage.thirdweb.com";
4449
const DEFAULT_BUNDLER_URL = "bundler.thirdweb.com";
4550
const DEFAULT_ANALYTICS_URL = "c.thirdweb.com";
46-
51+
const DEFAULT_INSIGHT_URL = "insight.thirdweb.com";
4752
let domains: { [k in keyof DomainOverrides]-?: string } = {
4853
rpc: DEFAULT_RPC_URL,
4954
inAppWallet: DEFAULT_IN_APP_WALLET_URL,
@@ -52,6 +57,7 @@ let domains: { [k in keyof DomainOverrides]-?: string } = {
5257
storage: DEFAULT_STORAGE_URL,
5358
bundler: DEFAULT_BUNDLER_URL,
5459
analytics: DEFAULT_ANALYTICS_URL,
60+
insight: DEFAULT_INSIGHT_URL,
5561
};
5662

5763
/**
@@ -66,6 +72,7 @@ export const setThirdwebDomains = (DomainOverrides: DomainOverrides) => {
6672
storage: DomainOverrides.storage ?? DEFAULT_STORAGE_URL,
6773
bundler: DomainOverrides.bundler ?? DEFAULT_BUNDLER_URL,
6874
analytics: DomainOverrides.analytics ?? DEFAULT_ANALYTICS_URL,
75+
insight: DomainOverrides.insight ?? DEFAULT_INSIGHT_URL,
6976
};
7077
};
7178

0 commit comments

Comments
 (0)