Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/eight-zoos-invent.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"thirdweb": patch
---

Improve Transactions list in connect modal
Fix query cache for block explorers for raw chains
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ describe.runIf(process.env.TW_SECRET_KEY)("erc721.getNFTs", () => {
"value": "tan",
},
],
"contract_address": "0x8a90cab2b38dba80c64b7734e58ee1db38b8992e",
"description": "A community-driven collectibles project featuring art by Burnt Toast. Doodles come in a joyful range of colors, traits and sizes with a collection size of 10,000. Each Doodle allows its owner to vote for experiences and activations paid for by the Doodles Community Treasury. Burnt Toast is the working alias for Scott Martin, a Canadian–based illustrator, designer, animator and muralist.",
"image": "ipfs://QmUEfFfwAh4wyB5UfHCVPUxis4j4Q4kJXtm5x5p3g1fVUn",
"image_url": "ipfs://QmUEfFfwAh4wyB5UfHCVPUxis4j4Q4kJXtm5x5p3g1fVUn",
Expand Down Expand Up @@ -78,6 +79,7 @@ describe.runIf(process.env.TW_SECRET_KEY)("erc721.getNFTs", () => {
"value": "gradient 2",
},
],
"contract_address": "0x8a90cab2b38dba80c64b7734e58ee1db38b8992e",
"description": "A community-driven collectibles project featuring art by Burnt Toast. Doodles come in a joyful range of colors, traits and sizes with a collection size of 10,000. Each Doodle allows its owner to vote for experiences and activations paid for by the Doodles Community Treasury. Burnt Toast is the working alias for Scott Martin, a Canadian–based illustrator, designer, animator and muralist.",
"image": "ipfs://QmTDxnzcvj2p3xBrKcGv1wxoyhAn2yzCQnZZ9LmFjReuH9",
"image_url": "ipfs://QmTDxnzcvj2p3xBrKcGv1wxoyhAn2yzCQnZZ9LmFjReuH9",
Expand Down Expand Up @@ -115,6 +117,7 @@ describe.runIf(process.env.TW_SECRET_KEY)("erc721.getNFTs", () => {
"value": "purple",
},
],
"contract_address": "0x8a90cab2b38dba80c64b7734e58ee1db38b8992e",
"description": "A community-driven collectibles project featuring art by Burnt Toast. Doodles come in a joyful range of colors, traits and sizes with a collection size of 10,000. Each Doodle allows its owner to vote for experiences and activations paid for by the Doodles Community Treasury. Burnt Toast is the working alias for Scott Martin, a Canadian–based illustrator, designer, animator and muralist.",
"image": "ipfs://QmbvZ2hbF3nEq5r3ijMEiSGssAmJvtyFwiejTAGHv74LR5",
"image_url": "ipfs://QmbvZ2hbF3nEq5r3ijMEiSGssAmJvtyFwiejTAGHv74LR5",
Expand Down Expand Up @@ -152,6 +155,7 @@ describe.runIf(process.env.TW_SECRET_KEY)("erc721.getNFTs", () => {
"value": "pale",
},
],
"contract_address": "0x8a90cab2b38dba80c64b7734e58ee1db38b8992e",
"description": "A community-driven collectibles project featuring art by Burnt Toast. Doodles come in a joyful range of colors, traits and sizes with a collection size of 10,000. Each Doodle allows its owner to vote for experiences and activations paid for by the Doodles Community Treasury. Burnt Toast is the working alias for Scott Martin, a Canadian–based illustrator, designer, animator and muralist.",
"image": "ipfs://QmVpwaCqLut3wqwB5KSQr2fGnbLuJt5e3LhNvzvcisewZB",
"image_url": "ipfs://QmVpwaCqLut3wqwB5KSQr2fGnbLuJt5e3LhNvzvcisewZB",
Expand Down Expand Up @@ -189,6 +193,7 @@ describe.runIf(process.env.TW_SECRET_KEY)("erc721.getNFTs", () => {
"value": "purple",
},
],
"contract_address": "0x8a90cab2b38dba80c64b7734e58ee1db38b8992e",
"description": "A community-driven collectibles project featuring art by Burnt Toast. Doodles come in a joyful range of colors, traits and sizes with a collection size of 10,000. Each Doodle allows its owner to vote for experiences and activations paid for by the Doodles Community Treasury. Burnt Toast is the working alias for Scott Martin, a Canadian–based illustrator, designer, animator and muralist.",
"image": "ipfs://QmcyuFVLbfBmSeQ9ynu4dk67r97nB1abEekotuVuRGWedm",
"image_url": "ipfs://QmcyuFVLbfBmSeQ9ynu4dk67r97nB1abEekotuVuRGWedm",
Expand Down
48 changes: 20 additions & 28 deletions packages/thirdweb/src/react/core/hooks/others/useChainQuery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,15 @@ export function useChainName(chain?: Chain) {
// only if we have a chain and no chain name!
const isEnabled = !!chain && !chain.name;
const chainQuery = useQuery({
enabled: isEnabled,
queryFn: async () => {
if (!chain) {
throw new Error("chain is required");
}
return convertApiChainToChain(await getChainMetadata(chain));
},
queryKey: ["chain", chain?.id],
...getQueryOptions(chain),
enabled: isEnabled,
retry: false,
// 1 hour
staleTime: 60 * 60 * 1000,
});

return {
Expand All @@ -39,17 +37,15 @@ export function useChainIconUrl(chain?: Chain) {
const isEnabled = !!chain && !chain.icon?.url;
const chainQuery = useQuery({
// only if we have a chain and no chain icon url!
enabled: isEnabled,
queryFn: async () => {
if (!chain) {
throw new Error("chain is required");
}
return convertApiChainToChain(await getChainMetadata(chain));
},
queryKey: ["chain", chain?.id],
...getQueryOptions(chain),
enabled: isEnabled,
retry: false,
// 1 hour
staleTime: 60 * 60 * 1000,
});

return {
Expand All @@ -67,17 +63,14 @@ export function useChainFaucets(chain?: Chain) {
chain.id !== 1337;

const chainQuery = useQuery({
enabled: isEnabled,
queryFn: async () => {
if (!chain) {
throw new Error("chain is required");
}
return convertApiChainToChain(await getChainMetadata(chain));
return getChainMetadata(chain);
},
queryKey: ["chain", chain?.id],
retry: false,
// 1 hour
staleTime: 60 * 60 * 1000,
...getQueryOptions(chain),
enabled: isEnabled,
});

return {
Expand All @@ -90,18 +83,14 @@ export function useChainSymbol(chain?: Chain) {
// only if we have a chain and no chain icon url!
const isEnabled = !!chain && !chain.nativeCurrency?.symbol;
const chainQuery = useQuery({
// only if we have a chain and no chain icon url!
enabled: isEnabled,
queryFn: async () => {
if (!chain) {
throw new Error("chain is required");
}
return convertApiChainToChain(await getChainMetadata(chain));
return getChainMetadata(chain);
},
queryKey: ["chain", chain?.id],
retry: false,
// 1 hour
staleTime: 60 * 60 * 1000,
...getQueryOptions(chain),
enabled: isEnabled,
});

return {
Expand All @@ -116,21 +105,24 @@ export function useChainExplorers(chain?: Chain) {
const isEnabled = !!chain && !chain.blockExplorers?.length;

const chainQuery = useQuery({
enabled: isEnabled,
queryFn: async () => {
if (!chain) {
throw new Error("chain is required");
}
return convertApiChainToChain(await getChainMetadata(chain));
return getChainMetadata(chain);
},
queryKey: ["chain", chain?.id],
retry: false,
// 1 hour
staleTime: 60 * 60 * 1000,
...getQueryOptions(chain),
enabled: isEnabled,
});

const toChain = chainQuery.data
? convertApiChainToChain(chainQuery.data)
: undefined;
return {
explorers: chain?.blockExplorers ?? chainQuery.data?.blockExplorers ?? [],
explorers:
chain?.blockExplorers && chain?.blockExplorers?.length > 0
? chain?.blockExplorers
: (toChain?.blockExplorers ?? []),
isLoading: isEnabled && chainQuery.isLoading,
};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ import { useActiveWalletChain } from "../../../../core/hooks/wallets/useActiveWa
import { Container } from "../../components/basic.js";
import { Button } from "../../components/buttons.js";
import { ChainIcon } from "../../components/ChainIcon.js";
import { ChainName } from "../../components/ChainName.js";
import { Spacer } from "../../components/Spacer.js";
import { Spinner } from "../../components/Spinner.js";
import { Text } from "../../components/text.js";
Expand Down Expand Up @@ -141,6 +140,7 @@ function TransactionButton(props: {
});
const chainIconQuery = useChainIconUrl(getCachedChain(props.tx.chainId));
const receipt = props.tx.receipt ?? fetchedReceipt;
const decoded = props.tx.decoded;

const content = (
<TxButton
Expand Down Expand Up @@ -181,9 +181,7 @@ function TransactionButton(props: {
}}
>
<Text color="primaryText" size="sm">
{receipt?.to
? `Interacted with ${shortenHex(receipt?.to, 4)}`
: `Hash: ${shortenHex(props.tx.transactionHash, 4)}`}
{decoded ? decoded.name : `Transaction Sent`}
</Text>
</Container>

Expand All @@ -198,11 +196,11 @@ function TransactionButton(props: {
justifyContent: "space-between",
}}
>
<ChainName
chain={getCachedChain(props.tx.chainId)}
client={props.client}
size="xs"
/>
<Text color="secondaryText" size="xs">
{receipt?.to
? shortenHex(receipt?.to, 4)
: shortenHex(props.tx.transactionHash, 4)}
</Text>
</Container>
</div>
</Container>
Expand Down
11 changes: 10 additions & 1 deletion packages/thirdweb/src/transaction/transaction-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,13 @@ export type StoredTransaction = {
status: "success" | "failed";
to: string;
};
decoded?: {
name: string;
signature: string;
inputs?: {
[key: string]: unknown;
};
};
};

const transactionsByAddress = new Map<string, Store<StoredTransaction[]>>();
Expand Down Expand Up @@ -81,6 +88,7 @@ export async function getPastTransactions(options: {
queryOptions: {
filter_block_timestamp_gte: oneMonthsAgoInSeconds,
limit: 20,
decode: true,
},
walletAddress,
});
Expand All @@ -90,9 +98,10 @@ export async function getPastTransactions(options: {
? Number(tx.chain_id)
: (tx.chain_id as number),
receipt: {
status: tx.status === 1 ? "success" : "failed",
status: tx.status === 0 ? "failed" : "success",
to: tx.to_address,
},
transactionHash: tx.hash as Hex,
decoded: tx.decoded,
}));
Comment on lines +101 to 106
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Fix transaction status regression.

tx.status === 0 ? "failed" : "success" marks failures as success whenever the API returns "0" (string) or leaves the field undefined for pending receipts. Normalize the value before comparing.

-    receipt: {
-      status: tx.status === 0 ? "failed" : "success",
+    receipt: {
+      status:
+        (typeof tx.status === "string" ? Number(tx.status) : tx.status) === 1
+          ? "success"
+          : "failed",
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
status: tx.status === 0 ? "failed" : "success",
to: tx.to_address,
},
transactionHash: tx.hash as Hex,
decoded: tx.decoded,
}));
receipt: {
status:
(typeof tx.status === "string" ? Number(tx.status) : tx.status) === 1
? "success"
: "failed",
to: tx.to_address,
},
transactionHash: tx.hash as Hex,
decoded: tx.decoded,
}));
🤖 Prompt for AI Agents
In packages/thirdweb/src/transaction/transaction-store.ts around lines 101 to
106, the status mapping uses strict numeric comparison (tx.status === 0) which
fails when the API returns string "0" or undefined; normalize tx.status to a
number first (e.g., const s = Number(tx.status)) and then set status to "failed"
if s === 0, "success" if s === 1, and "pending" (or an appropriate default) if s
is NaN/undefined so pending receipts aren't marked as success.

}
Loading