import {
  Authorization,
  Hex,
  Rlp,
  Secp256k1,
  TxEnvelopeEip1559,
  TxEnvelopeEip7702,
  Value,
} from 'ox'
import { assertType, describe, expect, test } from 'vp/test'
import { wagmiContractConfig } from '../../../test/constants/abis.js'
import { accounts } from '../../../test/constants/accounts.js'
import { anvilMainnet } from '../../../test/prool.js'

describe('assert', () => {
  test('invalid chainId', () => {
    expect(() =>
      TxEnvelopeEip7702.assert({
        authorizationList: [
          {
            address: '0x0000000000000000000000000000000000000000',
            chainId: -1,
            nonce: 0n,
            r: '0x0000000000000000000000000000000000000000000000000000000000000000',
            s: '0x0000000000000000000000000000000000000000000000000000000000000000',
            yParity: 0,
          },
        ],
        chainId: 1,
      }),
    ).toThrowErrorMatchingInlineSnapshot(
      `[TransactionEnvelope.InvalidChainIdError: Chain ID "-1" is invalid.]`,
    )
  })

  test('invalid address', () => {
    expect(() =>
      TxEnvelopeEip7702.assert({
        authorizationList: [
          {
            address: '0x000000000000000000000000000000000000000z',
            chainId: 0,
            nonce: 0n,
            r: '0x0000000000000000000000000000000000000000000000000000000000000000',
            s: '0x0000000000000000000000000000000000000000000000000000000000000000',
            yParity: 0,
          },
        ],
        chainId: 1,
      }),
    ).toThrowErrorMatchingInlineSnapshot(
      `
    [Address.InvalidAddressError: Address "0x000000000000000000000000000000000000000z" is invalid.

    Details: Address is not a 20 byte (40 hexadecimal character) value.]
  `,
    )
  })

  test('fee cap too high', () => {
    expect(() =>
      TxEnvelopeEip7702.assert({
        authorizationList: [
          {
            address: '0x0000000000000000000000000000000000000000',
            chainId: 1,
            nonce: 0n,
            r: '0x0000000000000000000000000000000000000000000000000000000000000000',
            s: '0x0000000000000000000000000000000000000000000000000000000000000000',
            yParity: 0,
          },
        ],
        maxFeePerGas: 2n ** 256n - 1n + 1n,
        chainId: 1,
      }),
    ).toThrowErrorMatchingInlineSnapshot(
      '[TransactionEnvelope.FeeCapTooHighError: The fee cap (`maxFeePerGas`/`maxPriorityFeePerGas` = 115792089237316195423570985008687907853269984665640564039457584007913.129639936 gwei) cannot be higher than the maximum allowed value (2^256-1).]',
    )
  })
})

