Skip to content

Commit 63d12c9

Browse files
Handle already connected wallets in 1193 provider
1 parent bdcbe0e commit 63d12c9

File tree

30 files changed

+3319
-88
lines changed

30 files changed

+3319
-88
lines changed

.changeset/many-pants-tease.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+
Handle already connected wallets in 1193 provider

apps/dashboard/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@
6060
"remark-gfm": "4.0.1",
6161
"responsive-rsc": "0.0.7",
6262
"server-only": "^0.0.1",
63-
"shiki": "1.27.0",
63+
"shiki": "3.12.0",
6464
"sonner": "2.0.6",
6565
"spdx-correct": "^3.2.0",
6666
"stripe": "17.7.0",

apps/nebula/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
"react-markdown": "10.1.0",
3333
"remark-gfm": "4.0.1",
3434
"server-only": "^0.0.1",
35-
"shiki": "1.27.0",
35+
"shiki": "3.12.0",
3636
"sonner": "2.0.6",
3737
"tailwind-merge": "^2.6.0",
3838
"tailwindcss-animate": "^1.0.7",

apps/playground-web/package.json

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
{
22
"dependencies": {
33
"@abstract-foundation/agw-react": "^1.6.4",
4+
"@ai-sdk/react": "^2.0.25",
45
"@hookform/resolvers": "^3.9.1",
6+
"@radix-ui/react-avatar": "^1.1.10",
57
"@radix-ui/react-checkbox": "^1.3.2",
68
"@radix-ui/react-collapsible": "^1.1.11",
79
"@radix-ui/react-dialog": "1.1.14",
@@ -14,8 +16,11 @@
1416
"@radix-ui/react-slot": "^1.2.3",
1517
"@radix-ui/react-switch": "^1.2.5",
1618
"@radix-ui/react-tooltip": "1.2.7",
19+
"@radix-ui/react-use-controllable-state": "^1.2.2",
1720
"@tanstack/react-query": "5.81.5",
21+
"@thirdweb-dev/ai-sdk-provider": "workspace:*",
1822
"@workspace/ui": "workspace:*",
23+
"ai": "^5.0.25",
1924
"class-variance-authority": "^0.7.1",
2025
"clsx": "^2.1.1",
2126
"date-fns": "4.1.0",
@@ -35,11 +40,13 @@
3540
"react-pick-color": "^2.0.0",
3641
"remark-gfm": "4.0.1",
3742
"server-only": "^0.0.1",
38-
"shiki": "1.27.0",
43+
"shiki": "3.12.0",
3944
"sonner": "2.0.6",
45+
"streamdown": "^1.1.4",
4046
"tailwind-merge": "^2.6.0",
4147
"thirdweb": "workspace:*",
4248
"use-debounce": "^10.0.5",
49+
"use-stick-to-bottom": "^1.1.1",
4350
"zod": "3.25.75"
4451
},
4552
"devDependencies": {
Lines changed: 297 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,297 @@
1+
"use client";
2+
3+
import { useChat } from "@ai-sdk/react";
4+
import type { ThirdwebAiMessage } from "@thirdweb-dev/ai-sdk-provider";
5+
import { DefaultChatTransport } from "ai";
6+
import { useMemo, useState } from "react";
7+
import { defineChain, prepareTransaction } from "thirdweb";
8+
import {
9+
ConnectButton,
10+
TransactionButton,
11+
useActiveAccount,
12+
} from "thirdweb/react";
13+
import {
14+
Conversation,
15+
ConversationContent,
16+
ConversationScrollButton,
17+
} from "@/components/conversation";
18+
import { Message, MessageContent } from "@/components/message";
19+
import {
20+
PromptInput,
21+
PromptInputSubmit,
22+
PromptInputTextarea,
23+
} from "@/components/prompt-input";
24+
import {
25+
Reasoning,
26+
ReasoningContent,
27+
ReasoningTrigger,
28+
} from "@/components/reasoning";
29+
import { Response } from "@/components/response";
30+
import { Loader } from "../../../../components/loader";
31+
import { THIRDWEB_CLIENT } from "../../../../lib/client";
32+
33+
export function ChatContainer() {
34+
const [sessionId, setSessionId] = useState("");
35+
36+
const { messages, sendMessage, status, addToolResult } =
37+
useChat<ThirdwebAiMessage>({
38+
transport: new DefaultChatTransport({
39+
api: "/api/chat",
40+
}),
41+
onFinish: ({ message }) => {
42+
setSessionId(message.metadata?.session_id ?? "");
43+
},
44+
});
45+
const [input, setInput] = useState("");
46+
47+
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
48+
e.preventDefault();
49+
if (input.trim()) {
50+
sendMessage(
51+
{ text: input },
52+
{
53+
body: {
54+
sessionId,
55+
},
56+
},
57+
);
58+
setInput("");
59+
}
60+
};
61+
62+
return (
63+
<div className="max-w-4xl mx-auto p-6 relative size-full">
64+
<div className="flex flex-col h-[600px] rounded-lg border">
65+
<Conversation>
66+
<ConversationContent>
67+
{messages.length === 0 && (
68+
<div className="h-[300px] flex items-center justify-center text-center text-muted-foreground">
69+
Type a message to start the conversation
70+
</div>
71+
)}
72+
{messages.map((message) => (
73+
<Message from={message.role} key={message.id}>
74+
<MessageContent>
75+
{message.parts.map((part, i) => {
76+
switch (part.type) {
77+
case "text":
78+
return (
79+
<Response key={`${message.id}-${i}`}>
80+
{part.text}
81+
</Response>
82+
);
83+
case "reasoning":
84+
return (
85+
<Reasoning
86+
key={`${message.id}-reasoning-${i}`}
87+
className="w-full"
88+
isStreaming={status === "streaming"}
89+
>
90+
<ReasoningTrigger />
91+
<ReasoningContent className="text-xs text-muted-foreground italic flex flex-col gap-2">
92+
{part.text}
93+
</ReasoningContent>
94+
</Reasoning>
95+
);
96+
case "tool-sign_transaction":
97+
return (
98+
<SignTransactionButton
99+
key={`${message.id}-transaction-${i}`}
100+
input={part.input}
101+
addToolResult={addToolResult}
102+
sendMessage={sendMessage}
103+
toolCallId={part.toolCallId}
104+
sessionId={sessionId}
105+
/>
106+
);
107+
case "tool-sign_swap":
108+
console.log("---sign_swap", part);
109+
return (
110+
<SignSwapButton
111+
key={`${message.id}-swap-${i}`}
112+
input={part.input}
113+
addToolResult={addToolResult}
114+
sendMessage={sendMessage}
115+
toolCallId={part.toolCallId}
116+
sessionId={sessionId}
117+
/>
118+
);
119+
default:
120+
return null;
121+
}
122+
})}
123+
</MessageContent>
124+
</Message>
125+
))}
126+
{status === "submitted" && <Loader />}
127+
</ConversationContent>
128+
<ConversationScrollButton />
129+
</Conversation>
130+
131+
<PromptInput
132+
onSubmit={handleSubmit}
133+
className="mt-4 w-full max-w-2xl mx-auto relative"
134+
>
135+
<PromptInputTextarea
136+
value={input}
137+
placeholder="Say something..."
138+
onChange={(e) => setInput(e.currentTarget.value)}
139+
className="pr-12"
140+
/>
141+
<PromptInputSubmit
142+
status={status === "streaming" ? "streaming" : "ready"}
143+
disabled={!input.trim()}
144+
className="absolute top-4 right-4"
145+
/>
146+
</PromptInput>
147+
</div>
148+
</div>
149+
);
150+
}
151+
152+
type SignTransactionButtonProps = {
153+
input:
154+
| Extract<
155+
ReturnType<
156+
typeof useChat<ThirdwebAiMessage>
157+
>["messages"][number]["parts"][number],
158+
{ type: "tool-sign_transaction" }
159+
>["input"]
160+
| undefined;
161+
addToolResult: ReturnType<typeof useChat<ThirdwebAiMessage>>["addToolResult"];
162+
toolCallId: string;
163+
sendMessage: ReturnType<typeof useChat<ThirdwebAiMessage>>["sendMessage"];
164+
sessionId: string;
165+
};
166+
167+
const SignTransactionButton = (props: SignTransactionButtonProps) => {
168+
const { input, addToolResult, toolCallId, sendMessage, sessionId } = props;
169+
const transactionData: {
170+
chain_id: number;
171+
to: string;
172+
data: `0x${string}`;
173+
value: bigint;
174+
} = useMemo(() => {
175+
return {
176+
chain_id: input?.chain_id || 8453,
177+
to: input?.to || "",
178+
data: (input?.data as `0x${string}`) || "0x",
179+
value: input?.value ? BigInt(input.value) : BigInt(0),
180+
};
181+
}, [input]);
182+
const account = useActiveAccount();
183+
184+
if (!account) {
185+
return <ConnectButton client={THIRDWEB_CLIENT} />;
186+
}
187+
188+
return (
189+
<div className="py-4">
190+
<TransactionButton
191+
style={{
192+
width: "100%",
193+
}}
194+
transaction={() =>
195+
prepareTransaction({
196+
client: THIRDWEB_CLIENT,
197+
chain: defineChain(transactionData.chain_id),
198+
to: transactionData.to,
199+
data: transactionData.data,
200+
value: transactionData.value,
201+
})
202+
}
203+
onTransactionSent={(transaction) => {
204+
addToolResult({
205+
tool: "sign_transaction",
206+
toolCallId,
207+
output: {
208+
transaction_hash: transaction.transactionHash,
209+
chain_id: transaction.chain.id,
210+
},
211+
});
212+
sendMessage(undefined, {
213+
body: {
214+
sessionId,
215+
},
216+
});
217+
}}
218+
>
219+
Sign Transaction
220+
</TransactionButton>
221+
</div>
222+
);
223+
};
224+
225+
type SignSwapButtonProps = {
226+
input:
227+
| Extract<
228+
ReturnType<
229+
typeof useChat<ThirdwebAiMessage>
230+
>["messages"][number]["parts"][number],
231+
{ type: "tool-sign_swap" }
232+
>["input"]
233+
| undefined;
234+
addToolResult: ReturnType<typeof useChat<ThirdwebAiMessage>>["addToolResult"];
235+
toolCallId: string;
236+
sendMessage: ReturnType<typeof useChat<ThirdwebAiMessage>>["sendMessage"];
237+
sessionId: string;
238+
};
239+
const SignSwapButton = (props: SignSwapButtonProps) => {
240+
const { input, addToolResult, toolCallId, sendMessage, sessionId } = props;
241+
const transactionData: {
242+
chain_id: number;
243+
to: string;
244+
data: `0x${string}`;
245+
value: bigint;
246+
} = useMemo(() => {
247+
return {
248+
chain_id: input?.transaction?.chain_id || 8453,
249+
to: input?.transaction?.to || "",
250+
data: (input?.transaction?.data as `0x${string}`) || "0x",
251+
value: input?.transaction?.value
252+
? BigInt(input.transaction.value)
253+
: BigInt(0),
254+
};
255+
}, [input]);
256+
const account = useActiveAccount();
257+
258+
if (!account) {
259+
return <ConnectButton client={THIRDWEB_CLIENT} />;
260+
}
261+
262+
return (
263+
<div className="py-4">
264+
<TransactionButton
265+
style={{
266+
width: "100%",
267+
}}
268+
transaction={() =>
269+
prepareTransaction({
270+
client: THIRDWEB_CLIENT,
271+
chain: defineChain(transactionData.chain_id),
272+
to: transactionData.to,
273+
data: transactionData.data,
274+
value: transactionData.value,
275+
})
276+
}
277+
onTransactionSent={(transaction) => {
278+
addToolResult({
279+
tool: "sign_swap",
280+
toolCallId,
281+
output: {
282+
transaction_hash: transaction.transactionHash,
283+
chain_id: transaction.chain.id,
284+
},
285+
});
286+
sendMessage(undefined, {
287+
body: {
288+
sessionId,
289+
},
290+
});
291+
}}
292+
>
293+
Sign swap
294+
</TransactionButton>
295+
</div>
296+
);
297+
};

0 commit comments

Comments
 (0)