Skip to content

Commit 2afb9f0

Browse files
authored
Merge branch 'main' into yash/compute-ref-deployment-info
2 parents 577bb50 + 00b6c2e commit 2afb9f0

File tree

76 files changed

+1425
-1716
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

76 files changed

+1425
-1716
lines changed

.changeset/four-ducks-try.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
"thirdweb": minor
3+
---
4+
5+
Feature: Allows hiding the "Linked Profiles" button in the `ConnectButton` Details Modal
6+
7+
```tsx
8+
<ConnectButton detailsModal={{ manageWallet: { allowLinkingProfiles: false } }} />
9+
```

.changeset/shy-bats-eat.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+
Validate getContract params

apps/dashboard/src/@/components/blocks/Img.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ export function Img(props: imgElementProps) {
6565
"fade-in-0 object-cover transition-opacity duration-300",
6666
className,
6767
)}
68+
decoding="async"
6869
/>
6970

7071
{status !== "loaded" && (
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import type { Meta, StoryObj } from "@storybook/react";
2+
import { useState } from "react";
3+
import { BadgeContainer, mobileViewport } from "../../../stories/utils";
4+
import { MultiNetworkSelector } from "./NetworkSelectors";
5+
6+
const meta = {
7+
title: "blocks/Cards/MultiNetworkSelector",
8+
component: Story,
9+
parameters: {
10+
nextjs: {
11+
appDirectory: true,
12+
},
13+
},
14+
} satisfies Meta<typeof Story>;
15+
16+
export default meta;
17+
type Story = StoryObj<typeof meta>;
18+
19+
export const Desktop: Story = {
20+
args: {},
21+
};
22+
23+
export const Mobile: Story = {
24+
args: {},
25+
parameters: {
26+
viewport: mobileViewport("iphone14"),
27+
},
28+
};
29+
30+
function Story() {
31+
return (
32+
<div className="container flex max-w-[1000px] flex-col gap-8 lg:p-10">
33+
<Variant label="No Chains selected by default" selectedChainIds={[]} />
34+
<Variant
35+
label="Polygon, Ethereum selected by default"
36+
selectedChainIds={[1, 137]}
37+
/>
38+
</div>
39+
);
40+
}
41+
42+
function Variant(props: {
43+
label: string;
44+
selectedChainIds: number[];
45+
}) {
46+
const [chainIds, setChainIds] = useState<number[]>(props.selectedChainIds);
47+
return (
48+
<BadgeContainer label={props.label}>
49+
<MultiNetworkSelector
50+
selectedChainIds={chainIds}
51+
onChange={setChainIds}
52+
/>
53+
</BadgeContainer>
54+
);
55+
}

apps/dashboard/src/@/components/blocks/NetworkSelectors.tsx

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { MultiSelect } from "@/components/blocks/multi-select";
44
import { SelectWithSearch } from "@/components/blocks/select-with-search";
55
import { Badge } from "@/components/ui/badge";
66
import { useCallback, useMemo } from "react";
7+
import { ChainIcon } from "../../../components/icons/ChainIcon";
78
import { useAllChainsData } from "../../../hooks/chains/allChains";
89

910
function cleanChainName(chainName: string) {
@@ -51,7 +52,7 @@ export function MultiNetworkSelector(props: {
5152

5253
return (
5354
<div className="flex justify-between gap-4">
54-
<span className="grow truncate text-left">
55+
<span className="flex grow gap-2 truncate text-left">
5556
{cleanChainName(chain.name)}
5657
</span>
5758
<Badge variant="outline" className="gap-2">
@@ -133,8 +134,15 @@ export function SingleNetworkSelector(props: {
133134

134135
return (
135136
<div className="flex justify-between gap-4">
136-
<span className="grow truncate text-left">{chain.name}</span>
137-
<Badge variant="outline" className="gap-2">
137+
<span className="flex grow gap-2 truncate text-left">
138+
<ChainIcon
139+
className="size-5"
140+
ipfsSrc={chain.icon?.url}
141+
loading="lazy"
142+
/>
143+
{chain.name}
144+
</span>
145+
<Badge variant="outline" className="gap-2 max-sm:hidden">
138146
<span className="text-muted-foreground">Chain ID</span>
139147
{chain.chainId}
140148
</Badge>
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import type { Meta, StoryObj } from "@storybook/react";
2+
import { useState } from "react";
3+
import { BadgeContainer, mobileViewport } from "../../../stories/utils";
4+
import { SingleNetworkSelector } from "./NetworkSelectors";
5+
6+
const meta = {
7+
title: "blocks/Cards/SingleNetworkSelector",
8+
component: Story,
9+
parameters: {
10+
nextjs: {
11+
appDirectory: true,
12+
},
13+
},
14+
} satisfies Meta<typeof Story>;
15+
16+
export default meta;
17+
type Story = StoryObj<typeof meta>;
18+
19+
export const Desktop: Story = {
20+
args: {},
21+
};
22+
23+
export const Mobile: Story = {
24+
args: {},
25+
parameters: {
26+
viewport: mobileViewport("iphone14"),
27+
},
28+
};
29+
30+
function Story() {
31+
return (
32+
<div className="container flex max-w-[1000px] flex-col gap-8 lg:p-10">
33+
<Variant label="No Chain ID selected by default" chainId={undefined} />
34+
<Variant label="Polygon selected by default" chainId={137} />
35+
<Variant
36+
label="Show certain chains only"
37+
chainId={undefined}
38+
chainIds={[1, 137, 10]}
39+
/>
40+
</div>
41+
);
42+
}
43+
44+
function Variant(props: {
45+
label: string;
46+
chainId: number | undefined;
47+
chainIds?: number[];
48+
}) {
49+
const [chainId, setChainId] = useState<number | undefined>(props.chainId);
50+
return (
51+
<BadgeContainer label={props.label}>
52+
<SingleNetworkSelector
53+
chainId={chainId}
54+
onChange={setChainId}
55+
chainIds={props.chainIds}
56+
/>
57+
</BadgeContainer>
58+
);
59+
}

apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/_layout/metadata-header.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ export const MetadataHeader: React.FC<MetadataHeaderProps> = ({
8585
href={`/${chain.slug}`}
8686
className="flex w-fit shrink-0 items-center gap-2 rounded-3xl border border-border bg-muted/50 px-2.5 py-1.5 hover:bg-muted"
8787
>
88-
<ChainIcon ipfsSrc={chain.icon?.url} size={16} />
88+
<ChainIcon ipfsSrc={chain.icon?.url} className="size-4" />
8989
{cleanedChainName && (
9090
<span className="text-xs">{cleanedChainName}</span>
9191
)}

apps/dashboard/src/app/(dashboard)/(chain)/components/server/chain-icon.tsx

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,7 @@ import { DASHBOARD_THIRDWEB_SECRET_KEY } from "@/constants/env";
44
import { getThirdwebClient } from "@/constants/thirdweb.server";
55
import { resolveSchemeWithErrorHandler } from "@/lib/resolveSchemeWithErrorHandler";
66
import { cn } from "@/lib/utils";
7-
8-
const fallbackChainIcon =
9-
"data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iOTYiIGhlaWdodD0iOTYiIHZpZXdCb3g9IjAgMCA5NiA5NiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTY4LjE1MTkgNzUuNzM3MkM2Mi4yOTQzIDc5Ljk5MyA1NS4yMzk3IDgyLjI4NTIgNDcuOTk5MyA4Mi4yODUyQzQwLjc1ODkgODIuMjg1MiAzMy43MDQzIDc5Ljk5MyAyNy44NDY2IDc1LjczNzJNNjMuMDI5MSAxNy4xODM3QzY5LjUzNjggMjAuMzU3NyA3NC44NzI2IDI1LjUxMDQgNzguMjcxOCAzMS45MDMzQzgxLjY3MDkgMzguMjk2MiA4Mi45NTkgNDUuNjAxMiA4MS45NTEzIDUyLjc3MTFNMTQuMDQ3NiA1Mi43NzA4QzEzLjAzOTkgNDUuNjAwOCAxNC4zMjggMzguMjk1OSAxNy43MjcxIDMxLjkwM0MyMS4xMjYzIDI1LjUxMDEgMjYuNDYyMSAyMC4zNTczIDMyLjk2OTggMTcuMTgzM000Ni4wNTk4IDI5LjM2NzVMMjkuMzY3MyA0Ni4wNkMyOC42ODg1IDQ2LjczODkgMjguMzQ5IDQ3LjA3ODMgMjguMjIxOCA0Ny40Njk3QzI4LjExIDQ3LjgxNCAyOC4xMSA0OC4xODQ5IDI4LjIyMTggNDguNTI5MkMyOC4zNDkgNDguOTIwNiAyOC42ODg1IDQ5LjI2MDEgMjkuMzY3MyA0OS45MzlMNDYuMDU5OCA2Ni42MzE0QzQ2LjczODcgNjcuMzEwMyA0Ny4wNzgxIDY3LjY0OTcgNDcuNDY5NSA2Ny43NzY5QzQ3LjgxMzggNjcuODg4OCA0OC4xODQ3IDY3Ljg4ODggNDguNTI5IDY3Ljc3NjlDNDguOTIwNCA2Ny42NDk3IDQ5LjI1OTkgNjcuMzEwMyA0OS45Mzg4IDY2LjYzMTRMNjYuNjMxMiA0OS45MzlDNjcuMzEwMSA0OS4yNjAxIDY3LjY0OTUgNDguOTIwNiA2Ny43NzY3IDQ4LjUyOTJDNjcuODg4NiA0OC4xODQ5IDY3Ljg4ODYgNDcuODE0IDY3Ljc3NjcgNDcuNDY5N0M2Ny42NDk1IDQ3LjA3ODMgNjcuMzEwMSA0Ni43Mzg5IDY2LjYzMTIgNDYuMDZMNDkuOTM4OCAyOS4zNjc1QzQ5LjI1OTkgMjguNjg4NyA0OC45MjA0IDI4LjM0OTIgNDguNTI5IDI4LjIyMkM0OC4xODQ3IDI4LjExMDIgNDcuODEzOCAyOC4xMTAyIDQ3LjQ2OTUgMjguMjIyQzQ3LjA3ODEgMjguMzQ5MiA0Ni43Mzg3IDI4LjY4ODcgNDYuMDU5OCAyOS4zNjc1WiIgc3Ryb2tlPSIjNDA0MDQwIiBzdHJva2Utd2lkdGg9IjYuODU3MTQiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIvPgo8L3N2Zz4K";
7+
import { fallbackChainIcon } from "../../../../../utils/chain-icons";
108

119
export async function ChainIcon(props: {
1210
iconUrl?: string;
@@ -35,6 +33,11 @@ export async function ChainIcon(props: {
3533

3634
if (res?.status === 200) {
3735
imageLink = resolved;
36+
// check that its an image
37+
const contentType = res.headers.get("content-type");
38+
if (!contentType?.startsWith("image")) {
39+
imageLink = fallbackChainIcon;
40+
}
3841
}
3942
}
4043

apps/dashboard/src/app/nebula-app/(app)/components/ChatPageContent.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ export function ChatPageContent(props: {
2222
}) {
2323
const address = useActiveAccount()?.address;
2424
const activeChain = useActiveWalletChain();
25-
const client = useThirdwebClient();
25+
const client = useThirdwebClient(props.authToken);
2626
const [userHasSubmittedMessage, setUserHasSubmittedMessage] = useState(false);
2727
const [messages, setMessages] = useState<Array<ChatMessage>>(() => {
2828
if (props.session?.history) {

apps/dashboard/src/app/nebula-app/(app)/components/Chats.tsx

Lines changed: 41 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import { ScrollShadow } from "@/components/ui/ScrollShadow/ScrollShadow";
33
import { Spinner } from "@/components/ui/Spinner/Spinner";
44
import { Alert, AlertTitle } from "@/components/ui/alert";
55
import { Button } from "@/components/ui/button";
6-
import { getThirdwebClient } from "@/constants/thirdweb.server";
76
import { cn } from "@/lib/utils";
87
import type { Account as TWAccount } from "@3rdweb-sdk/react/hooks/useApi";
98
import { useMutation } from "@tanstack/react-query";
@@ -17,8 +16,7 @@ import {
1716
import { useEffect, useRef, useState } from "react";
1817
import { toast } from "sonner";
1918
import type { ThirdwebClient } from "thirdweb";
20-
import { sendTransaction } from "thirdweb";
21-
import { useActiveAccount } from "thirdweb/react";
19+
import { useSendTransaction } from "thirdweb/react";
2220
import type { Account } from "thirdweb/wallets";
2321
import { TransactionButton } from "../../../../components/buttons/TransactionButton";
2422
import { MarkdownRenderer } from "../../../../components/contract-components/published-contract/markdown-renderer";
@@ -170,9 +168,10 @@ export function Chats(props: {
170168
{message.text}
171169
</span>
172170
) : message.type === "send_transaction" ? (
173-
<SendTransactionButton
171+
<ExecuteTransaction
174172
txData={message.data}
175173
twAccount={props.twAccount}
174+
client={props.client}
176175
/>
177176
) : (
178177
<span className="leading-loose">{message.text}</span>
@@ -203,6 +202,29 @@ export function Chats(props: {
203202
);
204203
}
205204

205+
function ExecuteTransaction(props: {
206+
txData: SendTransactionOption | null;
207+
twAccount: TWAccount;
208+
client: ThirdwebClient;
209+
}) {
210+
if (!props.txData) {
211+
return (
212+
<Alert variant="destructive">
213+
<AlertCircleIcon className="size-5" />
214+
<AlertTitle>Failed to parse transaction data</AlertTitle>
215+
</Alert>
216+
);
217+
}
218+
219+
return (
220+
<SendTransactionButton
221+
txData={props.txData}
222+
twAccount={props.twAccount}
223+
client={props.client}
224+
/>
225+
);
226+
}
227+
206228
function MessageActions(props: {
207229
authToken: string;
208230
requestId: string;
@@ -297,51 +319,28 @@ function MessageActions(props: {
297319
}
298320

299321
function SendTransactionButton(props: {
300-
txData: SendTransactionOption | null;
322+
txData: SendTransactionOption;
301323
twAccount: TWAccount;
324+
client: ThirdwebClient;
302325
}) {
303-
const account = useActiveAccount();
304-
const chain = useV5DashboardChain(props.txData?.chainId);
305-
306-
const sendTxMutation = useMutation({
307-
mutationFn: () => {
308-
if (!account) {
309-
throw new Error("No active account");
310-
}
311-
312-
if (!props.txData || !chain) {
313-
throw new Error("Invalid transaction");
314-
}
315-
316-
return sendTransaction({
317-
account,
318-
transaction: {
319-
...props.txData,
320-
nonce: Number(props.txData.nonce),
321-
to: props.txData.to || undefined, // Get rid of the potential null value
322-
chain,
323-
client: getThirdwebClient(),
324-
},
325-
});
326-
},
327-
});
328-
329-
if (!props.txData) {
330-
return (
331-
<Alert variant="destructive">
332-
<AlertCircleIcon className="size-5" />
333-
<AlertTitle>Failed to parse transaction data</AlertTitle>
334-
</Alert>
335-
);
336-
}
326+
const { txData } = props;
327+
const sendTransaction = useSendTransaction();
328+
const chain = useV5DashboardChain(txData.chainId);
337329

338330
return (
339331
<TransactionButton
340-
isPending={sendTxMutation.isPending}
332+
isPending={sendTransaction.isPending}
341333
transactionCount={1}
342-
txChainID={props.txData.chainId}
334+
txChainID={txData.chainId}
343335
onClick={() => {
344-
const promise = sendTxMutation.mutateAsync();
336+
const promise = sendTransaction.mutateAsync({
337+
...props.txData,
338+
nonce: Number(txData.nonce),
339+
to: txData.to || undefined, // Get rid of the potential null value
340+
chain: chain,
341+
client: props.client,
342+
});
343+
345344
toast.promise(promise, {
346345
success: "Transaction sent successfully",
347346
error: "Failed to send transaction",

0 commit comments

Comments
 (0)