describe('deserialize', () => {
  const authorization_1 = Authorization.from({
    address: wagmiContractConfig.address,
    chainId: 1,
    nonce: 785n,
  })
  const signature_1 = Secp256k1.sign({
    payload: Authorization.getSignPayload(authorization_1),
    privateKey: accounts[0].privateKey,
  })

  const authorization_2 = Authorization.from({
    address: wagmiContractConfig.address,
    chainId: 10,
    nonce: 786n,
  })
  const signature_2 = Secp256k1.sign({
    payload: Authorization.getSignPayload(authorization_2),
    privateKey: accounts[0].privateKey,
  })

  const transaction = TxEnvelopeEip7702.from({
    authorizationList: [
      Authorization.from(authorization_1, { signature: signature_1 }),
      Authorization.from(authorization_2, { signature: signature_2 }),
    ],
    chainId: 1,
    nonce: 785n,
    to: accounts[1].address,
    value: Value.fromEther('1'),
    maxFeePerGas: Value.fromGwei('2'),
    maxPriorityFeePerGas: Value.fromGwei('2'),
  })

  test('default', () => {
    const serialized = TxEnvelopeEip7702.serialize(transaction)
    const deserialized = TxEnvelopeEip7702.deserialize(serialized)
    assertType<TxEnvelopeEip7702.TxEnvelopeEip7702>(deserialized)
    expect(deserialized).toEqual(transaction)
  })

  test('minimal', () => {
    const transaction = TxEnvelopeEip7702.from({
      authorizationList: [],
      chainId: 1,
      nonce: 0n,
    })
    const serialized = TxEnvelopeEip7702.serialize(transaction)
    expect(TxEnvelopeEip7702.deserialize(serialized)).toEqual(transaction)
  })

  test('gas', () => {
    const transaction_gas = TxEnvelopeEip7702.from({
      ...transaction,
      gas: 21001n,
    })
    const serialized = TxEnvelopeEip7702.serialize(transaction_gas)
    expect(TxEnvelopeEip7702.deserialize(serialized)).toEqual(transaction_gas)
  })

  test('accessList', () => {
    const transaction_accessList = TxEnvelopeEip7702.from({
      ...transaction,
      accessList: [
        {
          address: '0x0000000000000000000000000000000000000000',
          storageKeys: [
            '0x0000000000000000000000000000000000000000000000000000000000000001',
            '0x60fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fe',
          ],
        },
      ],
    })
    const serialized = TxEnvelopeEip7702.serialize(transaction_accessList)
    expect(TxEnvelopeEip7702.deserialize(serialized)).toEqual(
      transaction_accessList,
    )
  })

  test('data', () => {
    const transaction_data = TxEnvelopeEip7702.from({
      ...transaction,
      data: '0x1234',
    })
    const serialized = TxEnvelopeEip7702.serialize(transaction_data)
    expect(TxEnvelopeEip7702.deserialize(serialized)).toEqual(transaction_data)
  })

  test('signature', () => {
    const signature = Secp256k1.sign({
      payload: TxEnvelopeEip7702.getSignPayload(transaction),
      privateKey: accounts[0].privateKey,
    })
    const serialized = TxEnvelopeEip7702.serialize(transaction, {
      signature,
    })
    expect(TxEnvelopeEip7702.deserialize(serialized)).toEqual({
      ...transaction,
      ...signature,
    })
  })

  describe('raw', () => {
    test('default', () => {
      const serialized = `0x04${Rlp.fromHex([
        Hex.fromNumber(1), // chainId
        Hex.fromNumber(0), // nonce
        Hex.fromNumber(1), // maxPriorityFeePerGas
        Hex.fromNumber(1), // maxFeePerGas
        Hex.fromNumber(1), // gas
        '0x0000000000000000000000000000000000000000', // to
        Hex.fromNumber(0), // value
        '0x', // data
        '0x', // accessList
        '0x', // authorizationList
      ]).slice(2)}` as const
      expect(TxEnvelopeEip7702.deserialize(serialized)).toMatchInlineSnapshot(`
        {
          "chainId": 1,
          "gas": 1n,
          "maxFeePerGas": 1n,
          "maxPriorityFeePerGas": 1n,
          "nonce": 0n,
          "to": "0x0000000000000000000000000000000000000000",
          "type": "eip7702",
          "value": 0n,
        }
      `)
    })

    test('empty sig', () => {
      const serialized = `0x04${Rlp.fromHex([
        Hex.fromNumber(1), // chainId
        Hex.fromNumber(0), // nonce
        Hex.fromNumber(1), // maxPriorityFeePerGas
        Hex.fromNumber(1), // maxFeePerGas
        Hex.fromNumber(1), // gas
        '0x0000000000000000000000000000000000000000', // to
        Hex.fromNumber(0), // value
        '0x', // data
        '0x', // accessList
        '0x', // authorizationList
        '0x', // r
        '0x', // v
        '0x', // s
      ]).slice(2)}` as const
      expect(TxEnvelopeEip7702.deserialize(serialized)).toMatchInlineSnapshot(`
        {
          "chainId": 1,
          "gas": 1n,
          "maxFeePerGas": 1n,
          "maxPriorityFeePerGas": 1n,
          "nonce": 0n,
          "r": "0x0000000000000000000000000000000000000000000000000000000000000000",
          "s": "0x0000000000000000000000000000000000000000000000000000000000000000",
          "to": "0x0000000000000000000000000000000000000000",
          "type": "eip7702",
          "value": 0n,
          "yParity": 0,
        }
      `)
    })

    test('low sig coords', () => {
      const serialized = `0x04${Rlp.fromHex([
        Hex.fromNumber(1), // chainId
        Hex.fromNumber(0), // nonce
        Hex.fromNumber(1), // maxPriorityFeePerGas
        Hex.fromNumber(1), // maxFeePerGas
        Hex.fromNumber(1), // gas
        '0x0000000000000000000000000000000000000000', // to
        Hex.fromNumber(0), // value
        '0x', // data
        '0x', // accessList
        '0x', // authorizationList
        '0x', // r
        Hex.fromNumber(69), // v
        Hex.fromNumber(420), // s
      ]).slice(2)}` as const
      expect(TxEnvelopeEip7702.deserialize(serialized)).toMatchInlineSnapshot(`
        {
          "chainId": 1,
          "gas": 1n,
          "maxFeePerGas": 1n,
          "maxPriorityFeePerGas": 1n,
          "nonce": 0n,
          "r": "0x0000000000000000000000000000000000000000000000000000000000000045",
          "s": "0x00000000000000000000000000000000000000000000000000000000000001a4",
          "to": "0x0000000000000000000000000000000000000000",
          "type": "eip7702",
          "value": 0n,
          "yParity": 0,
        }
      `)
    })
  })

  describe('errors', () => {
    test('invalid access list (invalid address)', () => {
      expect(() =>
        TxEnvelopeEip7702.deserialize(
          `0x04${Rlp.fromHex([
            Hex.fromNumber(1), // chainId
            Hex.fromNumber(0), // nonce
            Hex.fromNumber(1), // maxPriorityFeePerGas
            Hex.fromNumber(1), // maxFeePerGas
            Hex.fromNumber(1), // gas
            '0x0000000000000000000000000000000000000000', // to
            Hex.fromNumber(0), // value
            '0x', // data
            [
              [
                '0x',
                [
                  '0x0000000000000000000000000000000000000000000000000000000000000001',
                ],
              ],
            ], // accessList
            '0x', // authorizationList
          ]).slice(2)}`,
        ),
      ).toThrowErrorMatchingInlineSnapshot(`
      [Address.InvalidAddressError: Address "0x" is invalid.

      Details: Address is not a 20 byte (40 hexadecimal character) value.]
    `)

      expect(() =>
        TxEnvelopeEip7702.deserialize(
          `0x04${Rlp.fromHex([
            Hex.fromNumber(1), // chainId
            Hex.fromNumber(0), // nonce
            Hex.fromNumber(1), // maxPriorityFeePerGas
            Hex.fromNumber(1), // maxFeePerGas
            Hex.fromNumber(1), // gas
            '0x0000000000000000000000000000000000000000', // to
            Hex.fromNumber(0), // value
            '0x', // data
            [['0x123456', ['0x0']]], // accessList
            '0x', // authorizationList
          ]).slice(2)}`,
        ),
      ).toThrowErrorMatchingInlineSnapshot(`
        [Address.InvalidAddressError: Address "0x123456" is invalid.

        Details: Address is not a 20 byte (40 hexadecimal character) value.]
      `)
    })

    test('invalid transaction (all missing)', () => {
      expect(() =>
        TxEnvelopeEip7702.deserialize(`0x04${Rlp.fromHex([]).slice(2)}`),
      ).toThrowErrorMatchingInlineSnapshot(`
      [TransactionEnvelope.InvalidSerializedError: Invalid serialized transaction of type "eip7702" was provided.

      Serialized Transaction: "0x04c0"
      Missing Attributes: chainId, nonce, maxPriorityFeePerGas, maxFeePerGas, gas, to, value, data, accessList, authorizationList]
    `)
    })

    test('invalid transaction (some missing)', () => {
      expect(() =>
        TxEnvelopeEip7702.deserialize(
          `0x04${Rlp.fromHex(['0x00', '0x01']).slice(2)}`,
        ),
      ).toThrowErrorMatchingInlineSnapshot(`
      [TransactionEnvelope.InvalidSerializedError: Invalid serialized transaction of type "eip7702" was provided.

      Serialized Transaction: "0x04c20001"
      Missing Attributes: maxPriorityFeePerGas, maxFeePerGas, gas, to, value, data, accessList, authorizationList]
    `)
    })

    test('invalid transaction (missing signature)', () => {
      expect(() =>
        TxEnvelopeEip7702.deserialize(
          `0x04${Rlp.fromHex([
            '0x',
            '0x',
            '0x',
            '0x',
            '0x',
            '0x',
            '0x',
            '0x',
            '0x',
            '0x',
            '0x',
          ]).slice(2)}`,
        ),
      ).toThrowErrorMatchingInlineSnapshot(`
      [TransactionEnvelope.InvalidSerializedError: Invalid serialized transaction of type "eip7702" was provided.

      Serialized Transaction: "0x04cb8080808080808080808080"
      Missing Attributes: r, s]
    `)
    })
  })
})

