import { describe, it, expect } from 'vitest' import { withEIP712Domain } from './signature' import type { EIP712TypedData } from './signature' const baseTypes = { TransferWithAuthorization: [ { name: 'from', type: 'address' }, { name: 'to', type: 'address' }, { name: 'value', type: 'uint256' }, { name: 'validAfter', type: 'uint256' }, { name: 'validBefore', type: 'uint256' }, { name: 'nonce', type: 'bytes32' } ] } const baseMessage = { from: '0xabc', to: '0xdef', value: '1000000', validAfter: 0, validBefore: 9999999999, nonce: '0xdeadbeef' } describe('withEIP712Domain', () => { it('injects EIP712Domain with all four standard fields when all are present in domain', () => { const input: EIP712TypedData = { domain: { name: 'USD Coin', version: '2', chainId: 1, verifyingContract: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48' }, types: baseTypes, primaryType: 'TransferWithAuthorization', message: baseMessage } const result = withEIP712Domain(input) expect(result.types.EIP712Domain).toEqual([ { name: 'name', type: 'string' }, { name: 'version', type: 'string' }, { name: 'chainId', type: 'uint256' }, { name: 'verifyingContract', type: 'address' } ]) // Existing types are preserved expect(result.types.TransferWithAuthorization).toEqual( baseTypes.TransferWithAuthorization ) // Other fields are unchanged expect(result.domain).toBe(input.domain) expect(result.primaryType).toBe(input.primaryType) expect(result.message).toBe(input.message) }) it('omits version from EIP712Domain when domain has no version (UNI-style)', () => { const input: EIP712TypedData = { domain: { name: 'Uniswap V2', chainId: 1, verifyingContract: '0x...' }, types: { Permit: [{ name: 'owner', type: 'address' }] }, primaryType: 'Permit', message: {} } const result = withEIP712Domain(input) expect(result.types.EIP712Domain).toEqual([ { name: 'name', type: 'string' }, { name: 'chainId', type: 'uint256' }, { name: 'verifyingContract', type: 'address' } ]) }) it('includes salt when present in domain', () => { const input: EIP712TypedData = { domain: { name: 'MyToken', chainId: 137, salt: '0xdeadbeef00000000000000000000000000000000000000000000000000000000' }, types: baseTypes, primaryType: 'TransferWithAuthorization', message: baseMessage } const result = withEIP712Domain(input) expect(result.types.EIP712Domain).toEqual([ { name: 'name', type: 'string' }, { name: 'chainId', type: 'uint256' }, { name: 'salt', type: 'bytes32' } ]) }) it('preserves canonical field order: name, version, chainId, verifyingContract, salt', () => { const input: EIP712TypedData = { domain: { salt: '0x00000000000000000000000000000000000000000000000000000000000000ab', verifyingContract: '0x123', name: 'Token', chainId: 1, version: '1' }, types: baseTypes, primaryType: 'TransferWithAuthorization', message: baseMessage } const result = withEIP712Domain(input) const names = result.types.EIP712Domain.map(f => f.name) expect(names).toEqual([ 'name', 'version', 'chainId', 'verifyingContract', 'salt' ]) }) it('does not modify input when EIP712Domain is already present', () => { const existingEIP712Domain = [ { name: 'name', type: 'string' }, { name: 'chainId', type: 'uint256' } ] const input: EIP712TypedData = { domain: { name: 'USD Coin', version: '2', chainId: 1, verifyingContract: '0xabc' }, types: { EIP712Domain: existingEIP712Domain, ...baseTypes }, primaryType: 'TransferWithAuthorization', message: baseMessage } const result = withEIP712Domain(input) // Returns input unchanged — caller's EIP712Domain is authoritative expect(result).toBe(input) expect(result.types.EIP712Domain).toBe(existingEIP712Domain) }) it('returns a new object (does not mutate input) when injecting EIP712Domain', () => { const input: EIP712TypedData = { domain: { name: 'USD Coin', version: '2', chainId: 1, verifyingContract: '0xabc' }, types: { ...baseTypes }, primaryType: 'TransferWithAuthorization', message: baseMessage } const result = withEIP712Domain(input) expect(result).not.toBe(input) expect(result.types).not.toBe(input.types) // Original is untouched expect(input.types.EIP712Domain).toBeUndefined() }) it('handles a domain with only chainId (minimal domain)', () => { const input: EIP712TypedData = { domain: { chainId: 8453 }, types: baseTypes, primaryType: 'TransferWithAuthorization', message: baseMessage } const result = withEIP712Domain(input) expect(result.types.EIP712Domain).toEqual([ { name: 'chainId', type: 'uint256' } ]) }) it('places EIP712Domain before other types in the types object', () => { const input: EIP712TypedData = { domain: { name: 'USD Coin', version: '2', chainId: 1, verifyingContract: '0xabc' }, types: baseTypes, primaryType: 'TransferWithAuthorization', message: baseMessage } const keys = Object.keys(withEIP712Domain(input).types) expect(keys[0]).toBe('EIP712Domain') }) })