diff --git a/packages/sdk-browser/package.json b/packages/sdk-browser/package.json index e17462b58..ffa5131dd 100644 --- a/packages/sdk-browser/package.json +++ b/packages/sdk-browser/package.json @@ -49,6 +49,7 @@ "@turnkey/encoding": "workspace:*", "@turnkey/http": "workspace:*", "@turnkey/iframe-stamper": "workspace:*", + "@turnkey/wallet-stamper": "workspace:^", "@turnkey/webauthn-stamper": "workspace:*", "bs58check": "^3.0.1", "buffer": "^6.0.3", diff --git a/packages/sdk-browser/src/__types__/base.ts b/packages/sdk-browser/src/__types__/base.ts index 1842f5880..527897a6e 100644 --- a/packages/sdk-browser/src/__types__/base.ts +++ b/packages/sdk-browser/src/__types__/base.ts @@ -1,4 +1,5 @@ -import type { TActivityId, TActivityStatus } from "@turnkey/http"; +import type { TActivityId, TActivityStatus } from '@turnkey/http'; +import type { WalletInterface } from '@turnkey/wallet-stamper'; export type GrpcStatus = { message: string; @@ -38,7 +39,7 @@ export class TurnkeyRequestError extends Error { super(turnkeyErrorMessage); - this.name = "TurnkeyRequestError"; + this.name = 'TurnkeyRequestError'; this.details = input.details ?? null; this.code = input.code; } @@ -105,3 +106,7 @@ export interface IframeClientParams { iframeUrl: string; iframeElementId?: string; } + +export interface TurnkeyWalletClientConfig extends SDKClientConfigWithStamper { + wallet: WalletInterface; +} diff --git a/packages/sdk-browser/src/index.ts b/packages/sdk-browser/src/index.ts index 3ef238320..2a9e9e8c0 100644 --- a/packages/sdk-browser/src/index.ts +++ b/packages/sdk-browser/src/index.ts @@ -8,47 +8,49 @@ import { type TSignedRequest, type TActivity, type TurnkeyApiTypes, -} from "@turnkey/http"; +} from '@turnkey/http'; import { ApiKeyStamper, signWithApiKey, TApiKeyStamperConfig, -} from "@turnkey/api-key-stamper"; +} from '@turnkey/api-key-stamper'; import { IframeEventType, IframeStamper, TIframeStamperConfig, -} from "@turnkey/iframe-stamper"; +} from '@turnkey/iframe-stamper'; import { TWebauthnStamperConfig, WebauthnStamper, -} from "@turnkey/webauthn-stamper"; +} from '@turnkey/webauthn-stamper'; import { TurnkeyBrowserSDK, TurnkeyBrowserClient, TurnkeyIframeClient, TurnkeyPasskeyClient, -} from "./sdk-client"; +} from './sdk-client'; -export { getStorageValue, setStorageValue, StorageKeys } from "./storage"; +export { TurnkeyWalletClient } from './wallet-client'; + +export { getStorageValue, setStorageValue, StorageKeys } from './storage'; import { defaultEthereumAccountAtIndex, DEFAULT_ETHEREUM_ACCOUNTS, defaultSolanaAccountAtIndex, DEFAULT_SOLANA_ACCOUNTS, -} from "./turnkey-helpers"; +} from './turnkey-helpers'; import type { TurnkeySDKClientConfig, TurnkeySDKBrowserConfig, -} from "./__types__/base"; +} from './__types__/base'; -import type * as TurnkeySDKApiTypes from "./__generated__/sdk_api_types"; +import type * as TurnkeySDKApiTypes from './__generated__/sdk_api_types'; // Classes export { diff --git a/packages/sdk-browser/src/sdk-client.ts b/packages/sdk-browser/src/sdk-client.ts index 15852545b..7cda36bf8 100644 --- a/packages/sdk-browser/src/sdk-client.ts +++ b/packages/sdk-browser/src/sdk-client.ts @@ -1,41 +1,43 @@ -import { WebauthnStamper } from "@turnkey/webauthn-stamper"; -import { IframeStamper, KeyFormat } from "@turnkey/iframe-stamper"; -import { getWebAuthnAttestation } from "@turnkey/http"; +import { WebauthnStamper } from '@turnkey/webauthn-stamper'; +import { IframeStamper, KeyFormat } from '@turnkey/iframe-stamper'; +import { getWebAuthnAttestation } from '@turnkey/http'; -import { VERSION } from "./__generated__/version"; -import WindowWrapper from "./__polyfills__/window"; +import { VERSION } from './__generated__/version'; +import WindowWrapper from './__polyfills__/window'; import type { GrpcStatus, TurnkeySDKClientConfig, TurnkeySDKBrowserConfig, IframeClientParams, -} from "./__types__/base"; +} from './__types__/base'; -import { TurnkeyRequestError } from "./__types__/base"; +import { TurnkeyRequestError } from './__types__/base'; -import { TurnkeySDKClientBase } from "./__generated__/sdk-client-base"; -import type * as SdkApiTypes from "./__generated__/sdk_api_types"; +import { TurnkeySDKClientBase } from './__generated__/sdk-client-base'; +import type * as SdkApiTypes from './__generated__/sdk_api_types'; import type { User, SubOrganization, ReadWriteSession, Passkey, -} from "./models"; +} from './models'; import { StorageKeys, getStorageValue, removeStorageValue, setStorageValue, -} from "./storage"; +} from './storage'; import { generateRandomBuffer, base64UrlEncode, createEmbeddedAPIKey, -} from "./utils"; +} from './utils'; +import { type WalletInterface } from '@turnkey/wallet-stamper'; +import { type TurnkeyWalletClient } from './wallet-client'; -const DEFAULT_SESSION_EXPIRATION = "900"; // default to 15 minutes +const DEFAULT_SESSION_EXPIRATION = '900'; // default to 15 minutes export class TurnkeyBrowserSDK { config: TurnkeySDKBrowserConfig; @@ -50,7 +52,7 @@ export class TurnkeyBrowserSDK { if (!targetRpId) { throw new Error( - "Tried to initialize a passkey client with no rpId defined" + 'Tried to initialize a passkey client with no rpId defined' ); } @@ -70,12 +72,12 @@ export class TurnkeyBrowserSDK { ): Promise => { if (!params.iframeUrl) { throw new Error( - "Tried to initialize iframeClient with no iframeUrl defined" + 'Tried to initialize iframeClient with no iframeUrl defined' ); } const TurnkeyIframeElementId = - params.iframeElementId ?? "turnkey-default-iframe-element-id"; + params.iframeElementId ?? 'turnkey-default-iframe-element-id'; const iframeStamper = new IframeStamper({ iframeContainer: params.iframeContainer, @@ -92,6 +94,21 @@ export class TurnkeyBrowserSDK { }); }; + walletClient = async ( + wallet: WalletInterface + ): Promise => { + const { WalletStamper, TurnkeyWalletClient } = await import( + './wallet-client' + ); + + return new TurnkeyWalletClient({ + stamper: new WalletStamper(wallet), + wallet, + apiBaseUrl: this.config.apiBaseUrl, + organizationId: this.config.defaultOrganizationId, + }); + }; + serverSign = async ( methodName: string, params: any[], @@ -100,7 +117,7 @@ export class TurnkeyBrowserSDK { const targetServerSignUrl = serverSignUrl ?? this.config.serverSignUrl; if (!targetServerSignUrl) { - throw new Error("Tried to call serverSign with no serverSignUrl defined"); + throw new Error('Tried to call serverSign with no serverSignUrl defined'); } const stringifiedBody = JSON.stringify({ @@ -109,13 +126,13 @@ export class TurnkeyBrowserSDK { }); const response = await fetch(targetServerSignUrl, { - method: "POST", + method: 'POST', headers: { - "Content-Type": "application/json", - "X-Client-Version": VERSION, + 'Content-Type': 'application/json', + 'X-Client-Version': VERSION, }, body: stringifiedBody, - redirect: "follow", + redirect: 'follow', }); if (!response.ok) { @@ -316,19 +333,19 @@ export class TurnkeyPasskeyClient extends TurnkeyBrowserClient { publicKey: { rp: { id: config.publicKey?.rp?.id ?? this.rpId, - name: config.publicKey?.rp?.name ?? "", + name: config.publicKey?.rp?.name ?? '', }, challenge: config.publicKey?.challenge ?? challenge, pubKeyCredParams: config.publicKey?.pubKeyCredParams ?? [ { - type: "public-key", + type: 'public-key', alg: -7, }, ], user: { id: config.publicKey?.user?.id ?? authenticatorUserId, - name: config.publicKey?.user?.name ?? "Default User", - displayName: config.publicKey?.user?.displayName ?? "Default User", + name: config.publicKey?.user?.name ?? 'Default User', + displayName: config.publicKey?.user?.displayName ?? 'Default User', }, authenticatorSelection: { authenticatorAttachment: @@ -338,10 +355,10 @@ export class TurnkeyPasskeyClient extends TurnkeyBrowserClient { config.publicKey?.authenticatorSelection?.requireResidentKey ?? true, residentKey: - config.publicKey?.authenticatorSelection?.residentKey ?? "required", + config.publicKey?.authenticatorSelection?.residentKey ?? 'required', userVerification: config.publicKey?.authenticatorSelection?.userVerification ?? - "preferred", + 'preferred', }, }, }; @@ -387,7 +404,7 @@ export class TurnkeyPasskeyClient extends TurnkeyBrowserClient { apiKeyName: `Session Key ${String(Date.now())}`, publicKey, expirationSeconds, - curveType: "API_KEY_CURVE_P256", + curveType: 'API_KEY_CURVE_P256', }, ], }); diff --git a/packages/sdk-browser/src/wallet-client.ts b/packages/sdk-browser/src/wallet-client.ts new file mode 100644 index 000000000..56b68130f --- /dev/null +++ b/packages/sdk-browser/src/wallet-client.ts @@ -0,0 +1,10 @@ +import { TurnkeyWalletClientConfig } from './__types__/base'; +import { TurnkeyBrowserClient } from './sdk-client'; + +export { WalletStamper } from '@turnkey/wallet-stamper'; + +export class TurnkeyWalletClient extends TurnkeyBrowserClient { + constructor(config: TurnkeyWalletClientConfig) { + super(config); + } +} diff --git a/packages/sdk-react/package.json b/packages/sdk-react/package.json index e44fcd2fe..ba0c576f5 100644 --- a/packages/sdk-react/package.json +++ b/packages/sdk-react/package.json @@ -43,7 +43,8 @@ "typecheck": "tsc -p tsconfig.typecheck.json" }, "dependencies": { - "@turnkey/sdk-browser": "workspace:*" + "@turnkey/sdk-browser": "workspace:*", + "@turnkey/wallet-stamper": "workspace:^" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0" diff --git a/packages/sdk-react/src/contexts/TurnkeyContext.tsx b/packages/sdk-react/src/contexts/TurnkeyContext.tsx index b419cc95b..9be173773 100644 --- a/packages/sdk-react/src/contexts/TurnkeyContext.tsx +++ b/packages/sdk-react/src/contexts/TurnkeyContext.tsx @@ -1,16 +1,19 @@ -import { ReactNode, createContext, useState, useEffect, useRef } from "react"; +import { ReactNode, createContext, useState, useEffect, useRef } from 'react'; import { Turnkey, TurnkeyIframeClient, TurnkeyPasskeyClient, TurnkeySDKBrowserConfig, TurnkeyBrowserClient, -} from "@turnkey/sdk-browser"; + TurnkeyWalletClient, +} from '@turnkey/sdk-browser'; +import type { WalletInterface } from '@turnkey/wallet-stamper'; export interface TurnkeyClientType { turnkey: Turnkey | undefined; authIframeClient: TurnkeyIframeClient | undefined; passkeyClient: TurnkeyPasskeyClient | undefined; + walletClient: TurnkeyWalletClient | undefined; getActiveClient: () => Promise; } @@ -18,6 +21,7 @@ export const TurnkeyContext = createContext({ turnkey: undefined, passkeyClient: undefined, authIframeClient: undefined, + walletClient: undefined, getActiveClient: async () => { return undefined; }, @@ -25,7 +29,9 @@ export const TurnkeyContext = createContext({ interface TurnkeyProviderProps { children: ReactNode; - config: TurnkeySDKBrowserConfig; + config: TurnkeySDKBrowserConfig & { + wallet: WalletInterface; + }; } export const TurnkeyProvider: React.FC = ({ @@ -36,13 +42,16 @@ export const TurnkeyProvider: React.FC = ({ const [passkeyClient, setPasskeyClient] = useState< TurnkeyPasskeyClient | undefined >(undefined); + const [walletClient, setWalletClient] = useState< + TurnkeyWalletClient | undefined + >(undefined); const [authIframeClient, setAuthIframeClient] = useState< TurnkeyIframeClient | undefined >(undefined); const iframeInit = useRef(false); - const TurnkeyAuthIframeContainerId = "turnkey-auth-iframe-container-id"; - const TurnkeyAuthIframeElementId = "turnkey-auth-iframe-element-id"; + const TurnkeyAuthIframeContainerId = 'turnkey-auth-iframe-container-id'; + const TurnkeyAuthIframeElementId = 'turnkey-auth-iframe-element-id'; const getActiveClient = async () => { let currentClient: TurnkeyBrowserClient | undefined = passkeyClient; @@ -90,12 +99,13 @@ export const TurnkeyProvider: React.FC = ({ const newTurnkey = new Turnkey(config); setTurnkey(newTurnkey); setPasskeyClient(newTurnkey.passkeyClient()); + setWalletClient(await newTurnkey.walletClient(config.wallet)); const newAuthIframeClient = await newTurnkey.iframeClient({ iframeContainer: document.getElementById( TurnkeyAuthIframeContainerId ), - iframeUrl: "https://auth.turnkey.com", + iframeUrl: 'https://auth.turnkey.com', iframeElementId: TurnkeyAuthIframeElementId, }); setAuthIframeClient(newAuthIframeClient); @@ -109,6 +119,7 @@ export const TurnkeyProvider: React.FC = ({ turnkey, passkeyClient, authIframeClient, + walletClient, getActiveClient, }} > @@ -116,7 +127,7 @@ export const TurnkeyProvider: React.FC = ({
); diff --git a/packages/sdk-react/src/hooks/use-turnkey.ts b/packages/sdk-react/src/hooks/use-turnkey.ts new file mode 100644 index 000000000..f28d4d450 --- /dev/null +++ b/packages/sdk-react/src/hooks/use-turnkey.ts @@ -0,0 +1,10 @@ +import { useContext } from "react"; +import { TurnkeyContext } from "../contexts/TurnkeyContext"; + +export const useTurnkey = () => { + const context = useContext(TurnkeyContext); + if (!context) { + throw new Error("useTurnkey must be used within a TurnkeyProvider"); + } + return context; +}; diff --git a/packages/sdk-react/src/index.ts b/packages/sdk-react/src/index.ts index 77616bdfb..fd97925c9 100644 --- a/packages/sdk-react/src/index.ts +++ b/packages/sdk-react/src/index.ts @@ -1,4 +1,4 @@ -import { TurnkeyContext, TurnkeyProvider } from "./contexts/TurnkeyContext"; -import { useTurnkey } from "./hooks/useTurnkey"; +import { TurnkeyContext, TurnkeyProvider } from './contexts/TurnkeyContext'; +import { useTurnkey } from './hooks/use-turnkey'; export { TurnkeyContext, TurnkeyProvider, useTurnkey }; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f94535fa2..077e715c5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1581,6 +1581,9 @@ importers: '@turnkey/iframe-stamper': specifier: workspace:* version: link:../iframe-stamper + '@turnkey/wallet-stamper': + specifier: workspace:^ + version: link:../wallet-stamper '@turnkey/webauthn-stamper': specifier: workspace:* version: link:../webauthn-stamper @@ -1612,6 +1615,9 @@ importers: '@turnkey/sdk-browser': specifier: workspace:* version: link:../sdk-browser + '@turnkey/wallet-stamper': + specifier: workspace:^ + version: link:../wallet-stamper devDependencies: '@types/react': specifier: ^18.2.75