From efd2ea127a3eda40edc57a6f9c6f6a8430208432 Mon Sep 17 00:00:00 2001 From: Jordan Leigh Date: Thu, 2 May 2024 15:28:11 -0400 Subject: [PATCH 1/3] initial PoC of import/export react components --- packages/iframe-stamper/src/index.ts | 4 + packages/sdk-browser/src/__types__/base.ts | 6 + packages/sdk-browser/src/sdk-client.ts | 54 +++++-- .../sdk-react/src/components/ExportWallet.tsx | 125 ++++++++++++++ .../sdk-react/src/components/ImportWallet.tsx | 152 ++++++++++++++++++ .../sdk-react/src/contexts/TurnkeyContext.tsx | 50 +++--- packages/sdk-react/src/index.ts | 11 +- 7 files changed, 369 insertions(+), 33 deletions(-) create mode 100644 packages/sdk-react/src/components/ExportWallet.tsx create mode 100644 packages/sdk-react/src/components/ImportWallet.tsx diff --git a/packages/iframe-stamper/src/index.ts b/packages/iframe-stamper/src/index.ts index 666bcde19..a27f19fdf 100644 --- a/packages/iframe-stamper/src/index.ts +++ b/packages/iframe-stamper/src/index.ts @@ -233,6 +233,10 @@ export class IframeStamper { organizationId: string, keyFormat?: KeyFormat ): Promise { + console.log(`Calling inject key export bundle`); + console.log(`BUNDLE: ${bundle}`); + console.log(`ORGANIZATION ID: ${organizationId}`); + console.log(`KEY FORMAT: ${keyFormat}`); this.iframe.contentWindow?.postMessage( { type: IframeEventType.InjectKeyExportBundle, diff --git a/packages/sdk-browser/src/__types__/base.ts b/packages/sdk-browser/src/__types__/base.ts index 8866c37b3..1d3cbd2ff 100644 --- a/packages/sdk-browser/src/__types__/base.ts +++ b/packages/sdk-browser/src/__types__/base.ts @@ -96,3 +96,9 @@ export type commandOverrideParams = { organizationId?: string; timestampMs?: string; }; + +export interface IframeClientParams { + iframeContainer: HTMLElement | null | undefined; + iframeUrl: string; + iframeElementId?: string; +} diff --git a/packages/sdk-browser/src/sdk-client.ts b/packages/sdk-browser/src/sdk-client.ts index 85f36b38a..36a95ead8 100644 --- a/packages/sdk-browser/src/sdk-client.ts +++ b/packages/sdk-browser/src/sdk-client.ts @@ -9,6 +9,7 @@ import type { GrpcStatus, TurnkeySDKClientConfig, TurnkeySDKBrowserConfig, + IframeClientParams } from "./__types__/base"; import { TurnkeyRequestError } from "./__types__/base"; @@ -72,24 +73,21 @@ export class TurnkeyBrowserSDK { }); }; - iframeClient = async ( - iframeContainer: HTMLElement | null | undefined, - iframeUrl?: string - ): Promise => { - const targetIframeUrl = iframeUrl ?? this.config.iframeUrl; + iframeClient = async (params: IframeClientParams): Promise => { + const targetIframeUrl = params.iframeUrl; if (!targetIframeUrl) { throw new Error( - "Tried to initialize iframeSigner with no iframeUrl defined" + "Tried to initialize iframeClient with no iframeUrl defined" ); } - const TurnkeyIframeElementId = "turnkey-default-iframe-element-id"; + const TurnkeyIframeElementId = params.iframeElementId ?? "turnkey-iframe-element-id"; const iframeStamper = new IframeStamper({ + iframeContainer: params.iframeContainer, iframeUrl: targetIframeUrl, - iframeElementId: TurnkeyIframeElementId, - iframeContainer: iframeContainer, + iframeElementId: TurnkeyIframeElementId }); await iframeStamper.init(); @@ -263,4 +261,42 @@ export class TurnkeyIframeClient extends TurnkeyBrowserClient { const stamper = this.config.stamper as IframeStamper; return await stamper.injectCredentialBundle(credentialBundle); }; + + injectWalletExportBundle = async ( + credentialBundle: string, + organizationId: string + ): Promise => { + const stamper = this.config.stamper as IframeStamper; + return await stamper.injectWalletExportBundle(credentialBundle, organizationId); + } + + injectKeyExportBundle = async ( + credentialBundle: string, + organizationId: string + ): Promise => { + const stamper = this.config.stamper as IframeStamper; + return await stamper.injectKeyExportBundle( + credentialBundle, + organizationId + ) + } + + injectImportBundle = async ( + bundle: string, + organizationId: string, + userId: string + ): Promise => { + const stamper = this.config.stamper as IframeStamper; + return await stamper.injectImportBundle( + bundle, + organizationId, + userId + ); + } + + extractWalletEncryptedBundle = async (): Promise => { + const stamper = this.config.stamper as IframeStamper; + return await stamper.extractWalletEncryptedBundle(); + } + } diff --git a/packages/sdk-react/src/components/ExportWallet.tsx b/packages/sdk-react/src/components/ExportWallet.tsx new file mode 100644 index 000000000..de8cbd715 --- /dev/null +++ b/packages/sdk-react/src/components/ExportWallet.tsx @@ -0,0 +1,125 @@ +import { useEffect, useRef, useState } from "react"; +import { useTurnkey } from "../hooks/useTurnkey"; +import type { TurnkeyIframeClient } from "@turnkey/sdk-browser"; + +type ExportWalletProps = { + wallet: { + walletName: string; + walletId: string; + }, + walletAccount: { + address: string; + }, + onCancel?: () => void +} + +export const ExportWallet: React.FC = ({ + wallet, + walletAccount, + onCancel = () => undefined +}) => { + const { turnkey, passkeyClient } = useTurnkey(); + const [iframeClient, setIframeClient] = useState(undefined); + const [iframeStyle, setIframeStyle] = useState>({ display: "none" }); + const iframeInit = useRef(false); + + const TurnkeyExportIframeContainerId = "turnkey-export-iframe-container-id"; + + useEffect(() => { + (async () => { + if (!iframeInit.current) { + + iframeInit.current = true; + + const newExportIframeClient = await turnkey?.iframeClient({ + iframeContainer: document.getElementById(TurnkeyExportIframeContainerId), + iframeUrl: "https://export.turnkey.com" + }); + setIframeClient(newExportIframeClient); + + } + })(); + }, []); + + const exportWallet = async () => { + const currentUser = await turnkey?.getCurrentUser(); + const exportResponse = await passkeyClient?.exportWallet({ + walletId: wallet.walletId, + targetPublicKey: `${iframeClient?.iframePublicKey}` + }); + if (exportResponse?.exportBundle) { + const injectResponse = await iframeClient?.injectWalletExportBundle( + exportResponse.exportBundle, + `${currentUser?.organization.organizationId}` + ); + if (injectResponse) { + setIframeStyle({ + display: "block", + width: "100%", + boxSizing: "border-box", + padding: "20px", + borderStyle: "solid", + borderWidth: "1px", + borderRadius: "8px", + borderColor: "rgba(216, 219, 227, 1)" + }); + } + } + } + + const exportWalletAccount = async () => { + const currentUser = await turnkey?.getCurrentUser(); + const exportResponse = await passkeyClient?.exportWalletAccount({ + address: walletAccount.address, + targetPublicKey: `${iframeClient?.iframePublicKey}` + }); + if (exportResponse?.exportBundle) { + const injectResponse = await iframeClient?.injectKeyExportBundle( + exportResponse.exportBundle, + `${currentUser?.organization.organizationId}` + ); + if (injectResponse) { + setIframeStyle({ + display: "block", + width: "100%", + boxSizing: "border-box", + padding: "20px", + borderStyle: "solid", + borderWidth: "1px", + borderRadius: "8px", + borderColor: "rgba(216, 219, 227, 1)" + }); + } + } + } + + return ( +
+

Export

+

{`Wallet Name: ${wallet.walletName}`}

+

{`Account Address: ${walletAccount.address}`}

+ +
+
+

Export Wallet Seed

+
+ +
+

Export Account Private Key

+
+ +
+

Cancel

+
+
+ +
+
+ ) +} diff --git a/packages/sdk-react/src/components/ImportWallet.tsx b/packages/sdk-react/src/components/ImportWallet.tsx new file mode 100644 index 000000000..fae9574e0 --- /dev/null +++ b/packages/sdk-react/src/components/ImportWallet.tsx @@ -0,0 +1,152 @@ +import { useEffect, useRef, useState } from "react"; +import { useTurnkey } from "../hooks/useTurnkey"; +import { DEFAULT_ETHEREUM_ACCOUNTS } from "@turnkey/sdk-browser"; +import type { TurnkeyIframeClient } from "@turnkey/sdk-browser"; + +type ImportWalletProps = { + onCancel?: () => void; + onWalletImportSuccess?: () => void; +} + +export const ImportWallet: React.FC = ({ + onCancel = () => undefined, + onWalletImportSuccess = () => undefined +}) => { + const { turnkey, passkeyClient } = useTurnkey(); + const [iframeClient, setIframeClient] = useState(undefined); + const [iframeStyle, setIframeStyle] = useState>({ display: "none" }); + const iframeInit = useRef(false); + + const [initImportWalletComplete, setInitImportWalletComplete] = useState(false); + const [_initImportWalletAccountComplete, _setInitImportWalletAccountComplete] = useState(false); + + const [newWalletName, setNewWalletName] = useState(''); + + const TurnkeyImportIframeContainerId = "turnkey-import-iframe-container-id"; + + useEffect(() => { + if (initImportWalletComplete) { + setIframeStyle({ + display: "block", + width: "100%", + boxSizing: "border-box", + padding: "20px", + borderStyle: "solid", + borderWidth: "1px", + borderRadius: "8px", + borderColor: "rgba(216, 219, 227, 1)" + }); + } + }, [initImportWalletComplete]) + + useEffect(() => { + (async () => { + if (!iframeInit.current) { + + iframeInit.current = true; + + const newImportIframeClient = await turnkey?.iframeClient({ + iframeContainer: document.getElementById(TurnkeyImportIframeContainerId), + iframeUrl: "https://import.turnkey.com" + }); + setIframeClient(newImportIframeClient); + + } + })(); + }, []); + + const initImportWallet = async () => { + + const currentUser = await turnkey?.getCurrentUser(); + const initImportResponse = await passkeyClient?.initImportWallet({ + userId: `${currentUser?.userId}` + }); + + if (initImportResponse?.importBundle) { + const injectResponse = await iframeClient?.injectImportBundle( + initImportResponse.importBundle, + `${currentUser?.organization.organizationId}`, + `${currentUser?.userId}` + ); + if (injectResponse) { + setInitImportWalletComplete(true); + } + } + + } + + const importWallet = async () => { + const currentUser = await turnkey?.getCurrentUser(); + const encryptedBundle = await iframeClient?.extractWalletEncryptedBundle(); + + if (encryptedBundle) { + const importResponse = await passkeyClient?.importWallet({ + userId: `${currentUser?.userId}`, + walletName: newWalletName, + encryptedBundle, + accounts: DEFAULT_ETHEREUM_ACCOUNTS + }); + + if (importResponse) { + onWalletImportSuccess(); + } + + } + + } + + const importWalletAccount = async () => { + console.log(`Called Import Wallet Account`); + } + + return ( +
+

Import Wallet

+ +
+
+

Begin Wallet Import

+
+ +
+

Begin Account Private Key Import

+
+ +
+

Cancel

+
+
+ + {initImportWalletComplete ? ( +

Paste your Mnemonic Here

+ ) : (<>)} + +
+ + {initImportWalletComplete ? ( + <> +
+

Wallet Name

+ setNewWalletName(e.target.value) } /> +
+ +
+

Import Wallet

+
+ + ) : (<>)} +
+ ) +} diff --git a/packages/sdk-react/src/contexts/TurnkeyContext.tsx b/packages/sdk-react/src/contexts/TurnkeyContext.tsx index 52b3f4adf..37e414f6e 100644 --- a/packages/sdk-react/src/contexts/TurnkeyContext.tsx +++ b/packages/sdk-react/src/contexts/TurnkeyContext.tsx @@ -1,4 +1,4 @@ -import { ReactNode, createContext, useState, useEffect, useRef } from "react"; +import { ReactNode, createContext, useState, useEffect, useRef, Dispatch, SetStateAction } from "react"; import { Turnkey, TurnkeyIframeClient, @@ -8,14 +8,16 @@ import { export interface TurnkeyClientType { turnkey: Turnkey | undefined; - iframeClient: TurnkeyIframeClient | undefined; passkeyClient: TurnkeyPasskeyClient | undefined; + authIframeClient: TurnkeyIframeClient | undefined; + setAuthIframeStyle: Dispatch>>; } export const TurnkeyContext = createContext({ turnkey: undefined, passkeyClient: undefined, - iframeClient: undefined, + authIframeClient: undefined, + setAuthIframeStyle: ({}) => undefined, }); interface TurnkeyProviderProps { @@ -28,27 +30,32 @@ export const TurnkeyProvider: React.FC = ({ children, }) => { const [turnkey, setTurnkey] = useState(undefined); - const [passkeyClient, setPasskeyClient] = useState< - TurnkeyPasskeyClient | undefined - >(undefined); - const [iframeClient, setIframeClient] = useState< - TurnkeyIframeClient | undefined - >(undefined); - const iframeInit = useRef(false); + const [passkeyClient, setPasskeyClient] = useState(undefined); - const TurnkeyIframeContainerId = "turnkey-default-iframe-container-id"; + const [authIframeClient, setAuthIframeClient] = useState(undefined); + const [authIframeStyle, setAuthIframeStyle] = useState>({ display: "none" }); + + const iframesInit = useRef(false); + + const TurnkeyAuthIframeContainerId = "turnkey-auth-iframe-container-id"; useEffect(() => { (async () => { - if (!iframeInit.current) { - iframeInit.current = true; + if (!iframesInit.current) { + + iframesInit.current = true; + const newTurnkey = new Turnkey(config); setTurnkey(newTurnkey); + setPasskeyClient(newTurnkey.passkeyClient()); - const newIframeClient = await newTurnkey.iframeClient( - document.getElementById(TurnkeyIframeContainerId) - ); - setIframeClient(newIframeClient); + + const newAuthIframeClient = await newTurnkey.iframeClient({ + iframeContainer: document.getElementById(TurnkeyAuthIframeContainerId), + iframeUrl: "https://auth.turnkey.com" + }); + setAuthIframeClient(newAuthIframeClient); + } })(); }, []); @@ -58,15 +65,12 @@ export const TurnkeyProvider: React.FC = ({ value={{ turnkey, passkeyClient, - iframeClient, + authIframeClient, + setAuthIframeStyle, }} > {children} -
+
); }; diff --git a/packages/sdk-react/src/index.ts b/packages/sdk-react/src/index.ts index 77616bdfb..9962b7cf4 100644 --- a/packages/sdk-react/src/index.ts +++ b/packages/sdk-react/src/index.ts @@ -1,4 +1,13 @@ import { TurnkeyContext, TurnkeyProvider } from "./contexts/TurnkeyContext"; import { useTurnkey } from "./hooks/useTurnkey"; -export { TurnkeyContext, TurnkeyProvider, useTurnkey }; +import { ExportWallet } from "./components/ExportWallet"; +import { ImportWallet } from "./components/ImportWallet"; + +export { + ExportWallet, + ImportWallet, + TurnkeyContext, + TurnkeyProvider, + useTurnkey +}; From 6b73ba6173ef61054868f73c272d1d8108edb407 Mon Sep 17 00:00:00 2001 From: Jordan Leigh Date: Thu, 2 May 2024 17:23:12 -0400 Subject: [PATCH 2/3] working export with wallet select --- .../sdk-react/src/components/ExportWallet.tsx | 85 ++++++++++++++++--- 1 file changed, 72 insertions(+), 13 deletions(-) diff --git a/packages/sdk-react/src/components/ExportWallet.tsx b/packages/sdk-react/src/components/ExportWallet.tsx index de8cbd715..3c38dde86 100644 --- a/packages/sdk-react/src/components/ExportWallet.tsx +++ b/packages/sdk-react/src/components/ExportWallet.tsx @@ -3,19 +3,10 @@ import { useTurnkey } from "../hooks/useTurnkey"; import type { TurnkeyIframeClient } from "@turnkey/sdk-browser"; type ExportWalletProps = { - wallet: { - walletName: string; - walletId: string; - }, - walletAccount: { - address: string; - }, onCancel?: () => void } export const ExportWallet: React.FC = ({ - wallet, - walletAccount, onCancel = () => undefined }) => { const { turnkey, passkeyClient } = useTurnkey(); @@ -23,6 +14,11 @@ export const ExportWallet: React.FC = ({ const [iframeStyle, setIframeStyle] = useState>({ display: "none" }); const iframeInit = useRef(false); + const [wallets, setWallets] = useState([]); + const [selectedWallet, setSelectedWallet] = useState(undefined); + const [walletAccounts, setWalletAccounts] = useState([]); + const [selectedWalletAccount, setSelectedWalletAccount] = useState(undefined); + const TurnkeyExportIframeContainerId = "turnkey-export-iframe-container-id"; useEffect(() => { @@ -41,10 +37,44 @@ export const ExportWallet: React.FC = ({ })(); }, []); + useEffect(() => { + if (turnkey) { + (async () => { + const currentUserSession = await turnkey.currentUserSession(); + if (currentUserSession) { + const walletsResponse = await currentUserSession.getWallets(); + if (walletsResponse) { + setWallets(walletsResponse.wallets); + setSelectedWallet(walletsResponse.wallets[0]); + } + } + })(); + } + }, [turnkey]); + + useEffect(() => { + if (turnkey) { + (async () => { + const currentUserSession = await turnkey.currentUserSession(); + if (currentUserSession) { + if (selectedWallet) { + const walletAccountsResponse = await currentUserSession.getWalletAccounts({ + walletId: selectedWallet.walletId + }); + if (walletAccountsResponse) { + setWalletAccounts(walletAccountsResponse.accounts); + setSelectedWalletAccount(walletAccountsResponse.accounts[0]); + } + } + } + })(); + } + }, [selectedWallet]); + const exportWallet = async () => { const currentUser = await turnkey?.getCurrentUser(); const exportResponse = await passkeyClient?.exportWallet({ - walletId: wallet.walletId, + walletId: selectedWallet.walletId, targetPublicKey: `${iframeClient?.iframePublicKey}` }); if (exportResponse?.exportBundle) { @@ -70,7 +100,7 @@ export const ExportWallet: React.FC = ({ const exportWalletAccount = async () => { const currentUser = await turnkey?.getCurrentUser(); const exportResponse = await passkeyClient?.exportWalletAccount({ - address: walletAccount.address, + address: selectedWalletAccount.address, targetPublicKey: `${iframeClient?.iframePublicKey}` }); if (exportResponse?.exportBundle) { @@ -96,8 +126,37 @@ export const ExportWallet: React.FC = ({ return (

Export

-

{`Wallet Name: ${wallet.walletName}`}

-

{`Account Address: ${walletAccount.address}`}

+ +
+
+

Select Wallet:

+ +
+ +
+

Select Wallet Account:

+ +
+ +
Date: Fri, 3 May 2024 14:36:49 -0400 Subject: [PATCH 3/3] updated checkpoint --- packages/iframe-stamper/src/index.ts | 4 ---- packages/sdk-react/src/components/ImportWallet.tsx | 13 +++++++++---- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/packages/iframe-stamper/src/index.ts b/packages/iframe-stamper/src/index.ts index a27f19fdf..666bcde19 100644 --- a/packages/iframe-stamper/src/index.ts +++ b/packages/iframe-stamper/src/index.ts @@ -233,10 +233,6 @@ export class IframeStamper { organizationId: string, keyFormat?: KeyFormat ): Promise { - console.log(`Calling inject key export bundle`); - console.log(`BUNDLE: ${bundle}`); - console.log(`ORGANIZATION ID: ${organizationId}`); - console.log(`KEY FORMAT: ${keyFormat}`); this.iframe.contentWindow?.postMessage( { type: IframeEventType.InjectKeyExportBundle, diff --git a/packages/sdk-react/src/components/ImportWallet.tsx b/packages/sdk-react/src/components/ImportWallet.tsx index fae9574e0..4d11df70b 100644 --- a/packages/sdk-react/src/components/ImportWallet.tsx +++ b/packages/sdk-react/src/components/ImportWallet.tsx @@ -1,16 +1,17 @@ import { useEffect, useRef, useState } from "react"; import { useTurnkey } from "../hooks/useTurnkey"; -import { DEFAULT_ETHEREUM_ACCOUNTS } from "@turnkey/sdk-browser"; import type { TurnkeyIframeClient } from "@turnkey/sdk-browser"; type ImportWalletProps = { onCancel?: () => void; onWalletImportSuccess?: () => void; + onWalletAccountImportSuccess?: () => void; } export const ImportWallet: React.FC = ({ onCancel = () => undefined, - onWalletImportSuccess = () => undefined + onWalletImportSuccess = () => undefined, + onWalletAccountImportSuccess = () => undefined }) => { const { turnkey, passkeyClient } = useTurnkey(); const [iframeClient, setIframeClient] = useState(undefined); @@ -18,7 +19,7 @@ export const ImportWallet: React.FC = ({ const iframeInit = useRef(false); const [initImportWalletComplete, setInitImportWalletComplete] = useState(false); - const [_initImportWalletAccountComplete, _setInitImportWalletAccountComplete] = useState(false); + const [initImportWalletAccountComplete, setInitImportWalletAccountComplete] = useState(false); const [newWalletName, setNewWalletName] = useState(''); @@ -84,7 +85,7 @@ export const ImportWallet: React.FC = ({ userId: `${currentUser?.userId}`, walletName: newWalletName, encryptedBundle, - accounts: DEFAULT_ETHEREUM_ACCOUNTS + accounts: [] }); if (importResponse) { @@ -95,6 +96,10 @@ export const ImportWallet: React.FC = ({ } + const initImportWalletAccount = async () => { + + } + const importWalletAccount = async () => { console.log(`Called Import Wallet Account`); }