@turnkey/core
Version:
A core JavaScript web and React Native package for interfacing with Turnkey's infrastructure.
183 lines (179 loc) • 7.57 kB
JavaScript
;
var base = require('../__types__/base.js');
var encoding = require('@turnkey/encoding');
var utils = require('../utils.js');
const SIGNATURE_SCHEME_TK_API_SECP256K1_EIP191 = "SIGNATURE_SCHEME_TK_API_SECP256K1_EIP191";
const SIGNATURE_SCHEME_TK_API_ED25519 = "SIGNATURE_SCHEME_TK_API_ED25519";
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(base.WalletInterfaceType.Ethereum)) {
return base.WalletInterfaceType.Ethereum;
}
if (initializedInterfaces.includes(base.WalletInterfaceType.Solana)) {
return base.WalletInterfaceType.Solana;
}
if (initializedInterfaces.includes(base.WalletInterfaceType.WalletConnect)) {
return base.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, base.SignIntent.SignMessage);
}
catch (error) {
throw new Error(`Failed to sign the message: ${error}`);
}
const scheme = utils.isSolanaProvider(provider)
? SIGNATURE_SCHEME_TK_API_ED25519
: SIGNATURE_SCHEME_TK_API_SECP256K1_EIP191;
try {
if (utils.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 = encoding.uint8ArrayFromHexString(publicKeyHex);
const publicKeyBytesCompressed = compressRawPublicKey(publicKeyBytes);
publicKey = encoding.uint8ArrayToHexString(publicKeyBytesCompressed);
signature = toDerSignature(signature.replace("0x", ""));
}
else {
publicKey = await this.wallet.getPublicKey(provider);
}
}
catch (error) {
throw new Error(`Failed to recover public key: ${error}`);
}
return {
stampHeaderName: STAMP_HEADER_NAME,
stampHeaderValue: encoding.stringToBase64urlString(JSON.stringify({ publicKey, scheme, signature })),
};
}
}
exports.CrossPlatformWalletStamper = CrossPlatformWalletStamper;
exports.WalletStamper = WalletStamper;
//# sourceMappingURL=stamper.js.map