Skip to content

Instantly share code, notes, and snippets.

@Amxx
Last active June 6, 2025 21:20
Show Gist options
  • Save Amxx/ea6f262da365e81f799cfdc3994cb98c to your computer and use it in GitHub Desktop.
Save Amxx/ea6f262da365e81f799cfdc3994cb98c to your computer and use it in GitHub Desktop.
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(/(?<address>[.-:_%a-zA-Z0-9]*)@(?<chain>[.-:_a-zA-Z0-9]*)#(?<checksum>[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))
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment