@turnkey/core
Version:
A core JavaScript web and React Native package for interfacing with Turnkey's infrastructure.
649 lines (646 loc) • 27.2 kB
JavaScript
import { TurnkeyError, TurnkeyErrorCodes } from '@turnkey/sdk-types';
import { Chain, TurnkeyRequestError } from './__types__/base.mjs';
import { DEFAULT_XRP_ACCOUNTS, DEFAULT_TON_V4R2_ACCOUNTS, DEFAULT_TON_V3R2_ACCOUNTS, DEFAULT_DOGE_TESTNET_ACCOUNTS, DEFAULT_DOGE_MAINNET_ACCOUNTS, DEFAULT_XLM_ACCOUNTS, DEFAULT_SEI_ACCOUNTS, DEFAULT_BITCOIN_REGTEST_P2TR_ACCOUNTS, DEFAULT_BITCOIN_REGTEST_P2WSH_ACCOUNTS, DEFAULT_BITCOIN_REGTEST_P2WPKH_ACCOUNTS, DEFAULT_BITCOIN_REGTEST_P2SH_ACCOUNTS, DEFAULT_BITCOIN_REGTEST_P2PKH_ACCOUNTS, DEFAULT_BITCOIN_SIGNET_P2TR_ACCOUNTS, DEFAULT_BITCOIN_SIGNET_P2WSH_ACCOUNTS, DEFAULT_BITCOIN_SIGNET_P2WPKH_ACCOUNTS, DEFAULT_BITCOIN_SIGNET_P2SH_ACCOUNTS, DEFAULT_BITCOIN_SIGNET_P2PKH_ACCOUNTS, DEFAULT_BITCOIN_TESTNET_P2TR_ACCOUNTS, DEFAULT_BITCOIN_TESTNET_P2WSH_ACCOUNTS, DEFAULT_BITCOIN_TESTNET_P2WPKH_ACCOUNTS, DEFAULT_BITCOIN_TESTNET_P2SH_ACCOUNTS, DEFAULT_BITCOIN_TESTNET_P2PKH_ACCOUNTS, DEFAULT_BITCOIN_MAINNET_P2TR_ACCOUNTS, DEFAULT_BITCOIN_MAINNET_P2WSH_ACCOUNTS, DEFAULT_BITCOIN_MAINNET_P2WPKH_ACCOUNTS, DEFAULT_BITCOIN_MAINNET_P2SH_ACCOUNTS, DEFAULT_BITCOIN_MAINNET_P2PKH_ACCOUNTS, DEFAULT_APTOS_ACCOUNTS, DEFAULT_SUI_ACCOUNTS, DEFAULT_TRON_ACCOUNTS, DEFAULT_COSMOS_ACCOUNTS, DEFAULT_SOLANA_ACCOUNTS, DEFAULT_ETHEREUM_ACCOUNTS } from './turnkey-helpers.mjs';
import { fromDerSignature } from '@turnkey/crypto';
import { decodeBase64urlToString, uint8ArrayToHexString } from '@turnkey/encoding';
/**
* Configuration for all supported address formats.
*
* Includes:
* - encoding type
* - hash function
* - default accounts for the address format
* - display name for the address format
*
* ```ts
* // Example usage:
* import { addressFormatConfig } from "@turnkey/sdk-core";
*
* const config = addressFormatConfig["ADDRESS_FORMAT_ETHEREUM"];
* ```
*/
const addressFormatConfig = {
ADDRESS_FORMAT_UNCOMPRESSED: {
encoding: "PAYLOAD_ENCODING_HEXADECIMAL",
hashFunction: "HASH_FUNCTION_SHA256",
defaultAccounts: null,
displayName: "Uncompressed",
},
ADDRESS_FORMAT_COMPRESSED: {
encoding: "PAYLOAD_ENCODING_HEXADECIMAL",
hashFunction: "HASH_FUNCTION_SHA256",
defaultAccounts: null,
displayName: "Compressed",
},
ADDRESS_FORMAT_ETHEREUM: {
encoding: "PAYLOAD_ENCODING_HEXADECIMAL",
hashFunction: "HASH_FUNCTION_KECCAK256",
defaultAccounts: DEFAULT_ETHEREUM_ACCOUNTS,
displayName: "Ethereum",
},
ADDRESS_FORMAT_SOLANA: {
encoding: "PAYLOAD_ENCODING_HEXADECIMAL",
hashFunction: "HASH_FUNCTION_NOT_APPLICABLE",
defaultAccounts: DEFAULT_SOLANA_ACCOUNTS,
displayName: "Solana",
},
ADDRESS_FORMAT_COSMOS: {
encoding: "PAYLOAD_ENCODING_TEXT_UTF8",
hashFunction: "HASH_FUNCTION_SHA256",
defaultAccounts: DEFAULT_COSMOS_ACCOUNTS,
displayName: "Cosmos",
},
ADDRESS_FORMAT_TRON: {
encoding: "PAYLOAD_ENCODING_HEXADECIMAL",
hashFunction: "HASH_FUNCTION_SHA256",
defaultAccounts: DEFAULT_TRON_ACCOUNTS,
displayName: "Tron",
},
ADDRESS_FORMAT_SUI: {
encoding: "PAYLOAD_ENCODING_HEXADECIMAL",
hashFunction: "HASH_FUNCTION_NOT_APPLICABLE",
defaultAccounts: DEFAULT_SUI_ACCOUNTS,
displayName: "Sui",
},
ADDRESS_FORMAT_APTOS: {
encoding: "PAYLOAD_ENCODING_HEXADECIMAL",
hashFunction: "HASH_FUNCTION_NOT_APPLICABLE",
defaultAccounts: DEFAULT_APTOS_ACCOUNTS,
displayName: "Aptos",
},
ADDRESS_FORMAT_BITCOIN_MAINNET_P2PKH: {
encoding: "PAYLOAD_ENCODING_HEXADECIMAL",
hashFunction: "HASH_FUNCTION_SHA256",
defaultAccounts: DEFAULT_BITCOIN_MAINNET_P2PKH_ACCOUNTS,
displayName: "Bitcoin Mainnet P2PKH",
},
ADDRESS_FORMAT_BITCOIN_MAINNET_P2SH: {
encoding: "PAYLOAD_ENCODING_HEXADECIMAL",
hashFunction: "HASH_FUNCTION_SHA256",
defaultAccounts: DEFAULT_BITCOIN_MAINNET_P2SH_ACCOUNTS,
displayName: "Bitcoin Mainnet P2SH",
},
ADDRESS_FORMAT_BITCOIN_MAINNET_P2WPKH: {
encoding: "PAYLOAD_ENCODING_HEXADECIMAL",
hashFunction: "HASH_FUNCTION_SHA256",
defaultAccounts: DEFAULT_BITCOIN_MAINNET_P2WPKH_ACCOUNTS,
displayName: "Bitcoin Mainnet P2WPKH",
},
ADDRESS_FORMAT_BITCOIN_MAINNET_P2WSH: {
encoding: "PAYLOAD_ENCODING_HEXADECIMAL",
hashFunction: "HASH_FUNCTION_SHA256",
defaultAccounts: DEFAULT_BITCOIN_MAINNET_P2WSH_ACCOUNTS,
displayName: "Bitcoin Mainnet P2WSH",
},
ADDRESS_FORMAT_BITCOIN_MAINNET_P2TR: {
encoding: "PAYLOAD_ENCODING_HEXADECIMAL",
hashFunction: "HASH_FUNCTION_SHA256",
defaultAccounts: DEFAULT_BITCOIN_MAINNET_P2TR_ACCOUNTS,
displayName: "Bitcoin Mainnet P2TR",
},
ADDRESS_FORMAT_BITCOIN_TESTNET_P2PKH: {
encoding: "PAYLOAD_ENCODING_HEXADECIMAL",
hashFunction: "HASH_FUNCTION_SHA256",
defaultAccounts: DEFAULT_BITCOIN_TESTNET_P2PKH_ACCOUNTS,
displayName: "Bitcoin Testnet P2PKH",
},
ADDRESS_FORMAT_BITCOIN_TESTNET_P2SH: {
encoding: "PAYLOAD_ENCODING_HEXADECIMAL",
hashFunction: "HASH_FUNCTION_SHA256",
defaultAccounts: DEFAULT_BITCOIN_TESTNET_P2SH_ACCOUNTS,
displayName: "Bitcoin Testnet P2SH",
},
ADDRESS_FORMAT_BITCOIN_TESTNET_P2WPKH: {
encoding: "PAYLOAD_ENCODING_HEXADECIMAL",
hashFunction: "HASH_FUNCTION_SHA256",
defaultAccounts: DEFAULT_BITCOIN_TESTNET_P2WPKH_ACCOUNTS,
displayName: "Bitcoin Testnet P2WPKH",
},
ADDRESS_FORMAT_BITCOIN_TESTNET_P2WSH: {
encoding: "PAYLOAD_ENCODING_HEXADECIMAL",
hashFunction: "HASH_FUNCTION_SHA256",
defaultAccounts: DEFAULT_BITCOIN_TESTNET_P2WSH_ACCOUNTS,
displayName: "Bitcoin Testnet P2WSH",
},
ADDRESS_FORMAT_BITCOIN_TESTNET_P2TR: {
encoding: "PAYLOAD_ENCODING_HEXADECIMAL",
hashFunction: "HASH_FUNCTION_SHA256",
defaultAccounts: DEFAULT_BITCOIN_TESTNET_P2TR_ACCOUNTS,
displayName: "Bitcoin Testnet P2TR",
},
ADDRESS_FORMAT_BITCOIN_SIGNET_P2PKH: {
encoding: "PAYLOAD_ENCODING_HEXADECIMAL",
hashFunction: "HASH_FUNCTION_SHA256",
defaultAccounts: DEFAULT_BITCOIN_SIGNET_P2PKH_ACCOUNTS,
displayName: "Bitcoin Signet P2PKH",
},
ADDRESS_FORMAT_BITCOIN_SIGNET_P2SH: {
encoding: "PAYLOAD_ENCODING_HEXADECIMAL",
hashFunction: "HASH_FUNCTION_SHA256",
defaultAccounts: DEFAULT_BITCOIN_SIGNET_P2SH_ACCOUNTS,
displayName: "Bitcoin Signet P2SH",
},
ADDRESS_FORMAT_BITCOIN_SIGNET_P2WPKH: {
encoding: "PAYLOAD_ENCODING_HEXADECIMAL",
hashFunction: "HASH_FUNCTION_SHA256",
defaultAccounts: DEFAULT_BITCOIN_SIGNET_P2WPKH_ACCOUNTS,
displayName: "Bitcoin Signet P2WPKH",
},
ADDRESS_FORMAT_BITCOIN_SIGNET_P2WSH: {
encoding: "PAYLOAD_ENCODING_HEXADECIMAL",
hashFunction: "HASH_FUNCTION_SHA256",
defaultAccounts: DEFAULT_BITCOIN_SIGNET_P2WSH_ACCOUNTS,
displayName: "Bitcoin Signet P2WSH",
},
ADDRESS_FORMAT_BITCOIN_SIGNET_P2TR: {
encoding: "PAYLOAD_ENCODING_HEXADECIMAL",
hashFunction: "HASH_FUNCTION_SHA256",
defaultAccounts: DEFAULT_BITCOIN_SIGNET_P2TR_ACCOUNTS,
displayName: "Bitcoin Signet P2TR",
},
ADDRESS_FORMAT_BITCOIN_REGTEST_P2PKH: {
encoding: "PAYLOAD_ENCODING_HEXADECIMAL",
hashFunction: "HASH_FUNCTION_SHA256",
defaultAccounts: DEFAULT_BITCOIN_REGTEST_P2PKH_ACCOUNTS,
displayName: "Bitcoin Regtest P2PKH",
},
ADDRESS_FORMAT_BITCOIN_REGTEST_P2SH: {
encoding: "PAYLOAD_ENCODING_HEXADECIMAL",
hashFunction: "HASH_FUNCTION_SHA256",
defaultAccounts: DEFAULT_BITCOIN_REGTEST_P2SH_ACCOUNTS,
displayName: "Bitcoin Regtest P2SH",
},
ADDRESS_FORMAT_BITCOIN_REGTEST_P2WPKH: {
encoding: "PAYLOAD_ENCODING_HEXADECIMAL",
hashFunction: "HASH_FUNCTION_SHA256",
defaultAccounts: DEFAULT_BITCOIN_REGTEST_P2WPKH_ACCOUNTS,
displayName: "Bitcoin Regtest P2WPKH",
},
ADDRESS_FORMAT_BITCOIN_REGTEST_P2WSH: {
encoding: "PAYLOAD_ENCODING_HEXADECIMAL",
hashFunction: "HASH_FUNCTION_SHA256",
defaultAccounts: DEFAULT_BITCOIN_REGTEST_P2WSH_ACCOUNTS,
displayName: "Bitcoin Regtest P2WSH",
},
ADDRESS_FORMAT_BITCOIN_REGTEST_P2TR: {
encoding: "PAYLOAD_ENCODING_HEXADECIMAL",
hashFunction: "HASH_FUNCTION_SHA256",
defaultAccounts: DEFAULT_BITCOIN_REGTEST_P2TR_ACCOUNTS,
displayName: "Bitcoin Regtest P2TR",
},
ADDRESS_FORMAT_SEI: {
encoding: "PAYLOAD_ENCODING_TEXT_UTF8",
hashFunction: "HASH_FUNCTION_SHA256",
defaultAccounts: DEFAULT_SEI_ACCOUNTS,
displayName: "Sei",
},
ADDRESS_FORMAT_XLM: {
encoding: "PAYLOAD_ENCODING_HEXADECIMAL",
hashFunction: "HASH_FUNCTION_NOT_APPLICABLE",
defaultAccounts: DEFAULT_XLM_ACCOUNTS,
displayName: "Xlm",
},
ADDRESS_FORMAT_DOGE_MAINNET: {
encoding: "PAYLOAD_ENCODING_HEXADECIMAL",
hashFunction: "HASH_FUNCTION_SHA256",
defaultAccounts: DEFAULT_DOGE_MAINNET_ACCOUNTS,
displayName: "Doge Mainnet",
},
ADDRESS_FORMAT_DOGE_TESTNET: {
encoding: "PAYLOAD_ENCODING_HEXADECIMAL",
hashFunction: "HASH_FUNCTION_SHA256",
defaultAccounts: DEFAULT_DOGE_TESTNET_ACCOUNTS,
displayName: "Doge Testnet",
},
ADDRESS_FORMAT_TON_V3R2: {
encoding: "PAYLOAD_ENCODING_HEXADECIMAL",
hashFunction: "HASH_FUNCTION_NOT_APPLICABLE",
defaultAccounts: DEFAULT_TON_V3R2_ACCOUNTS,
displayName: "Ton V3R2",
},
ADDRESS_FORMAT_TON_V4R2: {
encoding: "PAYLOAD_ENCODING_HEXADECIMAL",
hashFunction: "HASH_FUNCTION_NOT_APPLICABLE",
defaultAccounts: DEFAULT_TON_V4R2_ACCOUNTS,
displayName: "Ton V4R2",
},
ADDRESS_FORMAT_TON_V5R1: {
encoding: "PAYLOAD_ENCODING_HEXADECIMAL",
hashFunction: "HASH_FUNCTION_NOT_APPLICABLE",
defaultAccounts: null,
displayName: "Ton V5R1",
},
ADDRESS_FORMAT_XRP: {
encoding: "PAYLOAD_ENCODING_HEXADECIMAL",
hashFunction: "HASH_FUNCTION_SHA256",
defaultAccounts: DEFAULT_XRP_ACCOUNTS,
displayName: "XRP",
},
};
const googleISS = "https://accounts.google.com";
const isReactNative = () => {
return (typeof navigator !== "undefined" && navigator.product === "ReactNative");
};
const isWeb = () => {
return typeof window !== "undefined" && typeof document !== "undefined";
};
const generateRandomBuffer = () => {
const arr = new Uint8Array(32);
crypto.getRandomValues(arr);
return arr.buffer;
};
Array.from({ length: 256 }, (_, i) => i.toString(16).padStart(2, "0"));
const toExternalTimestamp = (date = new Date()) => {
const millis = date.getTime();
const seconds = Math.floor(millis / 1000);
const nanos = (millis % 1000) * 1_000_000;
return {
seconds: seconds.toString(),
nanos: nanos.toString(),
};
};
function parseSession(token) {
if (typeof token !== "string") {
return token;
}
const [, payload] = token.split(".");
if (!payload) {
throw new Error("Invalid JWT: Missing payload");
}
const decoded = JSON.parse(atob(payload));
const { exp, public_key: publicKey, session_type: sessionType, user_id: userId, organization_id: organizationId, } = decoded;
if (!exp || !publicKey || !sessionType || !userId || !organizationId) {
throw new Error("JWT payload missing required fields");
}
const expSeconds = Math.ceil((exp * 1000 - Date.now()) / 1000);
return {
sessionType,
userId,
organizationId,
expiry: exp,
expirationSeconds: expSeconds.toString(),
publicKey,
token,
};
}
function getHashFunction(addressFormat) {
const config = addressFormatConfig[addressFormat];
if (!config) {
throw new TurnkeyError(`Unsupported address format: ${addressFormat}`, TurnkeyErrorCodes.INVALID_REQUEST);
}
return config.hashFunction;
}
function getEncodingType(addressFormat) {
const config = addressFormatConfig[addressFormat];
if (!config) {
throw new TurnkeyError(`Unsupported address format: ${addressFormat}`, TurnkeyErrorCodes.INVALID_REQUEST);
}
return config.encoding;
}
function getEncodedMessage(addressFormat, rawMessage) {
const config = addressFormatConfig[addressFormat];
if (!config) {
throw new TurnkeyError(`Unsupported address format: ${addressFormat}`, TurnkeyErrorCodes.INVALID_REQUEST);
}
if (config.encoding === "PAYLOAD_ENCODING_HEXADECIMAL") {
return ("0x" +
Array.from(new TextEncoder().encode(rawMessage))
.map((b) => b.toString(16).padStart(2, "0"))
.join(""));
}
return rawMessage;
}
const broadcastTransaction = async (params) => {
const { signedTransaction, rpcUrl, transactionType } = params;
switch (transactionType) {
case "TRANSACTION_TYPE_SOLANA": {
const response = await fetch(rpcUrl, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
jsonrpc: "2.0",
id: 1,
method: "sendTransaction",
params: [signedTransaction],
}),
});
const json = await response.json();
if (json.error) {
throw new TurnkeyError(`Solana RPC Error: ${json.error.message}`, TurnkeyErrorCodes.SIGN_AND_SEND_TRANSACTION_ERROR);
}
return json.result;
}
case "TRANSACTION_TYPE_ETHEREUM": {
const response = await fetch(rpcUrl, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
jsonrpc: "2.0",
id: 1,
method: "eth_sendRawTransaction",
params: [signedTransaction],
}),
});
const json = await response.json();
if (json.error) {
throw new TurnkeyError(`Ethereum RPC Error: ${json.error.message}`, TurnkeyErrorCodes.SIGN_AND_SEND_TRANSACTION_ERROR);
}
return json.result;
}
case "TRANSACTION_TYPE_TRON": {
const response = await fetch(`${rpcUrl}/wallet/broadcasthex`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ transaction: signedTransaction }),
});
const json = await response.json();
if (!json.result) {
throw new TurnkeyError(`Tron RPC Error: ${json.message}`, TurnkeyErrorCodes.SIGN_AND_SEND_TRANSACTION_ERROR);
}
return json.txid;
}
default:
throw new TurnkeyError(`Unsupported transaction type for broadcasting: ${transactionType}`, TurnkeyErrorCodes.SIGN_AND_SEND_TRANSACTION_ERROR);
}
};
function splitSignature(signature, addressFormat) {
const hex = signature.replace(/^0x/, "");
if (addressFormat === "ADDRESS_FORMAT_ETHEREUM") {
// this is a ECDSA signature
if (hex.length === 130) {
const r = hex.slice(0, 64);
const s = hex.slice(64, 128);
const v = hex.slice(128, 130);
return { r, s, v };
}
// this is a DER-encoded signatures (e.g., Ledger)
const raw = fromDerSignature(hex);
const r = uint8ArrayToHexString(raw.slice(0, 32));
const s = uint8ArrayToHexString(raw.slice(32, 64));
// DER signatures do not have a v component
// so we return 00 to match what Turnkey does
const v = "00";
return { r, s, v };
}
if (addressFormat === "ADDRESS_FORMAT_SOLANA") {
if (hex.length !== 128) {
throw new Error(`Invalid Solana signature length: expected 64 bytes (128 hex), got ${hex.length}`);
}
// this is a Ed25519 signature
const r = hex.slice(0, 64);
const s = hex.slice(64, 128);
// solana signatures do not have a v component
// so we return 00 to match what Turnkey does
return { r, s, v: "00" };
}
throw new Error(`Unsupported address format or invalid signature length: ${hex.length}`);
}
// Type guard to check if accounts is WalletAccount[]
function isWalletAccountArray(arr) {
return (arr.length === 0 ||
(typeof arr[0] === "object" &&
"addressFormat" in arr[0] &&
"curve" in arr[0] &&
"path" in arr[0] &&
"pathFormat" in arr[0]));
}
function createWalletAccountFromAddressFormat(addressFormat) {
const walletAccount = addressFormatConfig[addressFormat].defaultAccounts;
if (!walletAccount) {
throw new Error(`Unsupported address format: ${addressFormat}`);
}
if (walletAccount[0]) {
return walletAccount[0];
}
throw new Error(`No default accounts defined for address format: ${addressFormat}`);
}
/**@internal */
function generateWalletAccountsFromAddressFormat(params) {
const { addresses, existingWalletAccounts } = params;
const pathMap = new Map();
// Build a lookup for max index per (addressFormat, basePath)
const maxIndexMap = new Map();
if (existingWalletAccounts && existingWalletAccounts.length > 0) {
for (const acc of existingWalletAccounts) {
// Normalize base path (remove account index)
const basePath = acc.path.replace(/^((?:[^\/]+\/){3})[^\/]+/, "$1");
const key = `${acc.addressFormat}:${basePath}`;
const idxSegment = acc.path.split("/")[3];
const idx = idxSegment ? parseInt(idxSegment.replace(/'/, ""), 10) : -1;
if (!isNaN(idx)) {
maxIndexMap.set(key, Math.max(maxIndexMap.get(key) ?? -1, idx));
}
}
}
return addresses.map((addressFormat) => {
const account = createWalletAccountFromAddressFormat(addressFormat);
const basePath = account.path.replace(/^((?:[^\/]+\/){3})[^\/]+/, "$1");
const key = `${addressFormat}:${basePath}`;
let nextIndex = 0;
if (maxIndexMap.has(key)) {
nextIndex = maxIndexMap.get(key) + 1;
}
else if (pathMap.has(account.path)) {
nextIndex = pathMap.get(account.path);
}
const pathWithIndex = account.path.replace(/^((?:[^\/]*\/){3})(\d+)/, (_, prefix) => `${prefix}${nextIndex}`);
pathMap.set(account.path, nextIndex + 1);
return {
...account,
path: pathWithIndex,
};
});
}
function buildSignUpBody(params) {
const { createSubOrgParams } = params;
const websiteName = window.location.hostname;
let authenticators = [];
if (createSubOrgParams?.authenticators?.length) {
authenticators =
createSubOrgParams?.authenticators?.map((authenticator) => ({
authenticatorName: authenticator.authenticatorName || `${websiteName}-${Date.now()}`,
challenge: authenticator.challenge,
attestation: authenticator.attestation,
})) || [];
}
let apiKeys = [];
if (createSubOrgParams?.apiKeys?.length) {
apiKeys = createSubOrgParams.apiKeys
.filter((apiKey) => apiKey.curveType !== undefined)
.map((apiKey) => ({
apiKeyName: apiKey.apiKeyName || `api-key-${Date.now()}`,
publicKey: apiKey.publicKey,
curveType: apiKey.curveType,
...(apiKey?.expirationSeconds && {
expirationSeconds: apiKey.expirationSeconds,
}),
}));
}
return {
userName: createSubOrgParams?.userName ||
createSubOrgParams?.userEmail ||
`user-${Date.now()}`,
...(createSubOrgParams?.userEmail && {
userEmail: createSubOrgParams?.userEmail,
}),
...(createSubOrgParams?.authenticators?.length
? {
authenticators,
}
: { authenticators: [] }),
...(createSubOrgParams?.userPhoneNumber && {
userPhoneNumber: createSubOrgParams.userPhoneNumber,
}),
...(createSubOrgParams?.userTag && {
userTag: createSubOrgParams?.userTag,
}),
organizationName: createSubOrgParams?.subOrgName || `sub-org-${Date.now()}`,
...(createSubOrgParams?.verificationToken && {
verificationToken: createSubOrgParams?.verificationToken,
}),
...(createSubOrgParams?.apiKeys?.length
? {
apiKeys,
}
: { apiKeys: [] }),
...(createSubOrgParams?.oauthProviders?.length
? {
oauthProviders: createSubOrgParams.oauthProviders,
}
: { oauthProviders: [] }),
...(createSubOrgParams?.customWallet && {
wallet: {
walletName: createSubOrgParams.customWallet.walletName,
accounts: createSubOrgParams.customWallet.walletAccounts,
},
}),
};
}
/**
* Extracts the public key from a Turnkey stamp header value.
* @param stampHeaderValue - The base64url encoded stamp header value
* @returns The public key as a hex string
*/
function getPublicKeyFromStampHeader(stampHeaderValue) {
try {
// we decode the base64url string to get the JSON stamp
const stampJson = decodeBase64urlToString(stampHeaderValue);
// we parse the JSON to get the stamp object
const stamp = JSON.parse(stampJson);
return stamp.publicKey;
}
catch (error) {
throw new Error(`Failed to extract public key from stamp header: ${error instanceof Error ? error.message : String(error)}`);
}
}
/**@internal */
function isEthereumProvider(provider) {
return provider.chainInfo.namespace === Chain.Ethereum;
}
/**@internal */
function isSolanaProvider(provider) {
return provider.chainInfo.namespace === Chain.Solana;
}
/** @internal */
function findWalletProviderFromAddress(address, providers) {
for (const provider of providers) {
if (provider.connectedAddresses.includes(address)) {
return provider;
}
}
// no provider found for that address
return undefined;
}
/**@internal */
async function getAuthProxyConfig(authProxyConfigId, authProxyUrl) {
const fullUrl = (authProxyUrl ?? "https://authproxy.turnkey.com") + "/v1/wallet_kit_config";
var headers = {
"Content-Type": "application/json",
"X-Auth-Proxy-Config-ID": authProxyConfigId,
};
const response = await fetch(fullUrl, {
method: "POST",
headers: headers,
});
if (!response.ok) {
let res;
try {
res = await response.json();
}
catch (_) {
throw new Error(`${response.status} ${response.statusText}`);
}
throw new TurnkeyRequestError(res);
}
const data = await response.json();
return data;
}
/**
* @internal
* Executes an async function with error handling.
*
* @param fn The async function to execute with error handling
* @param errorOptions Options for customizing error handling
* @param errorOptions.catchFn Optional function to execute in the catch block
* @param errorOptions.errorMessage The default error message to use if no custom message is found
* @param errorOptions.errorCode The default error code to use if no custom message is found
* @param errorOptions.customMessageByCodes Optional mapping of error codes to custom messages, if you're trying to target a specific error code and surface a custom message, use this
* @param errorOptions.customMessageByMessages Optional mapping of error messages to custom messages, if you're trying to target a specific error message and surface a custom message, use this
* @param finallyFn Optional function to execute in the finally block
* @returns The result of the async function or throws an error
*/
async function withTurnkeyErrorHandling(fn, catchOptions, finallyOptions) {
const { errorMessage, errorCode, customMessageByCodes, customMessageByMessages, catchFn, } = catchOptions;
const finallyFn = finallyOptions?.finallyFn;
try {
return await fn();
}
catch (error) {
await catchFn?.();
if (error instanceof TurnkeyError) {
const customCodeMessage = customMessageByCodes?.[error.code];
if (customCodeMessage) {
throw new TurnkeyError(customCodeMessage.message, customCodeMessage.code, error);
}
throwMatchingMessage(error.message, customMessageByMessages, error);
throw error;
}
else if (error instanceof TurnkeyRequestError) {
throwMatchingMessage(error.message, customMessageByMessages, error);
throw new TurnkeyError(errorMessage, errorCode, error);
}
else if (error instanceof Error) {
throwMatchingMessage(error.message, customMessageByMessages, error);
// Wrap other errors in a TurnkeyError
throw new TurnkeyError(errorMessage, errorCode, error);
}
else {
throwMatchingMessage(String(error), customMessageByMessages, error);
// Handle non-Error exceptions
throw new TurnkeyError(String(error), errorCode, error);
}
}
finally {
await finallyFn?.();
}
}
/**
* Throws a TurnkeyError with a custom message if the error message matches any key in customMessageByMessages.
* If no match is found, it does nothing.
*
* @param errorMessage The error message to check against the custom messages.
* @param customMessageByMessages An object mapping error messages to custom messages and codes.
* @param error The original error that triggered this function.
*/
const throwMatchingMessage = (errorMessage, customMessageByMessages, error) => {
if (customMessageByMessages &&
Object.keys(customMessageByMessages).length > 0) {
Object.keys(customMessageByMessages).forEach((key) => {
if (errorMessage.includes(key)) {
throw new TurnkeyError(customMessageByMessages[key].message, customMessageByMessages[key].code, error);
}
});
}
};
export { addressFormatConfig, broadcastTransaction, buildSignUpBody, createWalletAccountFromAddressFormat, findWalletProviderFromAddress, generateRandomBuffer, generateWalletAccountsFromAddressFormat, getAuthProxyConfig, getEncodedMessage, getEncodingType, getHashFunction, getPublicKeyFromStampHeader, googleISS, isEthereumProvider, isReactNative, isSolanaProvider, isWalletAccountArray, isWeb, parseSession, splitSignature, toExternalTimestamp, withTurnkeyErrorHandling };
//# sourceMappingURL=utils.mjs.map