Skip to content

Commit 61d7598

Browse files
committed
Merge branch 'main' into bc/turborepo
2 parents f6db388 + 5eb89a4 commit 61d7598

File tree

19 files changed

+294
-110
lines changed

19 files changed

+294
-110
lines changed

.changeset/breezy-knives-learn.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@turnkey/eip-1193-provider": minor
3+
---
4+
5+
Add support for @turnkey/sdk-browser clients

examples/with-biconomy-aa/eip1193.png

304 KB
Loading

examples/with-eip-1193-provider/README.md

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
# Example: `with-eip-1193-provider`
22

3-
This example shows how to the `@turnkey/eip-1193-provider` package.
3+
This example shows how to use the `@turnkey/eip-1193-provider` package.
4+
5+
<img src="./img/eip1193.png" alt="primary screenshot" width="1000px">
46

57
## Getting started
68

Loading
133 KB
Loading
134 KB
Loading
83.6 KB
Loading
104 KB
Loading

examples/with-eip-1193-provider/package.json

+4-2
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,12 @@
1616
"@radix-ui/react-select": "^2.0.0",
1717
"@radix-ui/react-slot": "^1.0.2",
1818
"@radix-ui/react-toast": "^1.1.5",
19-
"@turnkey/api-key-stamper": "workspace:^",
19+
"@turnkey/sdk-server": "workspace:*",
20+
"@turnkey/sdk-react": "workspace:*",
21+
"@turnkey/api-key-stamper": "workspace:*",
2022
"@turnkey/eip-1193-provider": "workspace:*",
2123
"@turnkey/http": "workspace:^",
22-
"@turnkey/webauthn-stamper": "workspace:^",
24+
"@turnkey/webauthn-stamper": "workspace:*",
2325
"class-variance-authority": "^0.7.0",
2426
"clsx": "^2.1.0",
2527
"lucide-react": "^0.471.0",

examples/with-eip-1193-provider/src/components/dashboard.tsx

+120-5
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
getAddress,
1010
Hex,
1111
numberToHex,
12+
hashMessage,
1213
} from "viem";
1314

