@turnkey/viem
Version:
Turnkey Helpers to work with Viem
287 lines (283 loc) • 11 kB
JavaScript
;
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