Skip to content

Commit 1c89599

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

File tree

33 files changed

+3459
-90
lines changed

33 files changed

+3459
-90
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: 295 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,295 @@
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 { THIRDWEB_CLIENT } from "../../../../lib/client";
31+
32+
export function ChatContainer() {
33+
const [sessionId, setSessionId] = useState("");
34+
35+
const { messages, sendMessage, status, addToolResult } =
36+
useChat<ThirdwebAiMessage>({
37+
transport: new DefaultChatTransport({
38+
api: "/api/chat",
39+
}),
40+
onFinish: ({ message }) => {
41+
setSessionId(message.metadata?.session_id ?? "");
42+
},
43+
});
44+
const [input, setInput] = useState("");
45+
46+
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
47+
e.preventDefault();
48+
if (input.trim()) {
49+
sendMessage(
50+
{ text: input },
51+
{
52+
body: {
53+
sessionId,
54+
},
55+
},
56+
);
57+
setInput("");
58+
}
59+
};
60+
61+
return (
62+
<div className="max-w-4xl mx-auto p-6 relative size-full">
63+
<div className="flex flex-col h-[600px] rounded-lg border">
64+
<Conversation>
65+
<ConversationContent>
66+
{messages.length === 0 && (
67+
<div className="h-[300px] flex items-center justify-center text-center text-muted-foreground">
68+
Type a message to start the conversation
69+
</div>
70+
)}
71+
{messages.map((message) => (
72+
<Message from={message.role} key={message.id}>
73+
<MessageContent>
74+
{message.parts.map((part, i) => {
75+
switch (part.type) {
76+
case "text":
77+
return (
78+
<Response key={`${message.id}-${i}`}>
79+
{part.text}
80+
</Response>
81+
);
82+
case "reasoning":
83+
return (
84+
<Reasoning
85+
key={`${message.id}-reasoning-${i}`}
86+
className="w-full"
87+
isStreaming={status === "streaming"}
88+
>
89+
<ReasoningTrigger />
90+
<ReasoningContent className="text-xs text-muted-foreground italic flex flex-col gap-2">
91+
{part.text}
92+
</ReasoningContent>
93+
</Reasoning>
94+
);
95+
case "tool-sign_transaction":
96+
return (
97+
<SignTransactionButton
98+
key={`${message.id}-transaction-${i}`}
99+
input={part.input}
100+
addToolResult={addToolResult}
101+
sendMessage={sendMessage}
102+
toolCallId={part.toolCallId}
103+
sessionId={sessionId}
104+
/>
105+
);
106+
case "tool-sign_swap":
107+
console.log("---sign_swap", part);
108+
return (
109+
<SignSwapButton
110+
key={`${message.id}-swap-${i}`}
111+
input={part.input}
112+
addToolResult={addToolResult}
113+
sendMessage={sendMessage}
114+
toolCallId={part.toolCallId}
115+
sessionId={sessionId}
116+
/>
117+
);
118+
default:
119+
return null;
120+
}
121+
})}
122+
</MessageContent>
123+
</Message>
124+
))}
125+
</ConversationContent>
126+
<ConversationScrollButton />
127+
</Conversation>
128+
129+
<PromptInput
130+
onSubmit={handleSubmit}
131+
className="mt-4 w-full max-w-2xl mx-auto relative"
132+
>
133+
<PromptInputTextarea
134+
value={input}
135+
placeholder="Say something..."
136+
onChange={(e) => setInput(e.currentTarget.value)}
137+
className="pr-12"
138+
/>
139+
<PromptInputSubmit
140+
status={status === "streaming" ? "streaming" : "ready"}
141+
disabled={!input.trim()}
142+
className="absolute top-4 right-4"
143+
/>
144+
</PromptInput>
145+
</div>
146+
</div>
147+
);
148+
}
149+
150+
type SignTransactionButtonProps = {
151+
input:
152+
| Extract<
153+
ReturnType<
154+
typeof useChat<ThirdwebAiMessage>
155+
>["messages"][number]["parts"][number],
156+
{ type: "tool-sign_transaction" }
157+
>["input"]
158+
| undefined;
159+
addToolResult: ReturnType<typeof useChat<ThirdwebAiMessage>>["addToolResult"];
160+
toolCallId: string;
161+
sendMessage: ReturnType<typeof useChat<ThirdwebAiMessage>>["sendMessage"];
162+
sessionId: string;
163+
};
164+
165+
const SignTransactionButton = (props: SignTransactionButtonProps) => {
166+
const { input, addToolResult, toolCallId, sendMessage, sessionId } = props;
167+
const transactionData: {
168+
chain_id: number;
169+
to: string;
170+
data: `0x${string}`;
171+
value: bigint;
172+
} = useMemo(() => {
173+
return {
174+
chain_id: input?.chain_id || 8453,
175+
to: input?.to || "",
176+
data: (input?.data as `0x${string}`) || "0x",
177+
value: input?.value ? BigInt(input.value) : BigInt(0),
178+
};
179+
}, [input]);
180+
const account = useActiveAccount();
181+
182+
if (!account) {
183+
return <ConnectButton client={THIRDWEB_CLIENT} />;
184+
}
185+
186+
return (
187+
<div className="py-4">
188+
<TransactionButton
189+
style={{
190+
width: "100%",
191+
}}
192+
transaction={() =>
193+
prepareTransaction({
194+
client: THIRDWEB_CLIENT,
195+
chain: defineChain(transactionData.chain_id),
196+
to: transactionData.to,
197+
data: transactionData.data,
198+
value: transactionData.value,
199+
})
200+
}
201+
onTransactionSent={(transaction) => {
202+
addToolResult({
203+
tool: "sign_transaction",
204+
toolCallId,
205+
output: {
206+
transaction_hash: transaction.transactionHash,
207+
chain_id: transaction.chain.id,
208+
},
209+
});
210+
sendMessage(undefined, {
211+
body: {
212+
sessionId,
213+
},
214+
});
215+
}}
216+
>
217+
Sign Transaction
218+
</TransactionButton>
219+
</div>
220+
);
221+
};
222+
223+
type SignSwapButtonProps = {
224+
input:
225+
| Extract<
226+
ReturnType<
227+
typeof useChat<ThirdwebAiMessage>
228+
>["messages"][number]["parts"][number],
229+
{ type: "tool-sign_swap" }
230+
>["input"]
231+
| undefined;
232+
addToolResult: ReturnType<typeof useChat<ThirdwebAiMessage>>["addToolResult"];
233+
toolCallId: string;
234+
sendMessage: ReturnType<typeof useChat<ThirdwebAiMessage>>["sendMessage"];
235+
sessionId: string;
236+
};
237+
const SignSwapButton = (props: SignSwapButtonProps) => {
238+
const { input, addToolResult, toolCallId, sendMessage, sessionId } = props;
239+
const transactionData: {
240+
chain_id: number;
241+
to: string;
242+
data: `0x${string}`;
243+
value: bigint;
244+
} = useMemo(() => {
245+
return {
246+
chain_id: input?.transaction?.chain_id || 8453,
247+
to: input?.transaction?.to || "",
248+
data: (input?.transaction?.data as `0x${string}`) || "0x",
249+
value: input?.transaction?.value
250+
? BigInt(input.transaction.value)
251+
: BigInt(0),
252+
};
253+
}, [input]);
254+
const account = useActiveAccount();
255+
256+
if (!account) {
257+
return <ConnectButton client={THIRDWEB_CLIENT} />;
258+
}
259+
260+
return (
261+
<div className="py-4">
262+
<TransactionButton
263+
style={{
264+
width: "100%",
265+
}}
266+
transaction={() =>
267+
prepareTransaction({
268+
client: THIRDWEB_CLIENT,
269+
chain: defineChain(transactionData.chain_id),
270+
to: transactionData.to,
271+
data: transactionData.data,
272+
value: transactionData.value,
273+
})
274+
}
275+
onTransactionSent={(transaction) => {
276+
addToolResult({
277+
tool: "sign_swap",
278+
toolCallId,
279+
output: {
280+
transaction_hash: transaction.transactionHash,
281+
chain_id: transaction.chain.id,
282+
},
283+
});
284+
sendMessage(undefined, {
285+
body: {
286+
sessionId,
287+
},
288+
});
289+
}}
290+
>
291+
Sign swap
292+
</TransactionButton>
293+
</div>
294+
);
295+
};

0 commit comments

Comments
 (0)