UNPKG

viem

Version:

TypeScript Interface for Ethereum

525 lines 18.5 kB
import * as Address from 'ox/Address'; import * as Hex from 'ox/Hex'; import * as P256 from 'ox/P256'; import * as PublicKey from 'ox/PublicKey'; import * as Secp256k1 from 'ox/Secp256k1'; import * as Signature from 'ox/Signature'; import { Channel, KeyAuthorization, MultisigConfig, SignatureEnvelope, } from 'ox/tempo'; import * as WebAuthnP256 from 'ox/WebAuthnP256'; import * as WebCryptoP256 from 'ox/WebCryptoP256'; import { parseAccount } from '../accounts/utils/parseAccount.js'; import { hashAuthorization } from '../utils/authorization/hashAuthorization.js'; import { keccak256 } from '../utils/hash/keccak256.js'; import { hashMessage } from '../utils/signature/hashMessage.js'; import { hashTypedData } from '../utils/signature/hashTypedData.js'; import * as Transaction from './Transaction.js'; /** Instantiates an Account. */ export function from(parameters) { const { access } = parameters; if (access) return fromAccessKey(parameters); return fromRoot(parameters); } /** * Instantiates an Account from a headless WebAuthn credential (P256 private key). * * @example * ```ts * import { Account } from 'viem/tempo' * * const account = Account.fromHeadlessWebAuthn('0x...') * ``` * * @param privateKey P256 private key. * @returns Account. */ export function fromHeadlessWebAuthn(privateKey, options) { const { access, keyAuthorizationManager, rpId, origin, internal_version } = options; const publicKey = P256.getPublicKey({ privateKey }); return from({ ...(access ? { access, keyAuthorizationManager } : {}), internal_version, keyType: 'webAuthn', publicKey, async sign({ hash }) { const { metadata, payload } = WebAuthnP256.getSignPayload({ ...options, challenge: hash, rpId, origin, }); const signature = P256.sign({ payload, privateKey, hash: true, }); return SignatureEnvelope.serialize({ metadata, signature, publicKey, type: 'webAuthn', }); }, }); } /** * Instantiates an Account from a P256 private key. * * @example * ```ts * import { Account } from 'viem/tempo' * * const account = Account.fromP256('0x...') * ``` * * @param privateKey P256 private key. * @returns Account. */ export function fromP256(privateKey, options = {}) { const { access, keyAuthorizationManager, internal_version } = options; const publicKey = P256.getPublicKey({ privateKey }); return from({ ...(access ? { access, keyAuthorizationManager } : {}), internal_version, keyType: 'p256', publicKey, async sign({ hash }) { const signature = P256.sign({ payload: hash, privateKey }); return SignatureEnvelope.serialize({ signature, publicKey, type: 'p256', }); }, }); } /** * Instantiates an Account from a Secp256k1 private key. * * @example * ```ts * import { Account } from 'viem/tempo' * * const account = Account.fromSecp256k1('0x...') * ``` * * @param privateKey Secp256k1 private key. * @returns Account. */ export function fromSecp256k1(privateKey, options = {}) { const { access, keyAuthorizationManager, internal_version } = options; const publicKey = Secp256k1.getPublicKey({ privateKey }); return from({ ...(access ? { access, keyAuthorizationManager } : {}), internal_version, keyType: 'secp256k1', publicKey, async sign(parameters) { const { hash } = parameters; const signature = Secp256k1.sign({ payload: hash, privateKey }); return Signature.toHex(signature); }, }); } /** * Instantiates a synthetic Account for a native multisig (TIP-1061) config. * * The returned account does not hold a key. It is used purely to drive the * standard `sendTransaction` flow: it derives the multisig address from the * config and passes the prepared request (carrying the collected owner * `signatures`) through to the chain serializer, which combines the approvals * into the multisig signature envelope. * * Owner approvals are produced separately by signing with `multisig` request * metadata (see `signTransaction`), and provided here via `signatures`. * * Accepts a raw config and normalizes it internally (via `MultisigConfig.from`), * so callers don't need to call `MultisigConfig.from` themselves. * * @example * ```ts * import { Account } from 'viem/tempo' * * const account = Account.fromMultisig({ * threshold: 2, * owners: [ * { owner: owner_1.address, weight: 1 }, * { owner: owner_2.address, weight: 1 }, * ], * }) * * // Pass the account to `prepareTransactionRequest` — the multisig config is * // inferred from it, so no `multisig` field is needed. * const request = await client.prepareTransactionRequest({ account, ...rest }) * * // The prepared request carries the multisig account as sender, so it doesn't * // need to be re-passed to `sendTransaction`. * const transaction = await client.sendTransaction({ * ...request, * signatures: [signature_1, signature_2], * }) * ``` * * @param config Multisig config (raw or from `MultisigConfig.from`). * @returns Multisig account. */ export function fromMultisig(config) { const normalized = MultisigConfig.from(config); const address = Address.checksum(MultisigConfig.getAddress(normalized)); return { address, config: normalized, publicKey: '0x', source: 'multisig', type: 'local', async sign() { throw new Error('`sign` is not supported for multisig accounts.'); }, async signMessage() { throw new Error('`signMessage` is not supported for multisig accounts.'); }, async signTransaction(transaction, options) { const { serializer = Transaction.serialize } = options ?? {}; return (await serializer(transaction)); }, async signTypedData() { throw new Error('`signTypedData` is not supported for multisig accounts.'); }, }; } /** * Instantiates an Account from a WebAuthn credential. * * @example * * ### Create Passkey + Instantiate Account * * Create a credential with `WebAuthnP256.createCredential` and then instantiate * a Viem Account with `Account.fromWebAuthnP256`. * * It is highly recommended to store the credential's public key in an external store * for future use (ie. for future calls to `WebAuthnP256.getCredential`). * * ```ts * import { Account, WebAuthnP256 } from 'viem/tempo' * import { publicKeyStore } from './store' * * // 1. Create credential * const credential = await WebAuthnP256.createCredential({ name: 'Example' }) * * // 2. Instantiate account * const account = Account.fromWebAuthnP256(credential) * * // 3. Store public key * await publicKeyStore.set(credential.id, credential.publicKey) * * ``` * * @example * * ### Get Credential + Instantiate Account * * Gets a credential from `WebAuthnP256.getCredential` and then instantiates * an account with `Account.fromWebAuthnP256`. * * The `getPublicKey` function is required to fetch the public key paired with the credential * from an external store. The public key is required to derive the account's address. * * ```ts * import { Account, WebAuthnP256 } from 'viem/tempo' * import { publicKeyStore } from './store' * * // 1. Get credential * const credential = await WebAuthnP256.getCredential({ * async getPublicKey(credential) { * // 2. Get public key from external store. * return await publicKeyStore.get(credential.id) * } * }) * * // 3. Instantiate account * const account = Account.fromWebAuthnP256(credential) * ``` * * @param credential WebAuthnP256 credential. * @returns Account. */ export function fromWebAuthnP256(credential, options = {}) { const { id } = credential; const publicKey = PublicKey.fromHex(credential.publicKey); return from({ keyType: 'webAuthn', publicKey, async sign({ hash }) { const { metadata, signature } = await WebAuthnP256.sign({ ...options, challenge: hash, credentialId: id, }); return SignatureEnvelope.serialize({ publicKey, metadata, signature, type: 'webAuthn', }); }, }); } /** * Instantiates an Account from a P256 private key. * * @example * ```ts * import { Account } from 'viem/tempo' * import { WebCryptoP256 } from 'ox' * * const keyPair = await WebCryptoP256.createKeyPair() * * const account = Account.fromWebCryptoP256(keyPair) * ``` * * @param keyPair WebCryptoP256 key pair. * @returns Account. */ export function fromWebCryptoP256(keyPair, options = {}) { const { access, keyAuthorizationManager, internal_version } = options; const { publicKey, privateKey } = keyPair; return from({ ...(access ? { access, keyAuthorizationManager } : {}), internal_version, keyType: 'p256', publicKey, async sign({ hash }) { const signature = await WebCryptoP256.sign({ payload: hash, privateKey }); return SignatureEnvelope.serialize({ signature, prehash: true, publicKey, type: 'p256', }); }, }); } export async function signVoucher(account, parameters) { const hash = getVoucherSignPayload(parameters); if (isAccessKeyAccount(account)) return account.sign({ hash, raw: true }); return account.sign({ hash }); } function getVoucherSignPayload(parameters) { const { chainId, channel, cumulativeAmount } = parameters; const channelId = typeof channel === 'string' ? channel : Channel.computeId(channel, { chainId, }); return Channel.getVoucherSignPayload({ chainId, channelId, cumulativeAmount, }); } function isAccessKeyAccount(account) { return account.source === 'accessKey' && 'accessKeyAddress' in account; } export async function signKeyAuthorization(account, parameters) { const { chainId, key, expiry, limits, scopes, witness, admin } = parameters; const { accessKeyAddress, keyType: type } = resolveAccessKey(key); // When the signer is an admin access key, the authorization must be // signed directly by that key and bound to the parent account it acts // on behalf of, so the signed payload cannot be replayed against another // account. [TIP-1049] const isAccessKey = isAccessKeyAccount(account); const boundFields = isAccessKey ? { account: account.address } : {}; // Admin key authorizations are unrestricted and must not carry expiry, // limits, or call scopes (the protocol rejects them). [TIP-1049] const restrictions = admin ? {} : { expiry, limits, scopes }; const hash = KeyAuthorization.getSignPayload({ address: accessKeyAddress, chainId, type, witness, ...(admin ? { isAdmin: true } : {}), ...boundFields, ...restrictions, }); const signature = isAccessKey ? await account.sign({ hash, raw: true }) : await account.sign({ hash }); return KeyAuthorization.from({ address: accessKeyAddress, chainId, signature: SignatureEnvelope.from(signature), type, ...(witness ? { witness } : {}), ...(admin ? { isAdmin: true } : {}), ...boundFields, ...restrictions, }); } /** @internal */ // biome-ignore lint/correctness/noUnusedVariables: _ function fromBase(parameters) { const { keyType = 'secp256k1', parentAddress, source = 'privateKey', internal_version = 'v2', } = parameters; const address = parentAddress ?? Address.fromPublicKey(parameters.publicKey); const publicKey = PublicKey.toHex(parameters.publicKey, { includePrefix: false, }); async function sign({ hash, raw }) { if (raw) return await parameters.sign({ hash }); const innerHash = parentAddress && internal_version === 'v2' ? keccak256(Hex.concat('0x04', hash, parentAddress)) : hash; const signature = await parameters.sign({ hash: innerHash }); if (parentAddress) return SignatureEnvelope.serialize(SignatureEnvelope.from({ userAddress: parentAddress, inner: SignatureEnvelope.from(signature), type: 'keychain', version: internal_version, })); return signature; } return { address: Address.checksum(address), keyType, sign, async signAuthorization(parameters) { const { chainId, nonce } = parameters; const address = parameters.contractAddress ?? parameters.address; const signature = await sign({ hash: hashAuthorization({ address, chainId, nonce }), }); const envelope = SignatureEnvelope.from(signature); if (envelope.type !== 'secp256k1') throw new Error('Unsupported signature type. Expected `secp256k1` but got `' + envelope.type + '`.'); const { r, s, yParity } = envelope.signature; return { address, chainId, nonce, r: Hex.fromNumber(r, { size: 32 }), s: Hex.fromNumber(s, { size: 32 }), yParity, }; }, async signMessage(parameters) { const { message } = parameters; return await sign({ hash: hashMessage(message) }); }, async signTransaction(transaction, options) { const { serializer = Transaction.serialize } = options ?? {}; const presign = (() => { if ('feePayerSignature' in transaction && transaction.feePayerSignature) return { ...transaction, feePayerSignature: null }; return transaction; })(); const payload = keccak256(await serializer(presign)); // Native multisig (TIP-1061): return this owner's approval — a serialized // primitive signature over the multisig owner approval digest — instead of // a full serialized transaction. Approvals are combined later in // `sendTransaction({ signatures })`. const multisig = transaction.multisig; if (multisig) { const digest = MultisigConfig.getSignPayload({ payload, genesisConfig: multisig, }); return await sign({ hash: digest, raw: true }); } const signature = await sign({ hash: payload }); const envelope = SignatureEnvelope.from(signature); return await serializer(transaction, envelope); }, async signTypedData(typedData) { return await sign({ hash: hashTypedData(typedData) }); }, async signVoucher(parameters) { const hash = getVoucherSignPayload(parameters); if (parentAddress) return await sign({ hash, raw: true }); return await sign({ hash }); }, publicKey, source, type: 'local', }; } /** @internal */ // biome-ignore lint/correctness/noUnusedVariables: _ function fromRoot(parameters) { const account = fromBase(parameters); return { ...account, source: 'root', async signKeyAuthorization(key, parameters) { const { chainId, expiry, limits, scopes, witness, admin } = parameters; const { accessKeyAddress, keyType: type } = resolveAccessKey(key); // Admin key authorizations are unrestricted and must not carry expiry, // limits, or call scopes (the protocol rejects them). [TIP-1049] const restrictions = admin ? {} : { expiry, limits, scopes }; const signature = await account.sign({ hash: KeyAuthorization.getSignPayload({ address: accessKeyAddress, chainId, type, witness, ...(admin ? { isAdmin: true } : {}), ...restrictions, }), }); const keyAuthorization = KeyAuthorization.from({ address: accessKeyAddress, chainId, signature: SignatureEnvelope.from(signature), type, ...(witness ? { witness } : {}), ...(admin ? { isAdmin: true } : {}), ...restrictions, }); return keyAuthorization; }, }; } // biome-ignore lint/correctness/noUnusedVariables: _ function fromAccessKey(parameters) { const { access, keyAuthorizationManager } = parameters; const { address: parentAddress } = parseAccount(access); const account = fromBase({ ...parameters, parentAddress }); return { ...account, accessKeyAddress: Address.fromPublicKey(parameters.publicKey), keyAuthorizationManager, source: 'accessKey', }; } /** @internal */ export function resolveAccessKey(accessKey) { if ('accessKeyAddress' in accessKey) return { accessKeyAddress: accessKey.accessKeyAddress, keyType: accessKey.keyType, }; if ('publicKey' in accessKey && accessKey.publicKey) return { accessKeyAddress: Address.fromPublicKey(PublicKey.fromHex(accessKey.publicKey)), keyType: accessKey.type, }; return { accessKeyAddress: accessKey.address, keyType: accessKey.type, }; } // Export types required for inference. // biome-ignore lint/performance/noBarrelFile: _ export { /** @deprecated */ KeyAuthorization as z_KeyAuthorization, /** @deprecated */ SignatureEnvelope as z_SignatureEnvelope, /** @deprecated */ TxEnvelopeTempo as z_TxEnvelopeTempo, } from 'ox/tempo'; //# sourceMappingURL=Account.js.map