Skip to content

Commit

Permalink
Merge pull request #399 from ensdomains/feat/unnamed-evm-coins
Browse files Browse the repository at this point in the history
feat: support unknown evm chains
  • Loading branch information
TateB committed Feb 15, 2024
2 parents 455a334 + 1b0e0f1 commit 766db72
Show file tree
Hide file tree
Showing 8 changed files with 106 additions and 19 deletions.
10 changes: 10 additions & 0 deletions src/async.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,23 @@ test("evm coin name", async () => {
expect(coder.coinType).toBe(2147483658);
expect(coder.name).toBe("op");
expect(coder.evmChainId).toBe(10);
expect("isUnknownChain" in coder).toBeFalse();
});

test("evm coin type", async () => {
const coder = await getCoderByCoinTypeAsync(2147483658);
expect(coder.coinType).toBe(2147483658);
expect(coder.name).toBe("op");
expect(coder.evmChainId).toBe(10);
expect(coder.isUnknownChain).toBeFalse();
});

test("unknown evm coin type", async () => {
const coder = await getCoderByCoinTypeAsync(2147483659);
expect(coder.coinType).toBe(2147483659);
expect(coder.name).toBe("Unknown Chain (11)");
expect(coder.evmChainId).toBe(11);
expect(coder.isUnknownChain).toBeTrue();
});

const nonEvmCoinNames = Object.keys(nonEvmCoinNameToTypeMap);
Expand Down
12 changes: 6 additions & 6 deletions src/async.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,24 +46,24 @@ export const getCoderByCoinTypeAsync = async <
const names =
coinTypeToNameMap[String(coinType) as keyof typeof coinTypeToNameMap];

if (!names) throw new Error(`Unsupported coin type: ${coinType}`);

const [name] = names;

if (coinType >= SLIP44_MSB) {
// EVM coin
const evmChainId = coinTypeToEvmChainId(coinType);
const isUnknownChain = !names;
const name = isUnknownChain ? `Unknown Chain (${evmChainId})` : names[0];
return {
name,
coinType: coinType as EvmCoinType,
evmChainId,
isUnknownChain,
encode: eth.encode,
decode: eth.decode,
} as GetCoderByCoinType<TCoinType>;
}
const mod = await import(`./coin/${name}`);

if (!names) throw new Error(`Unsupported coin type: ${coinType}`);
const [name] = names;
const mod = await import(`./coin/${name}`);
if (!mod) throw new Error(`Failed to load coin: ${name}`);

return mod[name];
};
12 changes: 12 additions & 0 deletions src/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ test("evm coin name", () => {
expect(coder.coinType).toBe(2147483658);
expect(coder.name).toBe("op");
expect(coder.evmChainId).toBe(10);
expect("isUnknownChain" in coder).toBeFalse();
expect(coder.encode).toBeFunction();
expect(coder.decode).toBeFunction();
});
Expand All @@ -40,6 +41,17 @@ test("evm coin type", () => {
expect(coder.coinType).toBe(2147483658);
expect(coder.name).toBe("op");
expect(coder.evmChainId).toBe(10);
expect(coder.isUnknownChain).toBeFalse();
expect(coder.encode).toBeFunction();
expect(coder.decode).toBeFunction();
});

