UNPKG

ox

Version:

Ethereum Standard Library

1,395 lines (1,256 loc) 36.4 kB
import { Address, Hex, P256, Secp256k1, Value, WebAuthnP256, WebCryptoP256, } from 'ox' import { getTransactionCount } from 'viem/actions' import { beforeEach, describe, expect, test } from 'vitest' import { chain, client, fundAddress } from '../../test/tempo/config.js' import { AuthorizationTempo, KeyAuthorization, SignatureEnvelope, } from './index.js' import * as Transaction from './Transaction.js' import * as TransactionReceipt from './TransactionReceipt.js' import * as TxEnvelopeTempo from './TxEnvelopeTempo.js' const chainId = chain.id test('behavior: default (secp256k1)', async () => { const privateKey = Secp256k1.randomPrivateKey() const address = Address.fromPublicKey(Secp256k1.getPublicKey({ privateKey })) await fundAddress(client, { address, }) const nonce = await getTransactionCount(client, { address, blockTag: 'pending', }) const transaction = TxEnvelopeTempo.from({ calls: [ { to: '0x0000000000000000000000000000000000000000', }, ], chainId, feeToken: '0x20c0000000000000000000000000000000000001', nonce: BigInt(nonce), gas: 100_000n, maxFeePerGas: Value.fromGwei('20'), maxPriorityFeePerGas: Value.fromGwei('10'), }) const signature = Secp256k1.sign({ payload: TxEnvelopeTempo.getSignPayload(transaction), privateKey, }) const serialized_signed = TxEnvelopeTempo.serialize(transaction, { signature: SignatureEnvelope.from(signature), }) const receipt = (await client .request({ method: 'eth_sendRawTransactionSync', params: [serialized_signed], }) .then((tx) => TransactionReceipt.fromRpc(tx as any)))! expect(receipt).toBeDefined() { const response = await client .request({ method: 'eth_getTransactionByHash', params: [receipt.transactionHash], }) .then((tx) => Transaction.fromRpc(tx as any)) if (!response) throw new Error() const { blockNumber, blockHash, chainId, hash, feeToken: _, from, keyAuthorization: __, nonce, maxFeePerGas, maxPriorityFeePerGas, signature, transactionIndex, ...rest } = response expect(blockNumber).toBeDefined() expect(blockHash).toBeDefined() expect(chainId).toBe(chainId) expect(hash).toBe(receipt.transactionHash) expect(from).toBe(address) expect(maxFeePerGas).toBeDefined() expect(maxPriorityFeePerGas).toBeDefined() expect(nonce).toBeDefined() expect(signature).toBeDefined() expect(transactionIndex).toBeDefined() expect(rest).toMatchInlineSnapshot(` { "accessList": [], "authorizationList": [], "calls": [ { "data": "0x", "to": "0x0000000000000000000000000000000000000000", "value": 0n, }, ], "data": undefined, "feePayerSignature": null, "gas": 100000n, "gasPrice": 20000000000n, "nonceKey": 0n, "type": "tempo", "validAfter": null, "validBefore": null, "value": 0n, } `) } const { blockNumber, blockHash, cumulativeGasUsed, feePayer, feeToken: _, from, gasUsed, logs, logsBloom, transactionHash, transactionIndex, ...rest } = receipt expect(blockNumber).toBeDefined() expect(blockHash).toBeDefined() expect(cumulativeGasUsed).toBeDefined() expect(feePayer).toBeDefined() expect(from).toBe(address) expect(gasUsed).toBeDefined() expect(logs).toBeDefined() expect(logsBloom).toBeDefined() expect(transactionHash).toBe(receipt.transactionHash) expect(transactionIndex).toBeDefined() expect(rest).toMatchInlineSnapshot(` { "blobGasPrice": undefined, "blobGasUsed": undefined, "contractAddress": null, "effectiveGasPrice": 20000000000n, "status": "success", "to": "0x0000000000000000000000000000000000000000", "type": "0x76", } `) }) test('behavior: authorizationList (secp256k1)', async () => { const privateKey = Secp256k1.randomPrivateKey() const address = Address.fromPublicKey(Secp256k1.getPublicKey({ privateKey })) await fundAddress(client, { address, }) const nonce = await getTransactionCount(client, { address, blockTag: 'pending', }) const authorization = AuthorizationTempo.from({ address: '0x0000000000000000000000000000000000000001', chainId: 0, nonce: BigInt(nonce + 1), }) const authorizationSigned = AuthorizationTempo.from(authorization, { signature: SignatureEnvelope.from( Secp256k1.sign({ payload: AuthorizationTempo.getSignPayload(authorization), privateKey, }), ), }) const transaction = TxEnvelopeTempo.from({ authorizationList: [authorizationSigned], calls: [ { to: '0x0000000000000000000000000000000000000000', }, ], chainId, feeToken: '0x20c0000000000000000000000000000000000001', nonce: BigInt(nonce), gas: 100_000n, maxFeePerGas: Value.fromGwei('20'), maxPriorityFeePerGas: Value.fromGwei('10'), }) const signature = Secp256k1.sign({ payload: TxEnvelopeTempo.getSignPayload(transaction), privateKey, }) const serialized_signed = TxEnvelopeTempo.serialize(transaction, { signature: SignatureEnvelope.from(signature), }) const receipt = (await client .request({ method: 'eth_sendRawTransactionSync', params: [serialized_signed], }) .then((tx) => TransactionReceipt.fromRpc(tx as any)))! expect(receipt).toBeDefined() const code = await client.request({ method: 'eth_getCode', params: [address, 'latest'], }) expect(Hex.slice(code, 3)).toBe('0x0000000000000000000000000000000000000001') }) test('behavior: default (p256)', async () => { const privateKey = P256.randomPrivateKey() const publicKey = P256.getPublicKey({ privateKey }) const address = Address.fromPublicKey(publicKey) await fundAddress(client, { address, }) const transaction = TxEnvelopeTempo.from({ calls: [ { to: '0x0000000000000000000000000000000000000000', }, ], chainId, feeToken: '0x20c0000000000000000000000000000000000001', gas: 100_000n, maxFeePerGas: Value.fromGwei('20'), maxPriorityFeePerGas: Value.fromGwei('10'), }) const signature = P256.sign({ payload: TxEnvelopeTempo.getSignPayload(transaction), privateKey, hash: false, }) const serialized_signed = TxEnvelopeTempo.serialize(transaction, { signature: SignatureEnvelope.from({ signature, publicKey, prehash: false, }), }) const receipt = (await client .request({ method: 'eth_sendRawTransactionSync', params: [serialized_signed], }) .then((tx) => TransactionReceipt.fromRpc(tx as any)))! expect(receipt).toBeDefined() { const response = await client .request({ method: 'eth_getTransactionByHash', params: [receipt.transactionHash], }) .then((tx) => Transaction.fromRpc(tx as any)) if (!response) throw new Error() const { blockNumber, blockHash, chainId, feeToken: _, from, keyAuthorization: __, hash, nonce, maxFeePerGas, maxPriorityFeePerGas, signature, transactionIndex, ...rest } = response expect(blockNumber).toBeDefined() expect(blockHash).toBeDefined() expect(chainId).toBe(chainId) expect(from).toBe(address) expect(hash).toBe(receipt.transactionHash) expect(nonce).toBeDefined() expect(maxFeePerGas).toBeDefined() expect(maxPriorityFeePerGas).toBeDefined() expect(signature).toBeDefined() expect(transactionIndex).toBeDefined() expect(rest).toMatchInlineSnapshot(` { "accessList": [], "authorizationList": [], "calls": [ { "data": "0x", "to": "0x0000000000000000000000000000000000000000", "value": 0n, }, ], "data": undefined, "feePayerSignature": null, "gas": 100000n, "gasPrice": 20000000000n, "nonceKey": 0n, "type": "tempo", "validAfter": null, "validBefore": null, "value": 0n, } `) } const { blockNumber, blockHash, cumulativeGasUsed, feePayer, feeToken: _, from, gasUsed, logs, logsBloom, transactionHash, transactionIndex, ...rest } = receipt expect(blockNumber).toBeDefined() expect(blockHash).toBeDefined() expect(cumulativeGasUsed).toBeDefined() expect(feePayer).toBeDefined() expect(from).toBe(address) expect(gasUsed).toBeDefined() expect(logs).toBeDefined() expect(logsBloom).toBeDefined() expect(transactionHash).toBe(receipt.transactionHash) expect(transactionIndex).toBeDefined() expect(rest).toMatchInlineSnapshot(` { "blobGasPrice": undefined, "blobGasUsed": undefined, "contractAddress": null, "effectiveGasPrice": 20000000000n, "status": "success", "to": "0x0000000000000000000000000000000000000000", "type": "0x76", } `) }) test('behavior: default (p256 - webcrypto)', async () => { const keyPair = await WebCryptoP256.createKeyPair() const address = Address.fromPublicKey(keyPair.publicKey) await fundAddress(client, { address, }) const transaction = TxEnvelopeTempo.from({ calls: [ { to: '0x0000000000000000000000000000000000000000', }, ], chainId, feeToken: '0x20c0000000000000000000000000000000000001', gas: 100_000n, maxFeePerGas: Value.fromGwei('20'), maxPriorityFeePerGas: Value.fromGwei('10'), }) const signature = await WebCryptoP256.sign({ payload: TxEnvelopeTempo.getSignPayload(transaction), privateKey: keyPair.privateKey, }) const serialized_signed = TxEnvelopeTempo.serialize(transaction, { signature: SignatureEnvelope.from({ signature, publicKey: keyPair.publicKey, prehash: true, type: 'p256', }), }) const receipt = (await client .request({ method: 'eth_sendRawTransactionSync', params: [serialized_signed], }) .then((tx) => TransactionReceipt.fromRpc(tx as any)))! expect(receipt).toBeDefined() { const response = await client .request({ method: 'eth_getTransactionByHash', params: [receipt.transactionHash], }) .then((tx) => Transaction.fromRpc(tx as any)) if (!response) throw new Error() const { blockNumber, blockHash, chainId, feeToken: _, from, keyAuthorization: __, hash, nonce, maxFeePerGas, maxPriorityFeePerGas, signature, transactionIndex, ...rest } = response as any expect(blockNumber).toBeDefined() expect(blockHash).toBeDefined() expect(chainId).toBeDefined() expect(from).toBeDefined() expect(hash).toBe(receipt.transactionHash) expect(nonce).toBeDefined() expect(maxFeePerGas).toBeDefined() expect(maxPriorityFeePerGas).toBeDefined() expect(signature).toBeDefined() expect(transactionIndex).toBeDefined() expect(rest).toMatchInlineSnapshot(` { "accessList": [], "authorizationList": [], "calls": [ { "data": "0x", "to": "0x0000000000000000000000000000000000000000", "value": 0n, }, ], "data": undefined, "feePayerSignature": null, "gas": 100000n, "gasPrice": 20000000000n, "nonceKey": 0n, "type": "tempo", "validAfter": null, "validBefore": null, "value": 0n, } `) } const { blockNumber, blockHash, cumulativeGasUsed, feePayer, feeToken: _, from, gasUsed, logs, logsBloom, transactionHash, transactionIndex, ...rest } = receipt expect(blockNumber).toBeDefined() expect(blockHash).toBeDefined() expect(cumulativeGasUsed).toBeDefined() expect(feePayer).toBeDefined() expect(from).toBeDefined() expect(gasUsed).toBeDefined() expect(logs).toBeDefined() expect(logsBloom).toBeDefined() expect(transactionHash).toBe(receipt.transactionHash) expect(transactionIndex).toBeDefined() expect(rest).toMatchInlineSnapshot(` { "blobGasPrice": undefined, "blobGasUsed": undefined, "contractAddress": null, "effectiveGasPrice": 20000000000n, "status": "success", "to": "0x0000000000000000000000000000000000000000", "type": "0x76", } `) }) test('behavior: default (webauthn)', async () => { const privateKey = P256.randomPrivateKey() const publicKey = P256.getPublicKey({ privateKey }) const address = Address.fromPublicKey(publicKey) await fundAddress(client, { address, }) const transaction = TxEnvelopeTempo.from({ calls: [ { to: '0x0000000000000000000000000000000000000000', }, ], chainId, feeToken: '0x20c0000000000000000000000000000000000001', gas: 100_000n, maxFeePerGas: Value.fromGwei('20'), maxPriorityFeePerGas: Value.fromGwei('10'), }) const { metadata, payload } = WebAuthnP256.getSignPayload({ challenge: TxEnvelopeTempo.getSignPayload(transaction), rpId: 'localhost', origin: 'http://localhost', }) const signature = P256.sign({ payload, privateKey, hash: true, }) const serialized_signed = TxEnvelopeTempo.serialize(transaction, { signature: SignatureEnvelope.from({ signature, publicKey, metadata, }), }) const receipt = (await client .request({ method: 'eth_sendRawTransactionSync', params: [serialized_signed], }) .then((tx) => TransactionReceipt.fromRpc(tx as any)))! expect(receipt).toBeDefined() { const response = await client .request({ method: 'eth_getTransactionByHash', params: [receipt.transactionHash], }) .then((tx) => Transaction.fromRpc(tx as any)) if (!response) throw new Error() const { blockNumber, blockHash, chainId, feeToken: _, from, keyAuthorization: __, hash, nonce, maxFeePerGas, maxPriorityFeePerGas, transactionIndex, signature, ...rest } = response expect(blockNumber).toBeDefined() expect(blockHash).toBeDefined() expect(chainId).toBe(chainId) expect(from).toBeDefined() expect(hash).toBe(receipt.transactionHash) expect(nonce).toBeDefined() expect(maxFeePerGas).toBeDefined() expect(maxPriorityFeePerGas).toBeDefined() expect(signature).toBeDefined() expect(transactionIndex).toBeDefined() expect(rest).toMatchInlineSnapshot(` { "accessList": [], "authorizationList": [], "calls": [ { "data": "0x", "to": "0x0000000000000000000000000000000000000000", "value": 0n, }, ], "data": undefined, "feePayerSignature": null, "gas": 100000n, "gasPrice": 20000000000n, "nonceKey": 0n, "type": "tempo", "validAfter": null, "validBefore": null, "value": 0n, } `) } const { blockNumber, blockHash, cumulativeGasUsed, feePayer, feeToken: _, from, gasUsed, logs, logsBloom, transactionHash, transactionIndex, ...rest } = receipt expect(blockNumber).toBeDefined() expect(blockHash).toBeDefined() expect(cumulativeGasUsed).toBeDefined() expect(feePayer).toBeDefined() expect(from).toBe(address) expect(gasUsed).toBeDefined() expect(logs).toBeDefined() expect(logsBloom).toBeDefined() expect(transactionHash).toBe(receipt.transactionHash) expect(transactionIndex).toBeDefined() expect(rest).toMatchInlineSnapshot(` { "blobGasPrice": undefined, "blobGasUsed": undefined, "contractAddress": null, "effectiveGasPrice": 20000000000n, "status": "success", "to": "0x0000000000000000000000000000000000000000", "type": "0x76", } `) }) test('behavior: feePayerSignature (user → feePayer)', async () => { const feePayerPrivateKey = Secp256k1.randomPrivateKey() const feePayerAddress = Address.fromPublicKey( Secp256k1.getPublicKey({ privateKey: feePayerPrivateKey }), ) const senderPrivateKey = Secp256k1.randomPrivateKey() const senderAddress = Address.fromPublicKey( Secp256k1.getPublicKey({ privateKey: senderPrivateKey }), ) await fundAddress(client, { address: feePayerAddress, }) const nonce = await client.request({ method: 'eth_getTransactionCount', params: [senderAddress, 'pending'], }) ////////////////////////////////////////////////////////////////// // Sender flow const transaction = TxEnvelopeTempo.from({ calls: [{ to: '0x0000000000000000000000000000000000000000', value: 0n }], chainId, feePayerSignature: null, nonce: BigInt(nonce), gas: 100000n, maxFeePerGas: Value.fromGwei('20'), maxPriorityFeePerGas: Value.fromGwei('10'), }) const signature = Secp256k1.sign({ payload: TxEnvelopeTempo.getSignPayload(transaction), // unfunded PK privateKey: senderPrivateKey, }) const transaction_signed = TxEnvelopeTempo.from(transaction, { signature: SignatureEnvelope.from(signature), }) ////////////////////////////////////////////////////////////////// // Fee payer flow const transaction_feePayer = TxEnvelopeTempo.from({ ...transaction_signed, feeToken: '0x20c0000000000000000000000000000000000001', }) const feePayerSignature = Secp256k1.sign({ payload: TxEnvelopeTempo.getFeePayerSignPayload(transaction_feePayer, { sender: senderAddress, }), privateKey: feePayerPrivateKey, }) const serialized_signed = TxEnvelopeTempo.serialize(transaction_feePayer, { feePayerSignature, }) const receipt = (await client .request({ method: 'eth_sendRawTransactionSync', params: [serialized_signed], }) .then((tx) => TransactionReceipt.fromRpc(tx as any)))! { const { blockNumber, blockHash, cumulativeGasUsed, feePayer, feeToken: _, from, gasUsed, logs, logsBloom, transactionHash, transactionIndex, ...rest } = receipt expect(blockNumber).toBeDefined() expect(blockHash).toBeDefined() expect(cumulativeGasUsed).toBeDefined() expect(feePayer).toBe(feePayerAddress) expect(from).toBe(senderAddress) expect(gasUsed).toBeDefined() expect(logs).toBeDefined() expect(logsBloom).toBeDefined() expect(transactionHash).toBe(receipt.transactionHash) expect(transactionIndex).toBeDefined() expect(rest).toMatchInlineSnapshot(` { "blobGasPrice": undefined, "blobGasUsed": undefined, "contractAddress": null, "effectiveGasPrice": 20000000000n, "status": "success", "to": "0x0000000000000000000000000000000000000000", "type": "0x76", } `) } const { feeToken, from } = (await client .request({ method: 'eth_getTransactionByHash', params: [receipt.transactionHash], }) .then((tx) => Transaction.fromRpc(tx as any))) as any expect(feeToken).toBe('0x20c0000000000000000000000000000000000001') expect(from).toBe(senderAddress) }) describe('behavior: keyAuthorization', () => { const privateKey = Secp256k1.randomPrivateKey() const address = Address.fromPublicKey(Secp256k1.getPublicKey({ privateKey })) const root = { address, privateKey, } as const beforeEach(async () => { await fundAddress(client, { address, }) }) test('behavior: secp256k1 access key', async () => { const privateKey = '0x06a952d58c24d287245276dd8b4272d82a921d27d90874a6c27a3bc19ff4bfde' const publicKey = Secp256k1.getPublicKey({ privateKey }) const address = Address.fromPublicKey(publicKey) const access = { address, publicKey, privateKey, } as const const keyAuth = KeyAuthorization.from({ address: access.address, type: 'secp256k1', }) const keyAuth_signature = Secp256k1.sign({ payload: KeyAuthorization.getSignPayload(keyAuth), privateKey: root.privateKey, }) const keyAuth_signed = KeyAuthorization.from(keyAuth, { signature: SignatureEnvelope.from(keyAuth_signature), }) const nonce = await getTransactionCount(client, { address: root.address, blockTag: 'pending', }) const transaction = TxEnvelopeTempo.from({ calls: [ { to: '0x0000000000000000000000000000000000000000', }, ], chainId, feeToken: '0x20c0000000000000000000000000000000000001', keyAuthorization: keyAuth_signed, nonce: BigInt(nonce), gas: 100_000n, maxFeePerGas: Value.fromGwei('20'), maxPriorityFeePerGas: Value.fromGwei('10'), }) const signature = Secp256k1.sign({ payload: TxEnvelopeTempo.getSignPayload(transaction), privateKey: access.privateKey, }) const serialized_signed = TxEnvelopeTempo.serialize(transaction, { signature: SignatureEnvelope.from({ userAddress: root.address, inner: SignatureEnvelope.from(signature), type: 'keychain', }), }) const receipt = (await client .request({ method: 'eth_sendRawTransactionSync', params: [serialized_signed], }) .then((tx) => TransactionReceipt.fromRpc(tx as any)))! { const response = await client .request({ method: 'eth_getTransactionByHash', params: [receipt.transactionHash], }) .then((tx) => Transaction.fromRpc(tx as any)) if (!response) throw new Error() const { blockNumber, blockHash, chainId: _, gasPrice, hash, from, keyAuthorization, maxFeePerGas, maxPriorityFeePerGas, nonce, signature, transactionIndex, ...rest } = response expect(blockNumber).toBeDefined() expect(blockHash).toBeDefined() expect(gasPrice).toBeDefined() expect(maxFeePerGas).toBeDefined() expect(maxPriorityFeePerGas).toBeDefined() expect(nonce).toBeDefined() expect(from).toBe(root.address) expect(hash).toBe(receipt.transactionHash) expect(keyAuthorization).toBeDefined() expect(signature).toBeDefined() expect(transactionIndex).toBeDefined() expect(rest).toMatchInlineSnapshot(` { "accessList": [], "authorizationList": [], "calls": [ { "data": "0x", "to": "0x0000000000000000000000000000000000000000", "value": 0n, }, ], "data": undefined, "feePayerSignature": null, "feeToken": "0x20c0000000000000000000000000000000000001", "gas": 100000n, "nonceKey": 0n, "type": "tempo", "validAfter": null, "validBefore": null, "value": 0n, } `) } const { blockNumber, blockHash, cumulativeGasUsed, feePayer, feeToken, from, gasUsed, logs, logsBloom, transactionHash, transactionIndex, ...rest } = receipt expect(blockNumber).toBeDefined() expect(blockHash).toBeDefined() expect(cumulativeGasUsed).toBeDefined() expect(feeToken).toBeDefined() expect(feePayer).toBeDefined() expect(gasUsed).toBeDefined() expect(from).toBeDefined() expect(logs).toBeDefined() expect(logsBloom).toBeDefined() expect(transactionHash).toBe(receipt.transactionHash) expect(transactionIndex).toBeDefined() expect(rest).toMatchInlineSnapshot(` { "blobGasPrice": undefined, "blobGasUsed": undefined, "contractAddress": null, "effectiveGasPrice": 20000000000n, "status": "success", "to": "0x0000000000000000000000000000000000000000", "type": "0x76", } `) // Test a subsequent tx signed by access key with no keyAuthorization { const nonce = await getTransactionCount(client, { address: root.address, blockTag: 'pending', }) const transaction = TxEnvelopeTempo.from({ calls: [ { to: '0x0000000000000000000000000000000000000000', }, ], chainId, feeToken: '0x20c0000000000000000000000000000000000001', nonce: BigInt(nonce), gas: 100_000n, maxFeePerGas: Value.fromGwei('20'), maxPriorityFeePerGas: Value.fromGwei('10'), }) const signature = Secp256k1.sign({ payload: TxEnvelopeTempo.getSignPayload(transaction), privateKey: access.privateKey, }) const serialized_signed = TxEnvelopeTempo.serialize(transaction, { signature: SignatureEnvelope.from({ userAddress: root.address, inner: SignatureEnvelope.from(signature), type: 'keychain', }), }) const receipt = await client.request({ method: 'eth_sendRawTransactionSync', params: [serialized_signed], }) expect(receipt).toBeDefined() } }) test('behavior: p256 access key', async () => { const privateKey = '0x06a952d58c24d287245276dd8b4272d82a921d27d90874a6c27a3bc19ff4bfde' const publicKey = P256.getPublicKey({ privateKey }) const address = Address.fromPublicKey(publicKey) const access = { address, publicKey, privateKey, } as const const keyAuth = KeyAuthorization.from({ address: access.address, type: 'p256', }) const keyAuth_signature = Secp256k1.sign({ payload: KeyAuthorization.getSignPayload(keyAuth), privateKey: root.privateKey, }) const keyAuth_signed = KeyAuthorization.from(keyAuth, { signature: SignatureEnvelope.from(keyAuth_signature), }) const nonce = await getTransactionCount(client, { address: root.address, blockTag: 'pending', }) const transaction = TxEnvelopeTempo.from({ calls: [ { to: '0x0000000000000000000000000000000000000000', }, ], chainId, feeToken: '0x20c0000000000000000000000000000000000001', keyAuthorization: keyAuth_signed, nonce: BigInt(nonce), gas: 100_000n, maxFeePerGas: Value.fromGwei('20'), maxPriorityFeePerGas: Value.fromGwei('10'), }) const signature = P256.sign({ payload: TxEnvelopeTempo.getSignPayload(transaction), privateKey: access.privateKey, }) const serialized_signed = TxEnvelopeTempo.serialize(transaction, { signature: SignatureEnvelope.from({ userAddress: root.address, inner: SignatureEnvelope.from({ prehash: false, publicKey: access.publicKey, signature, type: 'p256', }), type: 'keychain', }), }) const receipt = (await client .request({ method: 'eth_sendRawTransactionSync', params: [serialized_signed], }) .then((tx) => TransactionReceipt.fromRpc(tx as any)))! expect(receipt).toBeDefined() { const response = await client .request({ method: 'eth_getTransactionByHash', params: [receipt.transactionHash], }) .then((tx) => Transaction.fromRpc(tx as any)) if (!response) throw new Error() const { blockNumber, blockHash, chainId: _, gasPrice, hash, from, keyAuthorization, maxFeePerGas, maxPriorityFeePerGas, nonce, signature, transactionIndex, ...rest } = response expect(blockNumber).toBeDefined() expect(blockHash).toBeDefined() expect(gasPrice).toBeDefined() expect(hash).toBe(receipt.transactionHash) expect(from).toBe(root.address) expect(keyAuthorization).toBeDefined() expect(maxFeePerGas).toBeDefined() expect(maxPriorityFeePerGas).toBeDefined() expect(nonce).toBeDefined() expect(signature).toBeDefined() expect(transactionIndex).toBeDefined() expect(rest).toMatchInlineSnapshot(` { "accessList": [], "authorizationList": [], "calls": [ { "data": "0x", "to": "0x0000000000000000000000000000000000000000", "value": 0n, }, ], "data": undefined, "feePayerSignature": null, "feeToken": "0x20c0000000000000000000000000000000000001", "gas": 100000n, "nonceKey": 0n, "type": "tempo", "validAfter": null, "validBefore": null, "value": 0n, } `) } const { blockNumber, blockHash, cumulativeGasUsed, feePayer, feeToken, from, gasUsed, logs, logsBloom, transactionHash, transactionIndex, ...rest } = receipt expect(blockNumber).toBeDefined() expect(blockHash).toBeDefined() expect(cumulativeGasUsed).toBeDefined() expect(feePayer).toBeDefined() expect(feeToken).toBeDefined() expect(from).toBeDefined() expect(gasUsed).toBeDefined() expect(logs).toBeDefined() expect(logsBloom).toBeDefined() expect(transactionHash).toBe(receipt.transactionHash) expect(transactionIndex).toBeDefined() expect(rest).toMatchInlineSnapshot(` { "blobGasPrice": undefined, "blobGasUsed": undefined, "contractAddress": null, "effectiveGasPrice": 20000000000n, "status": "success", "to": "0x0000000000000000000000000000000000000000", "type": "0x76", } `) // Test a subsequent tx signed by access key with no keyAuthorization { const nonce = await getTransactionCount(client, { address: root.address, blockTag: 'pending', }) const transaction = TxEnvelopeTempo.from({ calls: [ { to: '0x0000000000000000000000000000000000000000', }, ], chainId, feeToken: '0x20c0000000000000000000000000000000000001', nonce: BigInt(nonce), gas: 100_000n, maxFeePerGas: Value.fromGwei('20'), maxPriorityFeePerGas: Value.fromGwei('10'), }) const signature = P256.sign({ payload: TxEnvelopeTempo.getSignPayload(transaction), privateKey: access.privateKey, }) const serialized_signed = TxEnvelopeTempo.serialize(transaction, { signature: SignatureEnvelope.from({ userAddress: root.address, inner: SignatureEnvelope.from({ prehash: false, publicKey: access.publicKey, signature, type: 'p256', }), type: 'keychain', }), }) const receipt = await client.request({ method: 'eth_sendRawTransactionSync', params: [serialized_signed], }) expect(receipt).toBeDefined() } }) test('behavior: webcrypto access key', async () => { const keyPair = await WebCryptoP256.createKeyPair() const address = Address.fromPublicKey(keyPair.publicKey) const access = { address, publicKey: keyPair.publicKey, privateKey: keyPair.privateKey, } as const const keyAuth = KeyAuthorization.from({ address: access.address, type: 'p256', }) const keyAuth_signature = Secp256k1.sign({ payload: KeyAuthorization.getSignPayload(keyAuth), privateKey: root.privateKey, }) const keyAuth_signed = KeyAuthorization.from(keyAuth, { signature: SignatureEnvelope.from(keyAuth_signature), }) const nonce = await getTransactionCount(client, { address: root.address, blockTag: 'pending', }) const transaction = TxEnvelopeTempo.from({ calls: [ { to: '0x0000000000000000000000000000000000000000', }, ], chainId, feeToken: '0x20c0000000000000000000000000000000000001', keyAuthorization: keyAuth_signed, nonce: BigInt(nonce), gas: 100_000n, maxFeePerGas: Value.fromGwei('20'), maxPriorityFeePerGas: Value.fromGwei('10'), }) const signature = await WebCryptoP256.sign({ payload: TxEnvelopeTempo.getSignPayload(transaction), privateKey: keyPair.privateKey, }) const serialized_signed = TxEnvelopeTempo.serialize(transaction, { signature: SignatureEnvelope.from({ userAddress: root.address, inner: SignatureEnvelope.from({ prehash: true, publicKey: access.publicKey, signature, type: 'p256', }), type: 'keychain', }), }) const receipt = await client.request({ method: 'eth_sendRawTransactionSync', params: [serialized_signed], }) expect(receipt).toBeDefined() { const response = await client .request({ method: 'eth_getTransactionByHash', params: [receipt.transactionHash], }) .then((tx) => Transaction.fromRpc(tx as any)) if (!response) throw new Error() const { blockNumber, blockHash, chainId: _, gasPrice, hash, from, keyAuthorization, maxFeePerGas, maxPriorityFeePerGas, nonce, signature, transactionIndex, ...rest } = response expect(blockNumber).toBeDefined() expect(blockHash).toBeDefined() expect(gasPrice).toBeDefined() expect(hash).toBe(receipt.transactionHash) expect(from).toBe(root.address) expect(keyAuthorization).toBeDefined() expect(maxFeePerGas).toBeDefined() expect(maxPriorityFeePerGas).toBeDefined() expect(nonce).toBeDefined() expect(signature).toBeDefined() expect(transactionIndex).toBeDefined() expect(rest).toMatchInlineSnapshot(` { "accessList": [], "authorizationList": [], "calls": [ { "data": "0x", "to": "0x0000000000000000000000000000000000000000", "value": 0n, }, ], "data": undefined, "feePayerSignature": null, "feeToken": "0x20c0000000000000000000000000000000000001", "gas": 100000n, "nonceKey": 0n, "type": "tempo", "validAfter": null, "validBefore": null, "value": 0n, } `) } // Test a subsequent tx signed by access key with no keyAuthorization { const nonce = await getTransactionCount(client, { address: root.address, blockTag: 'pending', }) const transaction = TxEnvelopeTempo.from({ calls: [ { to: '0x0000000000000000000000000000000000000000', }, ], chainId, feeToken: '0x20c0000000000000000000000000000000000001', nonce: BigInt(nonce), gas: 100_000n, maxFeePerGas: Value.fromGwei('20'), maxPriorityFeePerGas: Value.fromGwei('10'), }) const signature = await WebCryptoP256.sign({ payload: TxEnvelopeTempo.getSignPayload(transaction), privateKey: keyPair.privateKey, }) const serialized_signed = TxEnvelopeTempo.serialize(transaction, { signature: SignatureEnvelope.from({ userAddress: root.address, inner: SignatureEnvelope.from({ prehash: true, publicKey: access.publicKey, signature, type: 'p256', }), type: 'keychain', }), }) const receipt = await client.request({ method: 'eth_sendRawTransactionSync', params: [serialized_signed], }) expect(receipt).toBeDefined() } }) })