1415
import { TurnkeyEIP1193Provider } from "@turnkey/eip-1193-provider";
@@ -26,6 +27,7 @@ import {
2627
DialogFooter,
2728
DialogHeader,
2829
DialogTitle,
30+
DialogClose,
2931
} from "@/components/ui/dialog";
3032
import { Input } from "@/components/ui/input";
3133
import { Label } from "@/components/ui/label";
@@ -43,9 +45,12 @@ export function Dashboard({ provider }: DashboardProps) {
4345
const [selectedAccount, setSelectedAccount] = useState<AddressType | "">("");
4446
const [sendToAddress, setSendToAddress] = useState<AddressType | "">("");
4547
const [sendAmount, setSendAmount] = useState("");
48+
const [message, setMessage] = useState("");
4649
const [dialogOpen, setDialogOpen] = useState(false);
50+
const [transactionDialogOpen, setTransactionDialogOpen] = useState(false);
51+
const [messageDialogOpen, setMessageDialogOpen] = useState(false);
4752
const [dialogContent, setDialogContent] = useState<
48-
"default" | "getting-accounts"
53+
"default" | "getting-accounts" | "send-transaction" | "sign-message"
4954
>("default");
5055

5156
useEffect(() => {
@@ -129,7 +134,7 @@ export function Dashboard({ provider }: DashboardProps) {
129134
);
130135
},
131136
},
132-
duration: 5000,
137+
duration: 10000,
133138
});
134139
} catch (error: any) {
135140
toast.error("Error sending transaction", {
@@ -141,6 +146,47 @@ export function Dashboard({ provider }: DashboardProps) {
141146
}
142147
};
143148

149+
const handleSignMessage = async () => {
150+
if (provider && selectedAccount && message) {
151+
const hashedMessage = hashMessage(message);
152+
153+
try {
154+
const signedMessage = await provider.request({
155+
method: "personal_sign",
156+
params: [hashedMessage, selectedAccount],
157+
});
158+
159+
toast("Verify here:", {
160+
description: `Navigate to Etherscan to verify`,
161+
action: {
162+
label: "Verify",
163+
onClick: () => {
164+
window.open(`https://etherscan.io/verifiedSignatures#`, "_blank");
165+
},
166+
},
167+
duration: 10000,
168+
});
169+
170+
toast("Signed message! 🎉", {
171+
description: `Message ${message} successfully signed: ${signedMessage}`,
172+
action: {
173+
label: "Copy",
174+
onClick: () => {
175+
navigator.clipboard.writeText(signedMessage);
176+
},
177+
},
178+
duration: 10000,
179+
});
180+
} catch (error: any) {
181+
toast.error("Error signing message", {
182+
description: error.details,
183+
duration: 10000,
184+
});
185+
}
186+
setDialogOpen(false);
187+
}
188+
};
189+
144190
return (
145191
<div className="flex flex-col items-center text-center mt-20 w-1/4 space-y-6">
146192
<Card className="w-full flex flex-col items-center">
@@ -154,11 +200,14 @@ export function Dashboard({ provider }: DashboardProps) {
154200
</CardHeader>
155201
</Card>
156202

157-
<Dialog open={dialogOpen}>
203+
<Dialog
204+
open={transactionDialogOpen}
205+
onOpenChange={setTransactionDialogOpen}
206+
>
158207
<Button
159208
onClick={() => {
160-
setDialogContent("default");
161-
setDialogOpen(true);
209+
setDialogContent("send-transaction");
210+
setTransactionDialogOpen(true);
162211
}}
163212
variant="secondary"
164213
className="w-full mx-auto"
@@ -232,6 +281,72 @@ export function Dashboard({ provider }: DashboardProps) {
232281
)}
233282
</DialogContent>
234283
</Dialog>
284+
285+
<Dialog open={messageDialogOpen} onOpenChange={setMessageDialogOpen}>
286+
<Button
287+
onClick={() => {
288+
setDialogContent("sign-message");
289+
setMessageDialogOpen(true);
290+
}}
291+
variant="secondary"
292+
className="w-full mx-auto"
293+
>
294+
Sign Message
295+
</Button>
296+
297+
<DialogContent className="sm:max-w-[425px]">
298+
{dialogContent === "getting-accounts" ? (
299+
<>
300+
<DialogHeader>
301+
<DialogTitle className="text-center">
302+
Getting Accounts
303+
</DialogTitle>
304+
<DialogDescription className="flex justify-center h-20 items-center">
305+
<Icons.spinner className="animate-spin w-10" />
306+
</DialogDescription>
307+
</DialogHeader>
308+
</>
309+
) : (
310+
<>
311+
<DialogHeader>
312+
<DialogTitle>Sign Message</DialogTitle>
313+
<DialogDescription>
314+
Enter your message and click sign when you're ready.
315+
</DialogDescription>
316+
</DialogHeader>
317+
<div className="grid gap-4 py-4">
318+
<div className="grid grid-cols-4 items-center gap-4">
319+
<Label htmlFor="signWith" className="text-right">
320+
Sign With
321+
</Label>
322+
<Input
323+
id="signWith"
324+
value={selectedAccount}
325+
disabled
326+
className="col-span-3"
327+
/>
328+
</div>
329+
<div className="grid grid-cols-4 items-center gap-4">
330+
<Label htmlFor="message" className="text-right">
331+
Message
332+
</Label>
333+
<Input
334+
id="message"
335+
value={message}
336+
onChange={(e) => setMessage(e.target.value)}
337+
className="col-span-3"
338+
/>
339+
</div>
340+
</div>
341+
<DialogFooter>
342+
<DialogClose asChild>
343+
<Button onClick={handleSignMessage}>Sign</Button>
344+
</DialogClose>
345+
</DialogFooter>
346+
</>
347+
)}
348+
</DialogContent>
349+
</Dialog>
235350
</div>
236351
);
237352
}

examples/with-eip-1193-provider/src/lib/turnkey.ts

+36-43
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,14 @@
11
"use server";
22

33
import {
4-
ApiKeyStamper,
5-
type TApiKeyStamperConfig,
6-
} from "@turnkey/api-key-stamper";
7-
import { TurnkeyClient, createActivityPoller } from "@turnkey/http";
4+
Turnkey as TurnkeyServerSDK,
5+
TurnkeyApiTypes,
6+
TurnkeyServerClient,
7+
} from "@turnkey/sdk-server";
8+
import { type TApiKeyStamperConfig } from "@turnkey/api-key-stamper";
89
import type { Attestation, Email, PasskeyRegistrationResult } from "./types";
910

10-
import {
11-
ETHEREUM_WALLET_DEFAULT_PATH,
12-
CURVE_TYPE_SECP256K1,
13-
} from "./constants";
11+
import { ETHEREUM_WALLET_DEFAULT_PATH } from "./constants";
1412
import type { UUID } from "crypto";
1513
import type { Address } from "viem";
1614

@@ -21,41 +19,36 @@ const {
2119
NEXT_PUBLIC_BASE_URL,
2220
} = process.env;
2321