test("unknown evm coin type", () => {
const coder = getCoderByCoinType(2147483659);
expect(coder.coinType).toBe(2147483659);
expect(coder.name).toBe("Unknown Chain (11)");
expect(coder.evmChainId).toBe(11);
expect(coder.isUnknownChain).toBeTrue();
expect(coder.encode).toBeFunction();
expect(coder.decode).toBeFunction();
});
Expand Down
14 changes: 10 additions & 4 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,22 +72,28 @@ export const getCoderByCoinType = <
): GetCoderByCoinType<TCoinType> => {
const names =
coinTypeToNameMap[String(coinType) as keyof typeof coinTypeToNameMap];
if (!names) {
throw new Error(`Unsupported coin type: ${coinType}`);
}
const [name] = names;
// https://docs.ens.domains/ens-improvement-proposals/ensip-11-evmchain-address-resolution

if (coinType >= SLIP44_MSB) {
// EVM coin
const evmChainId = coinTypeToEvmChainId(coinType);
const isUnknownChain = !names;
const name = isUnknownChain ? `Unknown Chain (${evmChainId})` : names[0]; // name is derivable
const ethFormat = formats["eth"];
return {
name,
coinType: coinType as EvmCoinType,
evmChainId,
isUnknownChain,
encode: ethFormat.encode,
decode: ethFormat.decode,
} as GetCoderByCoinType<TCoinType>;
}

if (!names) {
throw new Error(`Unsupported coin type: ${coinType}`);
}
const [name] = names;
const format = formats[name as keyof typeof formats];
return format as GetCoderByCoinType<TCoinType>;
};
27 changes: 19 additions & 8 deletions src/types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { Subtract } from "ts-arithmetic";
import type { GtOrEq, Subtract } from "ts-arithmetic";
import type * as formats from "./coins.js";
import type {
coinNameToTypeMap,
Expand All @@ -20,13 +20,15 @@ type NonEvmCoinTypeToFormat = {
};
export type CoinTypeToFormatMap = {
[key in CoinType]: key extends EvmCoinType
? Prettify<GetEvmCoin<CoinTypeToNameMap[`${key}`][0]>>
? Prettify<GetEvmCoin<key>>
: key extends keyof NonEvmCoinTypeToFormat
? NonEvmCoinTypeToFormat[key]
: never;
};
export type CoinNameToFormatMap = {
[key in CoinName]: CoinTypeToFormatMap[CoinNameToTypeMap[key]];
[key in CoinName]: Prettify<
Omit<CoinTypeToFormatMap[CoinNameToTypeMap[key]], "isUnknownChain">
>;
};

export type EvmCoinMap = typeof evmCoinNameToTypeMap;
Expand All @@ -35,12 +37,16 @@ export type EvmCoinType = EvmCoinMap[EvmCoinName];
export type EvmChainId = Subtract<EvmCoinType, typeof SLIP44_MSB>;

export type GetEvmCoin<
TEvmName extends EvmCoinName,
TCoinType extends CoinNameToTypeMap[TEvmName] = CoinNameToTypeMap[TEvmName]
TCoinType extends number,
TChainId extends number = Subtract<TCoinType, typeof SLIP44_MSB>,
TCoinName extends string = TCoinType extends EvmCoinType
? CoinTypeToNameMap[`${TCoinType}`][0]
: `Unknown Chain (${TChainId})`
> = {
name: TEvmName;
name: TCoinName;
coinType: TCoinType;
evmChainId: Subtract<TCoinType, typeof SLIP44_MSB>;
evmChainId: TChainId;
isUnknownChain: TCoinType extends EvmCoinType ? false : true;
encode: EncoderFunction;
decode: DecoderFunction;
};
Expand All @@ -52,6 +58,7 @@ export type CoinParameters = {
name: string;
coinType: number;
evmChainId?: number;
isUnknownChain?: boolean;
};

export type CoinCoder = {
Expand All @@ -72,7 +79,11 @@ export type GetCoderByCoinName<TCoinName extends CoinName | string> =
TCoinName extends CoinName ? CoinNameToFormatMap[TCoinName] : Coin;

export type GetCoderByCoinType<TCoinType extends CoinType | number> =
TCoinType extends CoinType ? CoinTypeToFormatMap[TCoinType] : Coin;
TCoinType extends CoinType
? CoinTypeToFormatMap[TCoinType]
: GtOrEq<TCoinType, typeof SLIP44_MSB> extends 1
? Prettify<GetEvmCoin<TCoinType>>
: Coin;

export type ParseInt<T> = T extends `${infer N extends number}` ? N : never;

Expand Down
35 changes: 35 additions & 0 deletions src/utils/evm.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { describe, expect, test } from "bun:test";
import {
coinTypeToEvmChainId,
evmChainIdToCoinType,
isEvmCoinType,
} from "./evm.js";

describe("isEvmCoinType()", () => {
test("non evm coin type", () => {
expect(isEvmCoinType(1000)).toBeFalse();
});
test("evm coin type", () => {
expect(isEvmCoinType(2147483658)).toBeTrue();
});
});

describe("evmChainIdToCoinType()", () => {
test("normal chainId", () => {
expect(evmChainIdToCoinType(10)).toBe(2147483658);
});
test("chainId too large", () => {
expect(() => evmChainIdToCoinType(2147483648)).toThrow("Invalid chainId");
});
});

describe("coinTypeToEvmChainId()", () => {
test("non evm coin type", () => {
expect(() => coinTypeToEvmChainId(1000)).toThrow(
"Coin type is not an EVM chain"
);
});
test("evm coin type", () => {
expect(coinTypeToEvmChainId(2147483658)).toBe(10);
});
});
14 changes: 13 additions & 1 deletion src/utils/evm.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,20 @@
import type { Add, Lt, Subtract } from "ts-arithmetic";
import type { Add, GtOrEq, Lt, Subtract } from "ts-arithmetic";
import type { EvmChainId, EvmCoinType } from "../types.js";

export const SLIP44_MSB = 0x80000000;

export const isEvmCoinType = <
TCoinType extends EvmCoinType | number = EvmCoinType | number
>(
coinType: TCoinType
) =>
((coinType & SLIP44_MSB) !== 0) as GtOrEq<
TCoinType,
typeof SLIP44_MSB
> extends 1
? true
: false;

type EvmChainIdToCoinType<
TChainId extends EvmChainId | number = EvmChainId | number
> = Lt<TChainId, typeof SLIP44_MSB> extends 1
Expand Down
1 change: 1 addition & 0 deletions src/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ export {
SLIP44_MSB,
coinTypeToEvmChainId,
evmChainIdToCoinType,
isEvmCoinType,
} from "./evm.js";
export { validateFlowAddress } from "./flow.js";
export { decodeLeb128, encodeLeb128 } from "./leb128.js";
Expand Down

0 comments on commit 766db72

Please sign in to comment.