describe('from', () => {
  test('default', () => {
    {
      const envelope = TxEnvelopeEip7702.from({
        authorizationList: [],
        chainId: 1,
        nonce: 0n,
      })
      expect(envelope).toMatchInlineSnapshot(`
        {
          "authorizationList": [],
          "chainId": 1,
          "nonce": 0n,
          "type": "eip7702",
        }
      `)
      const serialized = TxEnvelopeEip7702.serialize(envelope)
      const envelope2 = TxEnvelopeEip7702.from(serialized)
      expect(envelope2).toEqual(envelope)
    }

    {
      const envelope = TxEnvelopeEip7702.from({
        authorizationList: [],
        chainId: 1,
        nonce: 0n,
        r: '0x0000000000000000000000000000000000000000000000000000000000000000',
        s: '0x0000000000000000000000000000000000000000000000000000000000000001',
        yParity: 0,
      })
      expect(envelope).toMatchInlineSnapshot(`
        {
          "authorizationList": [],
          "chainId": 1,
          "nonce": 0n,
          "r": "0x0000000000000000000000000000000000000000000000000000000000000000",
          "s": "0x0000000000000000000000000000000000000000000000000000000000000001",
          "type": "eip7702",
          "yParity": 0,
        }
      `)
      const serialized = TxEnvelopeEip7702.serialize(envelope)
      const envelope2 = TxEnvelopeEip7702.from(serialized)
      expect(envelope2).toEqual(envelope)
    }
  })

  test('options: signature', () => {
    const envelope = TxEnvelopeEip7702.from(
      {
        authorizationList: [],
        chainId: 1,
        nonce: 0n,
      },
      {
        signature: {
          r: '0x0000000000000000000000000000000000000000000000000000000000000000',
          s: '0x0000000000000000000000000000000000000000000000000000000000000001',
          yParity: 0,
        },
      },
    )
    expect(envelope).toMatchInlineSnapshot(`
      {
        "authorizationList": [],
        "chainId": 1,
        "nonce": 0n,
        "r": "0x0000000000000000000000000000000000000000000000000000000000000000",
        "s": "0x0000000000000000000000000000000000000000000000000000000000000001",
        "type": "eip7702",
        "yParity": 0,
      }
    `)
    const serialized = TxEnvelopeEip7702.serialize(envelope)
    const envelope2 = TxEnvelopeEip7702.from(serialized)
    expect(envelope2).toEqual(envelope)
  })
})

