UNPKG

zksync-ethers

Version:

A Web3 library for interacting with the ZkSync Layer 2 scaling solution.

253 lines 9.74 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.populateTransactionMultisigECDSA = exports.populateTransactionECDSA = exports.signPayloadWithMultipleECDSA = exports.signPayloadWithECDSA = void 0; const ethers_1 = require("ethers"); const utils_1 = require("./utils"); /** * Signs the `payload` using an ECDSA private key. * * @param payload The payload that needs to be signed. * @param secret The ECDSA private key. * * @example Sign EIP712 transaction hash. * * import { EIP712Signer, types, utils } from "zksync-ethers"; * * const PRIVATE_KEY = "<PRIVATE_KEY>"; * * const tx: types.TransactionRequest = { * chainId: 270, * from: ADDRESS, * to: "<RECEIVER>", * value: 7_000_000_000, * }; * * const txHash = EIP712Signer.getSignedDigest(tx); * const result = await utils.signPayloadWithECDSA(txHash, PRIVATE_KEY); * * @example Sign message hash. * * import { utils } from "zksync-ethers"; * import { hashMessage } from "ethers"; * * const PRIVATE_KEY = "<PRIVATE_KEY>"; * * const message = 'Hello World!'; * const messageHash = hashMessage(message); * * const result = await utils.signPayloadWithECDSA(messageHash, PRIVATE_KEY); * * @example Sign typed data hash. * * import { utils } from "zksync-ethers"; * import { TypedDataEncoder } from "ethers"; * * const PRIVATE_KEY = "<PRIVATE_KEY>"; * * const typedDataHash = TypedDataEncoder.hash( * {name: 'Example', version: '1', chainId: 270}, * { * Person: [ * {name: 'name', type: 'string'}, * {name: 'age', type: 'uint8'}, * ], * }, * {name: 'John', age: 30} * ); * const result = await utils.signPayloadWithECDSA(typedDataHash, PRIVATE_KEY); */ const signPayloadWithECDSA = async (payload, secret) => { return new ethers_1.ethers.Wallet(secret).signingKey.sign(payload).serialized; }; exports.signPayloadWithECDSA = signPayloadWithECDSA; /** * Signs the `payload` using multiple ECDSA private keys. * The signature is generated by concatenating signatures created by signing with each key individually. * The length of the resulting signature should be `secrets.length * 65 + 2`. * * @param payload The payload that needs to be signed. * @param secret The list of the ECDSA private keys. * * @throws {Error} If the `secret` is not an array of at least two elements. * * @example Sign EIP712 transaction hash. * * import { EIP712Signer, types, utils } from "zksync-ethers"; * * const PRIVATE_KEY1 = "<PRIVATE_KEY1>"; * const PRIVATE_KEY2 = "<PRIVATE_KEY2>"; * * const tx: types.TransactionRequest = { * chainId: 270, * from: ADDRESS, * to: "<RECEIVER>", * value: 7_000_000_000, * }; * * const txHash = EIP712Signer.getSignedDigest(tx); * const result = await utils.signPayloadWithMultipleECDSA(typedDataHash, [PRIVATE_KEY1, PRIVATE_KEY2]); * * @example Sign message hash. * * import { utils } from "zksync-ethers"; * import { hashMessage } from "ethers"; * * const PRIVATE_KEY1 = "<PRIVATE_KEY1>"; * const PRIVATE_KEY2 = "<PRIVATE_KEY2>"; * * const message = 'Hello World!'; * const messageHash = hashMessage(message); * * const result = await utils.signPayloadWithMultipleECDSA(typedDataHash, [PRIVATE_KEY1, PRIVATE_KEY2]); * * @example Sign typed data hash. * * import { utils } from "zksync-ethers"; * import { TypedDataEncoder } from "ethers"; * * const PRIVATE_KEY1 = "<PRIVATE_KEY1>"; * const PRIVATE_KEY2 = "<PRIVATE_KEY2>"; * * const typedDataHash = TypedDataEncoder.hash( * {name: 'Example', version: '1', chainId: 270}, * { * Person: [ * {name: 'name', type: 'string'}, * {name: 'age', type: 'uint8'}, * ], * }, * {name: 'John', age: 30} * ); * const result = await utils.signPayloadWithMultipleECDSA(typedDataHash, [PRIVATE_KEY1, PRIVATE_KEY2]); */ const signPayloadWithMultipleECDSA = async (payload, secret) => { if (!Array.isArray(secret) || secret.length < 2) { throw new Error('Multiple keys are required for multisig signing!'); } const signatures = secret.map(key => // Note, that `signMessage` wouldn't work here, since we don't want // the signed hash to be prefixed with `\x19Ethereum Signed Message:\n` ethers_1.ethers.Signature.from(new ethers_1.ethers.Wallet(key).signingKey.sign(payload)) .serialized); return ethers_1.ethers.concat(signatures); }; exports.signPayloadWithMultipleECDSA = signPayloadWithMultipleECDSA; /** * Populates missing properties meant for signing using an ECDSA private key: * * - Populates `from` using the address derived from the ECDSA private key. * - Populates `nonce` via `provider.getTransactionCount(tx.from, "pending")`. * - Populates `gasLimit` via `provider.estimateGas(tx)`. If `tx.from` is not EOA, the estimation is done with address * derived from the ECDSA private key. * - Populates `chainId` via `provider.getNetwork()`. * - Populates `type` with `utils.EIP712_TX_TYPE`. * - Populates `value` by converting to `bigint` if set, otherwise to `0n`. * - Populates `data` with `0x`. * - Populates `customData` with `{factoryDeps=[], gasPerPubdata=utils.DEFAULT_GAS_PER_PUBDATA_LIMIT}`. * * @param tx The transaction that needs to be populated. * @param [secret] The ECDSA private key used for populating the transaction. * @param [provider] The provider is used to fetch data from the network if it is required for signing. * * @throws {Error} Requires `provider` to be set. * * @example * * import { Provider, types, utils } from "zksync-ethers"; * * const PRIVATE_KEY = "<PRIVATE_KEY>"; * * const provider = Provider.getDefaultProvider(types.Network.Sepolia); * * const populatedTx = await utils.populateTransactionECDSA( * { * chainId: 270, * to: "<RECEIVER>", * value: 7_000_000_000, * }, * PRIVATE_KEY, * provider * ); */ const populateTransactionECDSA = async (tx, secret, provider) => { var _a, _b; if (!provider) { throw new Error('Provider is required but is not provided!'); } const populatedTx = { ...tx }; populatedTx.type = utils_1.EIP712_TX_TYPE; populatedTx.chainId ?? (populatedTx.chainId = (await provider.getNetwork()).chainId); populatedTx.value = populatedTx.value ? BigInt(populatedTx.value) : 0n; populatedTx.data ?? (populatedTx.data = '0x'); populatedTx.customData = tx.customData ?? {}; (_a = populatedTx.customData).factoryDeps ?? (_a.factoryDeps = []); populatedTx.from ?? (populatedTx.from = new ethers_1.ethers.Wallet(secret).address); if (populatedTx.gasPrice && (populatedTx.maxFeePerGas || populatedTx.maxPriorityFeePerGas)) { throw new Error('Provide combination of maxFeePerGas and maxPriorityFeePerGas or provide gasPrice. Not both!'); } if (!populatedTx.gasLimit || (!populatedTx.gasPrice && (!populatedTx.maxFeePerGas || populatedTx.maxPriorityFeePerGas === null || populatedTx.maxPriorityFeePerGas === undefined))) { let fromToUse = populatedTx.from; const isContractAccount = ethers_1.ethers.getBytes(await provider.getCode(populatedTx.from)).length !== 0; if (isContractAccount) { // Gas estimation does not work when initiator is contract account (works only with EOA). // In order to estimation gas, the transaction's from value is replaced with signer's address. fromToUse = new ethers_1.ethers.Wallet(secret).address; } const fee = await provider.estimateFee({ ...populatedTx, from: fromToUse, }); populatedTx.gasLimit ?? (populatedTx.gasLimit = fee.gasLimit); (_b = populatedTx.customData).gasPerPubdata ?? (_b.gasPerPubdata = fee.gasPerPubdataLimit); if (!populatedTx.gasPrice) { populatedTx.maxFeePerGas ?? (populatedTx.maxFeePerGas = fee.maxFeePerGas); populatedTx.maxPriorityFeePerGas ?? (populatedTx.maxPriorityFeePerGas = fee.maxPriorityFeePerGas); } } populatedTx.nonce ?? (populatedTx.nonce = await provider.getTransactionCount(populatedTx.from, 'pending')); return populatedTx; }; exports.populateTransactionECDSA = populateTransactionECDSA; /** * Populates missing properties meant for signing using multiple ECDSA private keys. * It uses {@link populateTransactionECDSA}, where the address of the first ECDSA key is set as the `secret` argument. * * @param tx The transaction that needs to be populated. * @param [secret] The list of the ECDSA private keys used for populating the transaction. * @param [provider] The provider is used to fetch data from the network if it is required for signing. * * @throws {Error} The `secret` must be an array of at least two elements. * * @example * * import { Provider, types, utils } from "zksync-ethers"; * * const PRIVATE_KEY1 = "<PRIVATE_KEY1>"; * const PRIVATE_KEY2 = "<PRIVATE_KEY2>"; * * const provider = Provider.getDefaultProvider(types.Network.Sepolia); * * const populatedTx = await utils.populateTransactionMultisigECDSA( * { * chainId: 270, * to: "<RECEIVER>", * value: 7_000_000_000, * }, * [PRIVATE_KEY1, PRIVATE_KEY2], * provider * ); */ const populateTransactionMultisigECDSA = async (tx, secret, provider) => { if (!Array.isArray(secret) || secret.length < 2) { throw new Error('Multiple keys are required to build the transaction!'); } // estimates gas accepts only one address, so the first signer is chosen. return (0, exports.populateTransactionECDSA)(tx, secret[0], provider); }; exports.populateTransactionMultisigECDSA = populateTransactionMultisigECDSA; //# sourceMappingURL=smart-account-utils.js.map