24-
export const createAPIKeyStamper = (options?: TApiKeyStamperConfig) => {
22+
// Note: this is not currently in use
23+
export const createAPIKeyStamper = async (options?: TApiKeyStamperConfig) => {
2524
const apiPublicKey = options?.apiPublicKey || TURNKEY_API_PUBLIC_KEY;
2625
const apiPrivateKey = options?.apiPrivateKey || TURNKEY_API_PRIVATE_KEY;
2726

2827
if (!(apiPublicKey && apiPrivateKey)) {
2928
throw "Error must provide public and private api key or define API_PUBLIC_KEY API_PRIVATE_KEY in your .env file";
3029
}
3130

32-
return new ApiKeyStamper({
31+
return new TurnkeyServerSDK({
32+
apiBaseUrl: NEXT_PUBLIC_BASE_URL!,
33+
defaultOrganizationId: NEXT_PUBLIC_ORGANIZATION_ID!,
3334
apiPublicKey,
3435
apiPrivateKey,
3536
});
3637
};
3738

3839
export const createUserSubOrg = async (
39-
turnkeyClient: TurnkeyClient,
40+
turnkeyClient: TurnkeyServerClient,
4041
{
4142
email,
42-
// if challenge and attestation are provided we are creating a non-custodial wallet using the users provided authenticator
43+
// if challenge and attestation are provided we are creating a non-custodial wallet using the user's provided authenticator
4344
challenge,
4445
attestation,
4546
}: {
4647
email: Email;
4748
challenge: string;
4849
attestation: Attestation;
49-
},
50+
}
5051
) => {
51-
const activityPoller = createActivityPoller({
52-
client: turnkeyClient,
53-
requestFn: turnkeyClient.createSubOrganization,
54-
});
55-
56-
const organizationId = NEXT_PUBLIC_ORGANIZATION_ID!;
57-
const timestampMs = String(Date.now());
58-
5952
const rootUsersOptions =
6053
challenge && attestation
6154
? {
@@ -67,30 +60,29 @@ export const createUserSubOrg = async (
6760
},
6861
],
6962
apiKeys: [],
63+
oauthProviders: [],
7064
}
7165
: {
7266
authenticators: [],
7367
apiKeys: [
7468
{
7569
apiKeyName: "turnkey-demo",
7670
publicKey: TURNKEY_API_PUBLIC_KEY!,
77-
curveType: CURVE_TYPE_SECP256K1,
71+
curveType:
72+
"API_KEY_CURVE_P256" as TurnkeyApiTypes["v1ApiKeyCurve"],
7873
},
7974
],
75+
oauthProviders: [],
8076
};
8177
const userName = email.split("@")[0];
82-
const completedActivity = await activityPoller({
83-
type: "ACTIVITY_TYPE_CREATE_SUB_ORGANIZATION_V7",
84-
timestampMs,
85-
organizationId,
86-
parameters: {
78+
const createSubOrganizationResult = await turnkeyClient
79+
.createSubOrganization({
8780
subOrganizationName: `Sub Org - ${email}`,
8881
rootQuorumThreshold: 1,
8982
rootUsers: [
9083
{
9184
userName,
9285
userEmail: email,
93-
oauthProviders: [],
9486
...rootUsersOptions,
9587
},
9688
],
@@ -105,30 +97,31 @@ export const createUserSubOrg = async (
10597
},
10698
],
10799
},
108-
},
109-
}).catch((error) => {
110-
console.error(error);
111-
});
100+
})
101+
.catch((error: any) => {
102+
console.error(error);
103+
});
112104

113-
return completedActivity?.result.createSubOrganizationResultV4;
105+
return createSubOrganizationResult;
114106
};
115107

116108
export const signUp = async (
117109
email: Email,
118-
passKeyRegistrationResult: PasskeyRegistrationResult,
110+
passKeyRegistrationResult: PasskeyRegistrationResult
119111
) => {
120-
const client = new TurnkeyClient(
121-
{ baseUrl: NEXT_PUBLIC_BASE_URL! },
122-
new ApiKeyStamper({
123-
apiPublicKey: TURNKEY_API_PUBLIC_KEY!,
124-
apiPrivateKey: TURNKEY_API_PRIVATE_KEY!,
125-
}),
126-
);
112+
const client = new TurnkeyServerSDK({
113+
apiBaseUrl: NEXT_PUBLIC_BASE_URL!,
114+
defaultOrganizationId: NEXT_PUBLIC_ORGANIZATION_ID!,
115+
apiPublicKey: TURNKEY_API_PUBLIC_KEY!,
116+
apiPrivateKey: TURNKEY_API_PRIVATE_KEY!,
117+
});
127118

128119
// Create a new user sub org with email
129120
const { subOrganizationId, wallet } =
130-
(await createUserSubOrg(client, { email, ...passKeyRegistrationResult })) ??
131-
{};
121+
(await createUserSubOrg(client.apiClient(), {
122+
email,
123+
...passKeyRegistrationResult,
124+
})) ?? {};
132125

133126
return {
134127
subOrganizationId: subOrganizationId as UUID,

0 commit comments

Comments
 (0)