describe('getSignPayload', () => {
  test('default', () => {
    const authorization = Authorization.from({
      address: wagmiContractConfig.address,
      chainId: 1,
      nonce: 785n,
    })
    const signature = Secp256k1.sign({
      payload: Authorization.getSignPayload(authorization),
      privateKey: accounts[0].privateKey,
    })

    const envelope = TxEnvelopeEip7702.from({
      authorizationList: [Authorization.from(authorization, { signature })],
      chainId: 1,
      gas: 21000n,
      maxFeePerGas: 13000000000n,
      maxPriorityFeePerGas: 1000000000n,
      nonce: 665n,
      value: 1000000000000000000n,
      to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8',
    })

    const hash = TxEnvelopeEip7702.hash(envelope, { presign: true })
    expect(hash).toMatchInlineSnapshot(
      `"0x6458c62f981287bcbf1c85861e69de9c7344793116dc9388d90274ab153da8d8"`,
    )

    const signature_tx = Secp256k1.sign({
      payload: TxEnvelopeEip7702.getSignPayload(envelope),
      privateKey: accounts[0].privateKey,
    })

    const envelope_signed = TxEnvelopeEip7702.from(envelope, {
      signature: signature_tx,
    })

    {
      const hash = TxEnvelopeEip7702.hash(envelope_signed)
      expect(hash).toMatchInlineSnapshot(
        `"0x9714058a95376ce5963b33200dce2fb3afc6ebbefd47a65d9d137c0f66a5e7bf"`,
      )
    }
    {
      const hash_presign = TxEnvelopeEip7702.getSignPayload(envelope_signed)
      expect(hash_presign).toEqual(hash)
    }
  })
})

describe('hash', () => {
  test('default', () => {
    const envelope = TxEnvelopeEip7702.from({
      authorizationList: [],
      chainId: 1,
      gas: 21000n,
      maxFeePerGas: 13000000000n,
      maxPriorityFeePerGas: 1000000000n,
      nonce: 665n,
      value: 1000000000000000000n,
      to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8',
      r: '0xacf664dcd984d082b68c434feb66ac684711babdeefe6f101bf8df88fc367a37',
      s: '0x5e0800058a9b5c2250bed60ee969a45b7445e562a8298c2d222d114e6dfbfcb9',
      yParity: 0,
    })

    const hash = TxEnvelopeEip7702.hash(envelope)
    expect(hash).toMatchInlineSnapshot(
      `"0xa5ccd813e459b9154917af19270091cf3312bd759af08e0e18cd2560ef586cf4"`,
    )
  })
})

