UNPKG

@turnkey/core

Version:

A core JavaScript web and React Native package for interfacing with Turnkey's infrastructure.

181 lines (178 loc) 7.69 kB
import { WalletInterfaceType, SignIntent } from '../__types__/enums.mjs'; import { uint8ArrayFromHexString, uint8ArrayToHexString, stringToBase64urlString } from '@turnkey/encoding'; import { getSignatureSchemeFromProvider, isEthereumProvider, isSolanaProvider } from '../utils.mjs'; const STAMP_HEADER_NAME = "X-Stamp"; class CrossPlatformWalletStamper { /** * Constructs a CrossPlatformWalletStamper. * * - Validates that at least one wallet interface is provided. * - For each wallet interface, creates an internal `WalletStamper` bound to it. * - Ensures the stamper instance is always initialized in a usable state. * * @param wallets - A partial mapping of wallet interfaces by type. * @throws {Error} If no wallet interfaces are provided. */ constructor(wallets) { this.ctx = {}; const walletEntries = Object.entries(wallets).filter(([, w]) => Boolean(w)); if (walletEntries.length === 0) { throw new Error("Cannot create WalletStamper: no wallet interfaces provided"); } for (const [interfaceType, wallet] of walletEntries) { const typed = interfaceType; this.ctx[typed] = { wallet, stamper: new WalletStamper(wallet) }; } } /** * Stamps a payload using the specified wallet interface and provider. * * - Uses the explicitly provided interface and provider if given. * - Falls back to the default interface and stored provider otherwise. * * @param payload - The string payload to sign. * @param interfaceType - Optional wallet interface type (defaults to the active or first available). * @param provider - Optional provider (defaults to the one set via `setProvider`). * @returns A `TStamp` object containing the stamp header name and encoded value. * @throws {Error} If no provider is available for the selected interface. */ async stamp(payload, interfaceType = this.defaultInterface(), provider) { const ctx = this.getCtx(interfaceType); const selectedProvider = provider ?? ctx.provider; if (!selectedProvider) { throw new Error(`Could not find a provider for interface '${interfaceType}'.`); } return ctx.stamper.stamp(payload, selectedProvider); } /** * Retrieves the public key for the given provider. * * @param interfaceType - Optional wallet interface type (defaults to the active or first available). * @param provider - Wallet provider for which to fetch the public key. * @returns A promise resolving to the public key in hex format. */ async getPublicKey(interfaceType = this.defaultInterface(), provider) { return this.getCtx(interfaceType).wallet.getPublicKey(provider); } /** * Sets the active provider for a given wallet interface. * * - The active provider is used as a fallback in `stamp` if none is passed explicitly. * * @param interfaceType - Wallet interface type. * @param provider - Provider instance to associate with the interface. */ setProvider(interfaceType, provider) { this.getCtx(interfaceType).provider = provider; this.activeInterfaceType = interfaceType; } /** * Determines the default wallet interface to use when none is specified. * * - Preference order: Active provider > Ethereum > Solana > WalletConnect. * * @returns The default wallet interface type. * @throws {Error} If no wallet interfaces are initialized. */ defaultInterface() { if (this.activeInterfaceType) { return this.activeInterfaceType; } const initializedInterfaces = Object.keys(this.ctx); if (initializedInterfaces.includes(WalletInterfaceType.Ethereum)) { return WalletInterfaceType.Ethereum; } if (initializedInterfaces.includes(WalletInterfaceType.Solana)) { return WalletInterfaceType.Solana; } if (initializedInterfaces.includes(WalletInterfaceType.WalletConnect)) { return WalletInterfaceType.WalletConnect; } throw new Error("No interfaces initialized"); } /** * Retrieves the internal context for a given wallet interface. * * @param interfaceType - Wallet interface type. * @returns The context including wallet, stamper, and optional provider. * @throws {Error} If the interface is not initialized. */ getCtx(interfaceType) { const ctx = this.ctx[interfaceType]; if (!ctx) { throw new Error(`Interface '${interfaceType}' not initialised`); } return ctx; } } class WalletStamper { /** * Constructs a WalletStamper bound to a single wallet interface. * * @param wallet - The wallet interface used for signing. */ constructor(wallet) { this.wallet = wallet; } /** * Signs a payload and returns a standardized stamp header. * * - For Ethereum: * - Signs using EIP-191. * - Recovers and compresses the public key. * - Converts the signature into DER format. * - For Solana: * - Signs using Ed25519. * - Fetches the public key directly from the wallet. * * @param payload - The payload to sign. * @param provider - The wallet provider used for signing. * @returns A `TStamp` containing the header name and base64url-encoded JSON value. * @throws {Error} If signing or public key recovery fails. */ async stamp(payload, provider) { let signature; let publicKey; try { signature = await this.wallet.sign(payload, provider, SignIntent.SignMessage); } catch (error) { throw new Error(`Failed to sign the message: ${error}`); } const scheme = getSignatureSchemeFromProvider(provider); try { if (isEthereumProvider(provider)) { const { recoverPublicKey, hashMessage } = await import('viem'); const { compressRawPublicKey, toDerSignature } = await import('@turnkey/crypto'); const rawPublicKey = await recoverPublicKey({ hash: hashMessage(payload), signature: signature, }); const publicKeyHex = rawPublicKey.startsWith("0x") ? rawPublicKey.slice(2) : rawPublicKey; const publicKeyBytes = uint8ArrayFromHexString(publicKeyHex); const publicKeyBytesCompressed = compressRawPublicKey(publicKeyBytes); publicKey = uint8ArrayToHexString(publicKeyBytesCompressed); signature = toDerSignature(signature.replace("0x", "")); } else if (isSolanaProvider(provider)) { publicKey = await this.wallet.getPublicKey(provider); } else { // we should never hit this case // if we do then it means we added support for a new chain but missed updating the stamper throw new Error(`Unsupported provider namespace: ${provider.chainInfo.namespace}. Expected Ethereum or Solana.`); } } catch (error) { throw new Error(`Failed to recover public key: ${error}`); } return { stampHeaderName: STAMP_HEADER_NAME, stampHeaderValue: stringToBase64urlString(JSON.stringify({ publicKey, scheme, signature })), }; } } export { CrossPlatformWalletStamper, WalletStamper }; //# sourceMappingURL=stamper.mjs.map