const { ethers } = require('ethers'); // see https://github.com/ChainAgnostic/namespaces/pull/140 const CAIP350 = Object.fromEntries(Object.entries({ eip155: '0x0000', solana: '0x0002', }).flatMap(([k,v]) => [[k,v],[v,k]])); const ENCODERS = { eip155: { reference: x => ethers.toBigInt(x).toString(), address: ethers.hexlify }, solana: { reference: ethers.encodeBase58, address: ethers.encodeBase58 }, } const decode = value => { if (value === undefined || value === null || value === '') return '0x'; try { return ethers.toBeHex(value) } catch {} try { return ethers.toBeHex(ethers.decodeBase58(value)); } catch {} try { return ethers.toBeHex(ethers.decodeBase64(value)); } catch {} throw new Error(`Unable to decode ${value}`) } const formatERC7913v1 = ({ type, reference, address }) => { const chainReferenceHex = decode(reference); const addressHex = decode(address); const interoperableAddress = ethers.solidityPacked([ 'uint16', // version 'uint16', // type 'uint8', // chainReferenceLength 'bytes', // chainReference 'uint8', // addressLength 'bytes', // address ], [ 1n, CAIP350[type], ethers.getBytes(chainReferenceHex).length, chainReferenceHex, ethers.getBytes(addressHex).length, addressHex ]); const checksum = ethers.keccak256(ethers.getBytes(interoperableAddress).slice(2)).slice(2, 10).toUpperCase(); const interoperableName = [ address, '@', type, reference && `:${reference}`, '#', checksum ].filter(Boolean).join(''); return { address : interoperableAddress, name: interoperableName, checksum }; } const parseERC7913v1 = input => { const parse = input.match(/(?
[.-:_%a-zA-Z0-9]*)@(?[.-:_a-zA-Z0-9]*)#(?[0-9A-F]{8})/); if (parse) { const { address, chain, checksum } = parse.groups; const [ type, reference ] = chain.split(/:(.*)/s); const entry = { type, reference: reference || undefined, address: address || undefined }; return formatERC7913v1(entry).checksum === checksum ? entry : null; } else if (ethers.isBytesLike(input)) { const buffer = ethers.getBytes(input); if (ethers.toBigInt(buffer.slice(0, 2)) !== 1n) throw new Error('only version 1 is supported'); const type = CAIP350[ethers.hexlify(buffer.slice(2,4))]; const referenceLength = buffer[4]; const reference = referenceLength ? ENCODERS[type].reference(buffer.slice(5,5+referenceLength)) : undefined; const addressLength = buffer[5+referenceLength]; const address = addressLength ? ENCODERS[type].address(buffer.slice(6+referenceLength,6+referenceLength+addressLength)) : undefined; return buffer.length >= 6+referenceLength+addressLength ? { type, reference, address } : null; } else { return null; } } // Examples [ { type: 'eip155', reference: '1', address: '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045', }, { type: 'solana', reference: '5eykt4UsFv8P8NJdTREpY1vzqKqZKvdpKuc147dw2N9d', address: 'MJKqp326RZCHnAAbew9MDdui3iCKWco7fsK9sVuZTX2', }, { type: 'eip155', address: '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045', }, { type: 'solana', reference: '5eykt4UsFv8P8NJdTREpY1vzqKqZKvdpKuc147dw2N9d', }, { type: 'eip155', reference: '42161', address: '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045', }, ].forEach(entry => { console.log('=========================================================') console.log(formatERC7913v1(entry)) console.log(parseERC7913v1(formatERC7913v1(entry).address)) console.log(parseERC7913v1(formatERC7913v1(entry).name)) });