describe('serialize', () => {
  const authorization_1 = Authorization.from({
    address: wagmiContractConfig.address,
    chainId: 1,
    nonce: 785n,
  })
  const signature_1 = Secp256k1.sign({
    payload: Authorization.getSignPayload(authorization_1),
    privateKey: accounts[0].privateKey,
  })

  const authorization_2 = Authorization.from({
    address: wagmiContractConfig.address,
    chainId: 10,
    nonce: 786n,
  })
  const signature_2 = Secp256k1.sign({
    payload: Authorization.getSignPayload(authorization_2),
    privateKey: accounts[0].privateKey,
  })

  const transaction = TxEnvelopeEip7702.from({
    authorizationList: [
      Authorization.from(authorization_1, { signature: signature_1 }),
      Authorization.from(authorization_2, { signature: signature_2 }),
    ],
    chainId: 1,
    nonce: 785n,
    to: accounts[1].address,
    value: Value.fromEther('1'),
    maxFeePerGas: Value.fromGwei('2'),
    maxPriorityFeePerGas: Value.fromGwei('2'),
  })

  test('default', () => {
    const serialized = TxEnvelopeEip7702.serialize(transaction)
    assertType<TxEnvelopeEip7702.Serialized>(serialized)
    expect(serialized).toEqual(
      '0x04f8ed0182031184773594008477359400809470997970c51812dc3a010c7d01b50e0d17dc79c8880de0b6b3a764000080c0f8bcf85c0194fba3912ca04dd458c843e2ee08967fc04f3579c282031101a05f9eab9d5d717098430d03780924a379e350e5cdd38fb7ffe707393d587e4e35a07e28e4c4eadc81dafea5d73c8e5327034306d4ad2dc4dbd94ef829bec57f04dbf85c0a94fba3912ca04dd458c843e2ee08967fc04f3579c282031280a0f4181dd27f6b801404cd3bc24207aa1b07b1ff8991ea847b0af1a6c3e4ba9e99a00fda7db5a05f0c5298339ffa7ec9be2d2a483f5c0af7fe267237b01290bc9582',
    )
    expect(TxEnvelopeEip7702.deserialize(serialized)).toEqual(transaction)
  })

  test('default (all zeros)', () => {
    const transaction = TxEnvelopeEip7702.from({
      authorizationList: [],
      to: undefined,
      nonce: 0n,
      chainId: 1,
      maxFeePerGas: 0n,
      maxPriorityFeePerGas: 0n,
      value: 0n,
    })

    const serialized = TxEnvelopeEip7702.serialize(transaction)

    expect(serialized).toEqual('0x04ca0180808080808080c0c0')
    expect(TxEnvelopeEip7702.deserialize(serialized)).toEqual({
      authorizationList: [],
      chainId: 1,
      nonce: 0n,
      type: 'eip7702',
    })
  })

  test('minimal (w/ type)', () => {
    const transaction = TxEnvelopeEip7702.from({
      authorizationList: [],
      chainId: 1,
      nonce: 0n,
    })
    const serialized = TxEnvelopeEip7702.serialize(transaction)
    expect(serialized).toEqual('0x04ca0180808080808080c0c0')
    expect(TxEnvelopeEip7702.deserialize(serialized)).toEqual(transaction)
  })

  test('minimal (w/ maxFeePerGas)', () => {
    const transaction = TxEnvelopeEip7702.from({
      authorizationList: [],
      chainId: 1,
      maxFeePerGas: 1n,
      nonce: 0n,
    })
    const serialized = TxEnvelopeEip7702.serialize(transaction)
    expect(serialized).toEqual('0x04ca0180800180808080c0c0')
    expect(TxEnvelopeEip7702.deserialize(serialized)).toEqual(transaction)
  })

  test('gas', () => {
    const transaction_gas = TxEnvelopeEip7702.from({
      ...transaction,
      gas: 21001n,
    })
    const serialized = TxEnvelopeEip7702.serialize(transaction_gas)
    expect(serialized).toEqual(
      '0x04f8ef01820311847735940084773594008252099470997970c51812dc3a010c7d01b50e0d17dc79c8880de0b6b3a764000080c0f8bcf85c0194fba3912ca04dd458c843e2ee08967fc04f3579c282031101a05f9eab9d5d717098430d03780924a379e350e5cdd38fb7ffe707393d587e4e35a07e28e4c4eadc81dafea5d73c8e5327034306d4ad2dc4dbd94ef829bec57f04dbf85c0a94fba3912ca04dd458c843e2ee08967fc04f3579c282031280a0f4181dd27f6b801404cd3bc24207aa1b07b1ff8991ea847b0af1a6c3e4ba9e99a00fda7db5a05f0c5298339ffa7ec9be2d2a483f5c0af7fe267237b01290bc9582',
    )
    expect(TxEnvelopeEip7702.deserialize(serialized)).toEqual(transaction_gas)
  })

  test('accessList', () => {
    const transaction_accessList = TxEnvelopeEip7702.from({
      ...transaction,
      accessList: [
        {
          address: '0x0000000000000000000000000000000000000000',
          storageKeys: [
            '0x0000000000000000000000000000000000000000000000000000000000000001',
            '0x60fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fe',
          ],
        },
      ],
    })
    const serialized = TxEnvelopeEip7702.serialize(transaction_accessList)
    expect(serialized).toEqual(
      '0x04f901490182031184773594008477359400809470997970c51812dc3a010c7d01b50e0d17dc79c8880de0b6b3a764000080f85bf859940000000000000000000000000000000000000000f842a00000000000000000000000000000000000000000000000000000000000000001a060fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fef8bcf85c0194fba3912ca04dd458c843e2ee08967fc04f3579c282031101a05f9eab9d5d717098430d03780924a379e350e5cdd38fb7ffe707393d587e4e35a07e28e4c4eadc81dafea5d73c8e5327034306d4ad2dc4dbd94ef829bec57f04dbf85c0a94fba3912ca04dd458c843e2ee08967fc04f3579c282031280a0f4181dd27f6b801404cd3bc24207aa1b07b1ff8991ea847b0af1a6c3e4ba9e99a00fda7db5a05f0c5298339ffa7ec9be2d2a483f5c0af7fe267237b01290bc9582',
    )
    expect(TxEnvelopeEip7702.deserialize(serialized)).toEqual(
      transaction_accessList,
    )
  })

  test('data', () => {
    const transaction_data = TxEnvelopeEip7702.from({
      ...transaction,
      data: '0x1234',
    })
    const serialized = TxEnvelopeEip7702.serialize(transaction_data)
    expect(serialized).toEqual(
      '0x04f8ef0182031184773594008477359400809470997970c51812dc3a010c7d01b50e0d17dc79c8880de0b6b3a7640000821234c0f8bcf85c0194fba3912ca04dd458c843e2ee08967fc04f3579c282031101a05f9eab9d5d717098430d03780924a379e350e5cdd38fb7ffe707393d587e4e35a07e28e4c4eadc81dafea5d73c8e5327034306d4ad2dc4dbd94ef829bec57f04dbf85c0a94fba3912ca04dd458c843e2ee08967fc04f3579c282031280a0f4181dd27f6b801404cd3bc24207aa1b07b1ff8991ea847b0af1a6c3e4ba9e99a00fda7db5a05f0c5298339ffa7ec9be2d2a483f5c0af7fe267237b01290bc9582',
    )
    expect(TxEnvelopeEip7702.deserialize(serialized)).toEqual(transaction_data)
  })

  test('options: signature', async () => {
    const signature = Secp256k1.sign({
      payload: TxEnvelopeEip7702.getSignPayload(transaction),
      privateKey: accounts[0].privateKey,
    })
    const serialized = TxEnvelopeEip7702.serialize(transaction, {
      signature,
    })
    expect(serialized).toEqual(
      '0x04f901300182031184773594008477359400809470997970c51812dc3a010c7d01b50e0d17dc79c8880de0b6b3a764000080c0f8bcf85c0194fba3912ca04dd458c843e2ee08967fc04f3579c282031101a05f9eab9d5d717098430d03780924a379e350e5cdd38fb7ffe707393d587e4e35a07e28e4c4eadc81dafea5d73c8e5327034306d4ad2dc4dbd94ef829bec57f04dbf85c0a94fba3912ca04dd458c843e2ee08967fc04f3579c282031280a0f4181dd27f6b801404cd3bc24207aa1b07b1ff8991ea847b0af1a6c3e4ba9e99a00fda7db5a05f0c5298339ffa7ec9be2d2a483f5c0af7fe267237b01290bc958280a0c6e17df66749225c040251e51ab7c732b7c92d7844ed731c80ee5ff3b15baf42a03b4fb995198301b08f630d1aecfa4a567a722e184eda6e3789481091a0cd1dd5',
    )
    expect(TxEnvelopeEip7702.deserialize(serialized)).toEqual({
      ...transaction,
      ...signature,
    })
  })

  test('options: signature', () => {
    expect(
      TxEnvelopeEip7702.serialize(transaction, {
        signature: {
          r: '0x60fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fe',
          s: '0x60fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fe',
          yParity: 1,
        },
      }),
    ).toEqual(
      '0x04f901300182031184773594008477359400809470997970c51812dc3a010c7d01b50e0d17dc79c8880de0b6b3a764000080c0f8bcf85c0194fba3912ca04dd458c843e2ee08967fc04f3579c282031101a05f9eab9d5d717098430d03780924a379e350e5cdd38fb7ffe707393d587e4e35a07e28e4c4eadc81dafea5d73c8e5327034306d4ad2dc4dbd94ef829bec57f04dbf85c0a94fba3912ca04dd458c843e2ee08967fc04f3579c282031280a0f4181dd27f6b801404cd3bc24207aa1b07b1ff8991ea847b0af1a6c3e4ba9e99a00fda7db5a05f0c5298339ffa7ec9be2d2a483f5c0af7fe267237b01290bc958201a060fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fea060fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fe',
    )

    expect(
      TxEnvelopeEip7702.serialize(transaction, {
        signature: {
          r: '0x60fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fe',
          s: '0x60fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fe',
          yParity: 0,
        },
      }),
    ).toEqual(
      '0x04f901300182031184773594008477359400809470997970c51812dc3a010c7d01b50e0d17dc79c8880de0b6b3a764000080c0f8bcf85c0194fba3912ca04dd458c843e2ee08967fc04f3579c282031101a05f9eab9d5d717098430d03780924a379e350e5cdd38fb7ffe707393d587e4e35a07e28e4c4eadc81dafea5d73c8e5327034306d4ad2dc4dbd94ef829bec57f04dbf85c0a94fba3912ca04dd458c843e2ee08967fc04f3579c282031280a0f4181dd27f6b801404cd3bc24207aa1b07b1ff8991ea847b0af1a6c3e4ba9e99a00fda7db5a05f0c5298339ffa7ec9be2d2a483f5c0af7fe267237b01290bc958280a060fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fea060fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fe',
    )

    expect(
      TxEnvelopeEip7702.serialize(transaction, {
        signature: {
          r: '0x0000000000000000000000000000000000000000000000000000000000000000',
          s: '0x0000000000000000000000000000000000000000000000000000000000000000',
          yParity: 0,
        },
      }),
    ).toEqual(
      '0x04f8f00182031184773594008477359400809470997970c51812dc3a010c7d01b50e0d17dc79c8880de0b6b3a764000080c0f8bcf85c0194fba3912ca04dd458c843e2ee08967fc04f3579c282031101a05f9eab9d5d717098430d03780924a379e350e5cdd38fb7ffe707393d587e4e35a07e28e4c4eadc81dafea5d73c8e5327034306d4ad2dc4dbd94ef829bec57f04dbf85c0a94fba3912ca04dd458c843e2ee08967fc04f3579c282031280a0f4181dd27f6b801404cd3bc24207aa1b07b1ff8991ea847b0af1a6c3e4ba9e99a00fda7db5a05f0c5298339ffa7ec9be2d2a483f5c0af7fe267237b01290bc9582808080',
    )
  })

  test('behavior: network', async () => {
    const authority = accounts[0]
    const delegate = accounts[1]

    // Deploy delegate contract
    const contractAddress = await (async () => {
      const nonce = await anvilMainnet.request({
        method: 'eth_getTransactionCount',
        params: [delegate.address, 'pending'],
      })

      const envelope = TxEnvelopeEip1559.from({
        chainId: 1,
        nonce: BigInt(nonce),
        gas: 3_000_000n,
        data: '0x66365f5f37365fa05f5260076019f3',
        maxFeePerGas: Value.fromGwei('20'),
        maxPriorityFeePerGas: Value.fromGwei('10'),
      })

      const signature = Secp256k1.sign({
        payload: TxEnvelopeEip1559.getSignPayload(envelope),
        privateKey: delegate.privateKey,
      })

      const serialized = TxEnvelopeEip1559.serialize(envelope, {
        signature,
      })

      const hash = await anvilMainnet.request({
        method: 'eth_sendRawTransaction',
        params: [serialized],
      })

      await anvilMainnet.request({
        method: 'anvil_mine',
        params: ['0x1', '0x0'],
      })

      const receipt = await anvilMainnet.request({
        method: 'eth_getTransactionReceipt',
        params: [hash],
      })
      return receipt!.contractAddress
    })()

    // Authorize injection of delegation contract onto authority
    const authorization = await (async () => {
      const nonce = await anvilMainnet.request({
        method: 'eth_getTransactionCount',
        params: [authority.address, 'pending'],
      })

      const authorization = Authorization.from({
        address: contractAddress!,
        chainId: 1,
        nonce: BigInt(nonce),
      })

      const signature = Secp256k1.sign({
        payload: Authorization.getSignPayload(authorization),
        privateKey: authority.privateKey,
      })

      return Authorization.from(authorization, { signature })
    })()

    const nonce = await anvilMainnet.request({
      method: 'eth_getTransactionCount',
      params: [delegate.address, 'pending'],
    })

    const envelope = TxEnvelopeEip7702.from({
      chainId: 1,
      authorizationList: [authorization],
      data: '0xdeadbeef',
      nonce: BigInt(nonce),
      gas: 1_000_000n,
      to: authority.address,
      maxFeePerGas: Value.fromGwei('20'),
      maxPriorityFeePerGas: Value.fromGwei('10'),
    })

    const signature = Secp256k1.sign({
      payload: TxEnvelopeEip7702.getSignPayload(envelope),
      privateKey: delegate.privateKey,
    })

    const serialized = TxEnvelopeEip7702.serialize(envelope, {
      signature,
    })

    const hash = await anvilMainnet.request({
      method: 'eth_sendRawTransaction',
      params: [serialized],
    })

    const tx = await anvilMainnet.request({
      method: 'eth_getTransactionByHash',
      params: [hash],
    })

    expect(tx).toMatchObject({
      accessList: [],
      chainId: '0x1',
      from: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8',
      gas: '0xf4240',
      input: '0xdeadbeef',
      maxFeePerGas: '0x4a817c800',
      maxPriorityFeePerGas: '0x2540be400',
      to: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266',
      transactionIndex: '0x0',
      type: '0x4',
      value: '0x0',
    })
    expect(tx?.authorizationList).toHaveLength(1)
    expect(tx?.authorizationList?.[0]).toMatchObject({
      chainId: '0x1',
    })

    const receipt = await anvilMainnet.request({
      method: 'eth_getTransactionReceipt',
      params: [hash],
    })

    expect({
      ...receipt,
      blockHash: null,
      blockNumber: null,
      blockTimestamp: null,
      cumulativeGasUsed: null,
      effectiveGasPrice: null,
      gasUsed: null,
      transactionHash: null,
      logs: receipt!.logs.map((log) => ({
        ...log,
        blockHash: null,
        blockNumber: null,
        blockTimestamp: null,
        transactionHash: null,
      })),
    }).toMatchInlineSnapshot(`
      {
        "blobGasPrice": "0x1",
        "blockHash": null,
        "blockNumber": null,
        "blockTimestamp": null,
        "contractAddress": null,
        "cumulativeGasUsed": null,
        "effectiveGasPrice": null,
        "from": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8",
        "gasUsed": null,
        "logs": [
          {
            "address": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266",
            "blockHash": null,
            "blockNumber": null,
            "blockTimestamp": null,
            "data": "0xdeadbeef",
            "logIndex": "0x0",
            "removed": false,
            "topics": [],
            "transactionHash": null,
            "transactionIndex": "0x0",
          },
        ],
        "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
        "status": "0x1",
        "to": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266",
        "transactionHash": null,
        "transactionIndex": "0x0",
        "type": "0x4",
      }
    `)
  })
})

