UNPKG

@turnkey/solana

Version:

Turnkey Solana Signer for @solana/web3js

184 lines (181 loc) 8.87 kB
import { PublicKey, VersionedTransaction, Transaction } from '@solana/web3.js'; import { isHttpClient, assertActivityCompleted, assertNonNull } from '@turnkey/http'; class TurnkeySigner { constructor(input) { this.organizationId = input.organizationId; this.client = input.client; } /** * This function takes an array of Solana transactions and adds a signature with Turnkey to each of them * * @param txs array of Transaction | VersionedTransaction (native @solana/web3.js type) * @param fromAddress Solana address (base58 encoded) */ async signAllTransactions(txs, fromAddress, organizationId) { const fromKey = new PublicKey(fromAddress); let messages = txs.map((tx) => this.getMessageToSign(tx).toString("hex")); const signRawPayloadsResult = await this.signRawPayloads(messages, fromAddress, organizationId); const signatures = signRawPayloadsResult?.signatures?.map((sig) => `${sig?.r}${sig?.s}`); for (let i in txs) { txs[i]?.addSignature(fromKey, Buffer.from(signatures[i], "hex")); } return txs; } /** * This function takes a Solana transaction and adds a signature with Turnkey * * @param tx Transaction | VersionedTransaction object (native @solana/web3.js type) * @param fromAddress Solana address (base58 encoded) */ async addSignature(tx, fromAddress, organizationId) { const fromKey = new PublicKey(fromAddress); const messageToSign = this.getMessageToSign(tx); const signRawPayloadResult = await this.signRawPayload(messageToSign.toString("hex"), fromAddress, organizationId ?? this.organizationId); const signature = `${signRawPayloadResult?.r}${signRawPayloadResult?.s}`; tx.addSignature(fromKey, Buffer.from(signature, "hex")); } /** * This function takes a message and returns it after being signed with Turnkey * * @param message The message to sign (Uint8Array) * @param fromAddress Solana address (base58 encoded) */ async signMessage(message, fromAddress, organizationId) { const signRawPayloadResult = await this.signRawPayload(Buffer.from(message).toString("hex"), fromAddress, organizationId); return Buffer.from(`${signRawPayloadResult?.r}${signRawPayloadResult?.s}`, "hex"); } /** * This function takes a Solana transaction, adds a signature via Turnkey, * and returns a new transaction * * @param tx Transaction | VersionedTransaction object (native @solana/web3.js type) * @param fromAddress Solana address (base58 encoded) */ async signTransaction(tx, fromAddress, organizationId) { const payloadToSign = Buffer.from(tx.serialize({ requireAllSignatures: false, verifySignatures: false, })).toString("hex"); const signedTransaction = await this.signTransactionImpl(payloadToSign, fromAddress, organizationId); const decodedTransaction = Buffer.from(signedTransaction, "hex"); const recoveredTransaction = "version" in tx ? VersionedTransaction.deserialize(decodedTransaction) : Transaction.from(decodedTransaction); return recoveredTransaction; } async signTransactionImpl(unsignedTransaction, signWith, organizationId) { if (isHttpClient(this.client)) { const response = await this.client.signTransaction({ type: "ACTIVITY_TYPE_SIGN_TRANSACTION_V2", organizationId: organizationId ?? this.organizationId, timestampMs: String(Date.now()), parameters: { signWith, unsignedTransaction, type: "TRANSACTION_TYPE_SOLANA", }, }); const { activity } = response; assertActivityCompleted(activity); return assertNonNull(activity?.result?.signTransactionResult?.signedTransaction); } else { const { activity, signedTransaction } = await this.client.signTransaction({ ...(organizationId !== undefined && { organizationId }), signWith, unsignedTransaction, type: "TRANSACTION_TYPE_SOLANA", }); assertActivityCompleted(activity /* Type casting is ok here. The invalid types are both actually strings. TS is too strict here! */); return assertNonNull(signedTransaction); } } async signRawPayload(payload, signWith, organizationId) { if (isHttpClient(this.client)) { const response = await this.client.signRawPayload({ type: "ACTIVITY_TYPE_SIGN_RAW_PAYLOAD_V2", organizationId: organizationId ?? this.organizationId, timestampMs: String(Date.now()), parameters: { signWith, payload, encoding: "PAYLOAD_ENCODING_HEXADECIMAL", // Note: unlike ECDSA, EdDSA's API does not support signing raw digests (see RFC 8032). // Turnkey's signer requires an explicit value to be passed here to minimize ambiguity. hashFunction: "HASH_FUNCTION_NOT_APPLICABLE", }, }); const { activity } = response; assertActivityCompleted(activity); return assertNonNull(activity?.result?.signRawPayloadResult); } else { const { activity, r, s, v } = await this.client.signRawPayload({ ...(organizationId !== undefined && { organizationId }), signWith, payload, encoding: "PAYLOAD_ENCODING_HEXADECIMAL", // Note: unlike ECDSA, EdDSA's API does not support signing raw digests (see RFC 8032). // Turnkey's signer requires an explicit value to be passed here to minimize ambiguity. hashFunction: "HASH_FUNCTION_NOT_APPLICABLE", }); assertActivityCompleted(activity /* Type casting is ok here. The invalid types are both actually strings. TS is too strict here! */); return assertNonNull({ r, s, v, }); } } async signRawPayloads(payloads, signWith, organizationId) { if (isHttpClient(this.client)) { const response = await this.client.signRawPayloads({ type: "ACTIVITY_TYPE_SIGN_RAW_PAYLOADS", organizationId: organizationId ?? this.organizationId, timestampMs: String(Date.now()), parameters: { signWith, payloads, encoding: "PAYLOAD_ENCODING_HEXADECIMAL", // Note: unlike ECDSA, EdDSA's API does not support signing raw digests (see RFC 8032). // Turnkey's signer requires an explicit value to be passed here to minimize ambiguity. hashFunction: "HASH_FUNCTION_NOT_APPLICABLE", }, }); const { activity } = response; assertActivityCompleted(activity); return assertNonNull(activity?.result?.signRawPayloadsResult); } else { const { activity, signatures } = await this.client.signRawPayloads({ ...(organizationId !== undefined && { organizationId }), signWith, payloads, encoding: "PAYLOAD_ENCODING_HEXADECIMAL", // Note: unlike ECDSA, EdDSA's API does not support signing raw digests (see RFC 8032). // Turnkey's signer requires an explicit value to be passed here to minimize ambiguity. hashFunction: "HASH_FUNCTION_NOT_APPLICABLE", }); assertActivityCompleted(activity /* Type casting is ok here. The invalid types are both actually strings. TS is too strict here! */); return assertNonNull({ signatures: signatures, }); } } getMessageToSign(tx) { let messageToSign; // @ts-ignore // type narrowing (e.g. tx instanceof Transaction) does not seem to work here when the package gets compiled // and bundled. Instead, we will check for the existence of a property unique to the Transaction class // to determine whether the caller passed a Transaction or a VersionedTransaction if (typeof tx.serializeMessage === "function") { messageToSign = tx.serializeMessage(); } else { messageToSign = Buffer.from(tx.message.serialize()); } return messageToSign; } } export { TurnkeySigner }; //# sourceMappingURL=index.mjs.map