@shogun-sdk/accounts
Version:
Shogun with Turnkey: configs, encryption, authentication with Telegram/Turnkey OIDC, etc.
162 lines (135 loc) • 4.63 kB
text/typescript
import { getSecret } from './aes.js';
import { ApiKeyStamper, TurnkeyServerClient } from '@turnkey/sdk-server';
import { getPublicKey } from '@turnkey/crypto';
import { createAccount } from '@turnkey/viem';
import { createWalletClient, fallback, http, WalletClient, Chain } from 'viem';
import { getRpcUrls, getEthChainViem, SOLANA_CHAIN_ID } from '@shogun-sdk/money-legos';
import { TurnkeySigner } from '@turnkey/solana';
export function uint8ArrayToHex(uint8Array: Uint8Array): string {
return Array.from(uint8Array)
.map((byte) => byte.toString(16).padStart(2, '0'))
.join('');
}
export const getTurnkeyClient = async (
orgId: string,
encryptedPrivateKey: string,
publicOrganizationId: string,
dataEncryptionKey: string,
): Promise<TurnkeyServerClient> => {
if (!orgId) {
throw new Error('Org ID is required');
}
const decryptedPrivateKey = getSecret(orgId, encryptedPrivateKey, publicOrganizationId, dataEncryptionKey);
if (!decryptedPrivateKey) {
throw new Error('Failed to decrypt private key');
}
const publicKey = uint8ArrayToHex(getPublicKey(decryptedPrivateKey));
const user_stamper = new ApiKeyStamper({
apiPublicKey: publicKey,
apiPrivateKey: decryptedPrivateKey,
});
return new TurnkeyServerClient({
apiBaseUrl: 'https://api.turnkey.com',
organizationId: orgId,
stamper: user_stamper,
});
};
/**
* Returns a Viem WalletClient for EVM chains using Turnkey Server.
* @param orgId Organization ID
* @param walletAddress The EVM address to sign with
* @param chainId The EVM chain ID
*/
export const getEVMSignerTurnkeyServer = async (
orgId: string,
walletAddress: string,
chainId: number,
encryptedPrivateKey: string,
publicOrganizationId: string,
dataEncryptionKey: string,
): Promise<WalletClient> => {
if (!orgId) {
throw new Error('Org ID is required');
}
if (!walletAddress) {
throw new Error('Wallet address is required');
}
const decryptedPrivateKey = getSecret(orgId, encryptedPrivateKey, publicOrganizationId, dataEncryptionKey);
if (!decryptedPrivateKey) {
throw new Error('Failed to decrypt private key');
}
const publicKey = uint8ArrayToHex(getPublicKey(decryptedPrivateKey));
const user_stamper = new ApiKeyStamper({
apiPublicKey: publicKey,
apiPrivateKey: decryptedPrivateKey,
});
const httpClient = new TurnkeyServerClient({
apiBaseUrl: 'https://api.turnkey.com',
organizationId: orgId,
stamper: user_stamper,
});
// Use the same logic as getEVMChains to get the correct chain object
const chain = getEthChainViem(chainId) as Chain;
if (!chain) {
throw new Error(`Unsupported EVM chainId: ${chainId}`);
}
const turnkeyAccount = await createAccount({
client: httpClient as any,
organizationId: orgId,
signWith: walletAddress,
ethereumAddress: walletAddress,
});
const rpcUrls = getRpcUrls()[chainId]?.rpc;
if (!rpcUrls || rpcUrls.length === 0) {
throw new Error(`No RPC URLs found for chainId: ${chainId}`);
}
const transports = rpcUrls.map((url: string) => http(url));
const client = createWalletClient({
account: turnkeyAccount as any,
chain,
transport: fallback(transports),
});
return client;
};
/**
* Returns a TurnkeySigner for Solana using Turnkey Server.
* @param orgId Organization ID
* @param address The Solana address to sign with (optional, not required for TurnkeySigner)
*/
export const getTurnkeySolanaSignerServer = async (
orgId: string,
encryptedPrivateKey: string,
publicOrganizationId: string,
dataEncryptionKey: string,
): Promise<TurnkeySigner> => {
if (!orgId) {
throw new Error('Org ID is required');
}
const decryptedPrivateKey = getSecret(orgId, encryptedPrivateKey, publicOrganizationId, dataEncryptionKey);
if (!decryptedPrivateKey) {
throw new Error('Failed to decrypt private key');
}
const publicKey = uint8ArrayToHex(getPublicKey(decryptedPrivateKey));
const user_stamper = new ApiKeyStamper({
apiPublicKey: publicKey,
apiPrivateKey: decryptedPrivateKey,
});
const httpClient = new TurnkeyServerClient({
apiBaseUrl: 'https://api.turnkey.com',
organizationId: orgId,
stamper: user_stamper,
});
const turnkeySigner = new TurnkeySigner({
organizationId: orgId,
client: httpClient as any,
});
return turnkeySigner;
};
export const getChainTurnkeySigner = (
chainId: number,
): typeof getEVMSignerTurnkeyServer | typeof getTurnkeySolanaSignerServer => {
if (chainId === SOLANA_CHAIN_ID) {
return getTurnkeySolanaSignerServer;
}
return getEVMSignerTurnkeyServer;
};