describe('toRpc', () => {
  test('default', () => {
    const transaction = TxEnvelopeEip7702.toRpc({
      authorizationList: [
        {
          address: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8',
          chainId: 1,
          nonce: 0n,
          r: '0x0000000000000000000000000000000000000000000000000000000000000001',
          s: '0x0000000000000000000000000000000000000000000000000000000000000002',
          yParity: 0,
        },
      ],
      chainId: 1,
      nonce: 0n,
      gas: 21000n,
      maxFeePerGas: 1000000000n,
      to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8',
      value: 1000000000000000000n,
      r: '0x0000000000000000000000000000000000000000000000000000000000000001',
      s: '0x0000000000000000000000000000000000000000000000000000000000000002',
      yParity: 0,
    })
    expect(transaction).toMatchInlineSnapshot(`
      {
        "authorizationList": [
          {
            "address": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8",
            "chainId": "0x1",
            "nonce": "0x0",
            "r": "0x0000000000000000000000000000000000000000000000000000000000000001",
            "s": "0x0000000000000000000000000000000000000000000000000000000000000002",
            "yParity": "0x0",
          },
        ],
        "chainId": "0x1",
        "data": undefined,
        "gas": "0x5208",
        "maxFeePerGas": "0x3b9aca00",
        "nonce": "0x0",
        "r": "0x0000000000000000000000000000000000000000000000000000000000000001",
        "s": "0x0000000000000000000000000000000000000000000000000000000000000002",
        "to": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8",
        "type": "0x4",
        "value": "0xde0b6b3a7640000",
        "yParity": "0x0",
      }
    `)
  })

  test('numberish inputs', () => {
    const fromBigint = TxEnvelopeEip7702.toRpc({
      authorizationList: [
        {
          address: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8',
          chainId: 1,
          nonce: 0n,
          r: '0x0000000000000000000000000000000000000000000000000000000000000001',
          s: '0x0000000000000000000000000000000000000000000000000000000000000002',
          yParity: 0,
        },
      ],
      chainId: 1,
      nonce: 0n,
      gas: 21000n,
      maxFeePerGas: 1000n,
      maxPriorityFeePerGas: 100n,
      to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8',
      value: 1000n,
      r: '0x0000000000000000000000000000000000000000000000000000000000000001',
      s: '0x0000000000000000000000000000000000000000000000000000000000000002',
      yParity: 0,
    })
    const fromNumber = TxEnvelopeEip7702.toRpc({
      authorizationList: [
        {
          address: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8',
          chainId: 1,
          nonce: 0n,
          r: '0x0000000000000000000000000000000000000000000000000000000000000001',
          s: '0x0000000000000000000000000000000000000000000000000000000000000002',
          yParity: 0,
        },
      ],
      chainId: 1,
      nonce: 0,
      gas: 21000,
      maxFeePerGas: 1000,
      maxPriorityFeePerGas: 100,
      to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8',
      value: 1000,
      r: '0x0000000000000000000000000000000000000000000000000000000000000001',
      s: '0x0000000000000000000000000000000000000000000000000000000000000002',
      yParity: 0,
    })
    const fromHex = TxEnvelopeEip7702.toRpc({
      authorizationList: [
        {
          address: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8',
          chainId: 1,
          nonce: 0n,
          r: '0x0000000000000000000000000000000000000000000000000000000000000001',
          s: '0x0000000000000000000000000000000000000000000000000000000000000002',
          yParity: 0,
        },
      ],
      chainId: '0x1',
      nonce: '0x0',
      gas: '0x5208',
      maxFeePerGas: '0x3e8',
      maxPriorityFeePerGas: '0x64',
      to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8',
      value: '0x3e8',
      r: '0x0000000000000000000000000000000000000000000000000000000000000001',
      s: '0x0000000000000000000000000000000000000000000000000000000000000002',
      yParity: '0x0',
    })
    expect(fromBigint).toEqual(fromNumber)
    expect(fromBigint).toEqual(fromHex)
  })
})

describe('validate', () => {
  test('default', () => {
    expect(
      TxEnvelopeEip7702.validate({
        authorizationList: [],
        chainId: 1,
      }),
    ).toBe(true)
    expect(
      TxEnvelopeEip7702.validate({
        authorizationList: [],
        maxFeePerGas: 2n ** 257n,
        chainId: 1,
      }),
    ).toBe(false)
  })
})

test('exports', () => {
  expect(Object.keys(TxEnvelopeEip7702)).toMatchInlineSnapshot(`
    [
      "serializedType",
      "type",
      "assert",
      "deserialize",
      "from",
      "getSignPayload",
      "hash",
      "serialize",
      "toRpc",
      "validate",
    ]
  `)
})
