UNPKG

@turnkey/viem

Version:

Turnkey Helpers to work with Viem

287 lines (283 loc) 11 kB
'use strict'; var viem = require('viem'); var accounts = require('viem/accounts'); var http = require('@turnkey/http'); var apiKeyStamper = require('@turnkey/api-key-stamper'); class TurnkeyConsensusNeededError extends viem.BaseError { constructor({ message = "Turnkey activity requires consensus.", activityId, activityStatus, }) { super(message); this.name = "TurnkeyConsensusNeededError"; this.activityId = activityId; this.activityStatus = activityStatus; } } class TurnkeyActivityError extends viem.BaseError { constructor({ message = "Received unexpected Turnkey activity status.", activityId, activityStatus, }) { super(message); this.name = "TurnkeyActivityError"; this.activityId = activityId; this.activityStatus = activityStatus; } } function createAccountWithAddress(input) { const { client, organizationId, signWith } = input; let { ethereumAddress } = input; if (!signWith) { throw new http.TurnkeyActivityError({ message: `Missing signWith parameter`, }); } if (viem.isAddress(signWith)) { // override provided `ethereumAddress` ethereumAddress = signWith; } else if (!ethereumAddress) { throw new TurnkeyActivityError({ message: `Missing ethereumAddress parameter`, }); } return accounts.toAccount({ address: ethereumAddress, signMessage: function ({ message, }) { return signMessage(client, message, organizationId, signWith); }, signTransaction: function (transaction, options) { const serializer = options?.serializer ?? viem.serializeTransaction; return signTransaction(client, transaction, serializer, organizationId, signWith); }, signTypedData: function (typedData) { return signTypedData(client, typedData, organizationId, signWith); }, }); } async function createAccount(input) { const { client, organizationId, signWith } = input; let { ethereumAddress } = input; if (!signWith) { throw new TurnkeyActivityError({ message: `Missing signWith parameter`, }); } if (viem.isAddress(signWith)) { // override provided `ethereumAddress` ethereumAddress = signWith; } else if (!ethereumAddress) { // we have a private key ID, but not an ethereumAddress const data = await client.getPrivateKey({ privateKeyId: signWith, organizationId: organizationId, }); ethereumAddress = data.privateKey.addresses.find((item) => item.format === "ADDRESS_FORMAT_ETHEREUM")?.address; if (typeof ethereumAddress !== "string" || !ethereumAddress) { throw new TurnkeyActivityError({ message: `Unable to find Ethereum address for key ${signWith} under organization ${organizationId}`, }); } } return createAccountWithAddress({ client, organizationId, signWith, ethereumAddress, }); } /** * Creates a new Custom Account backed by a Turnkey API key. * @deprecated use {@link createAccount} instead. */ async function createApiKeyAccount(config) { const { apiPublicKey, apiPrivateKey, baseUrl, organizationId, privateKeyId } = config; const stamper = new apiKeyStamper.ApiKeyStamper({ apiPublicKey: apiPublicKey, apiPrivateKey: apiPrivateKey, }); const client = new http.TurnkeyClient({ baseUrl: baseUrl, }, stamper); const data = await client.getPrivateKey({ privateKeyId: privateKeyId, organizationId: organizationId, }); const ethereumAddress = data.privateKey.addresses.find((item) => item.format === "ADDRESS_FORMAT_ETHEREUM")?.address; if (typeof ethereumAddress !== "string" || !ethereumAddress) { throw new http.TurnkeyActivityError({ message: `Unable to find Ethereum address for key ${privateKeyId} under organization ${organizationId}`, }); } return accounts.toAccount({ address: ethereumAddress, signMessage: function ({ message, }) { return signMessage(client, message, organizationId, privateKeyId); }, signTransaction: function (transaction, options) { const serializer = options?.serializer ?? viem.serializeTransaction; return signTransaction(client, transaction, serializer, organizationId, privateKeyId); }, signTypedData: function (typedData) { return signTypedData(client, typedData, organizationId, privateKeyId); }, }); } async function signMessage(client, message, organizationId, signWith) { const hashedMessage = viem.hashMessage(message); const signedMessage = await signMessageWithErrorWrapping(client, hashedMessage, organizationId, signWith); return `${signedMessage}`; } async function signTransaction(client, transaction, serializer, organizationId, signWith) { const serializedTx = serializer(transaction); const nonHexPrefixedSerializedTx = serializedTx.replace(/^0x/, ""); return await signTransactionWithErrorWrapping(client, nonHexPrefixedSerializedTx, organizationId, signWith); } async function signTypedData(client, data, organizationId, signWith) { const hashToSign = viem.hashTypedData(data); return await signMessageWithErrorWrapping(client, hashToSign, organizationId, signWith); } async function signTransactionWithErrorWrapping(client, unsignedTransaction, organizationId, signWith) { let signedTx; try { signedTx = await signTransactionImpl(client, unsignedTransaction, organizationId, signWith); } catch (error) { // Wrap Turnkey error in Viem-specific error if (error instanceof http.TurnkeyActivityError) { throw new TurnkeyActivityError({ message: error.message, activityId: error.activityId, activityStatus: error.activityStatus, }); } if (error instanceof http.TurnkeyActivityConsensusNeededError) { throw new TurnkeyConsensusNeededError({ message: error.message, activityId: error.activityId, activityStatus: error.activityStatus, }); } throw new TurnkeyActivityError({ message: `Failed to sign: ${error.message}`, }); } return `0x${signedTx}`; } async function signTransactionImpl(client, unsignedTransaction, organizationId, signWith) { if (client instanceof http.TurnkeyClient) { const { activity } = await client.signTransaction({ type: "ACTIVITY_TYPE_SIGN_TRANSACTION_V2", organizationId: organizationId, parameters: { signWith, type: "TRANSACTION_TYPE_ETHEREUM", unsignedTransaction: unsignedTransaction, }, timestampMs: String(Date.now()), // millisecond timestamp }); http.assertActivityCompleted(activity); return http.assertNonNull(activity?.result?.signTransactionResult?.signedTransaction); } else { const { activity, signedTransaction } = await client.signTransaction({ organizationId, signWith, type: "TRANSACTION_TYPE_ETHEREUM", unsignedTransaction: unsignedTransaction, }); http.assertActivityCompleted(activity); return http.assertNonNull(signedTransaction); } } async function signMessageWithErrorWrapping(client, message, organizationId, signWith) { let signedMessage; try { signedMessage = await signMessageImpl(client, message, organizationId, signWith); } catch (error) { // Wrap Turnkey error in Viem-specific error if (error instanceof http.TurnkeyActivityError) { throw new TurnkeyActivityError({ message: error.message, activityId: error.activityId, activityStatus: error.activityStatus, }); } if (error instanceof http.TurnkeyActivityConsensusNeededError) { throw new TurnkeyConsensusNeededError({ message: error.message, activityId: error.activityId, activityStatus: error.activityStatus, }); } throw new TurnkeyActivityError({ message: `Failed to sign: ${error.message}`, }); } return signedMessage; } async function signMessageImpl(client, message, organizationId, signWith) { let result; if (client instanceof http.TurnkeyClient) { const { activity } = await client.signRawPayload({ type: "ACTIVITY_TYPE_SIGN_RAW_PAYLOAD_V2", organizationId: organizationId, parameters: { signWith, payload: message, encoding: "PAYLOAD_ENCODING_HEXADECIMAL", hashFunction: "HASH_FUNCTION_NO_OP", }, timestampMs: String(Date.now()), // millisecond timestamp }); http.assertActivityCompleted(activity); result = http.assertNonNull(activity?.result?.signRawPayloadResult); } else { const { activity, r, s, v } = await client.signRawPayload({ organizationId, signWith, payload: message, encoding: "PAYLOAD_ENCODING_HEXADECIMAL", hashFunction: "HASH_FUNCTION_NO_OP", }); http.assertActivityCompleted(activity); result = { r, s, v, }; } return http.assertNonNull(serializeSignature(result)); } function serializeSignature(sig) { // TODO: update this deprecated method return viem.signatureToHex({ r: `0x${sig.r}`, s: `0x${sig.s}`, v: sig.v === "00" ? 27n : 28n, }); } function isTurnkeyActivityConsensusNeededError(error) { return (typeof error.walk === "function" && error.walk((e) => { return e instanceof TurnkeyConsensusNeededError; })); } function isTurnkeyActivityError(error) { return (typeof error.walk === "function" && error.walk((e) => { return e instanceof TurnkeyActivityError; })); } exports.TurnkeyActivityError = TurnkeyActivityError; exports.TurnkeyConsensusNeededError = TurnkeyConsensusNeededError; exports.createAccount = createAccount; exports.createAccountWithAddress = createAccountWithAddress; exports.createApiKeyAccount = createApiKeyAccount; exports.isTurnkeyActivityConsensusNeededError = isTurnkeyActivityConsensusNeededError; exports.isTurnkeyActivityError = isTurnkeyActivityError; exports.serializeSignature = serializeSignature; exports.signMessage = signMessage; exports.signTransaction = signTransaction; exports.signTypedData = signTypedData; //# sourceMappingURL=index.js.map