Skip to content

Commit 766db72

Browse files
authored
Merge pull request #399 from ensdomains/feat/unnamed-evm-coins
feat: support unknown evm chains
2 parents 455a334 + 1b0e0f1 commit 766db72

File tree

8 files changed

+106
-19
lines changed

8 files changed

+106
-19
lines changed

src/async.test.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,23 @@ test("evm coin name", async () => {
2727
expect(coder.coinType).toBe(2147483658);
2828
expect(coder.name).toBe("op");
2929
expect(coder.evmChainId).toBe(10);
30+
expect("isUnknownChain" in coder).toBeFalse();
3031
});
3132

3233
test("evm coin type", async () => {
3334
const coder = await getCoderByCoinTypeAsync(2147483658);
3435
expect(coder.coinType).toBe(2147483658);
3536
expect(coder.name).toBe("op");
3637
expect(coder.evmChainId).toBe(10);
38+
expect(coder.isUnknownChain).toBeFalse();
39+
});
40+
41+
test("unknown evm coin type", async () => {
42+
const coder = await getCoderByCoinTypeAsync(2147483659);
43+
expect(coder.coinType).toBe(2147483659);
44+
expect(coder.name).toBe("Unknown Chain (11)");
45+
expect(coder.evmChainId).toBe(11);
46+
expect(coder.isUnknownChain).toBeTrue();
3747
});
3848

3949
const nonEvmCoinNames = Object.keys(nonEvmCoinNameToTypeMap);

src/async.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -46,24 +46,24 @@ export const getCoderByCoinTypeAsync = async <
4646
const names =
4747
coinTypeToNameMap[String(coinType) as keyof typeof coinTypeToNameMap];
4848

49-
if (!names) throw new Error(`Unsupported coin type: ${coinType}`);
50-
51-
const [name] = names;
52-
5349
if (coinType >= SLIP44_MSB) {
5450
// EVM coin
5551
const evmChainId = coinTypeToEvmChainId(coinType);
52+
const isUnknownChain = !names;
53+
const name = isUnknownChain ? `Unknown Chain (${evmChainId})` : names[0];
5654
return {
5755
name,
5856
coinType: coinType as EvmCoinType,
5957
evmChainId,
58+
isUnknownChain,
6059
encode: eth.encode,
6160
decode: eth.decode,
6261
} as GetCoderByCoinType<TCoinType>;
6362
}
64-
const mod = await import(`./coin/${name}`);
6563

64+
if (!names) throw new Error(`Unsupported coin type: ${coinType}`);
65+
const [name] = names;
66+
const mod = await import(`./coin/${name}`);
6667
if (!mod) throw new Error(`Failed to load coin: ${name}`);
67-
6868
return mod[name];
6969
};

src/index.test.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ test("evm coin name", () => {
3131
expect(coder.coinType).toBe(2147483658);
3232
expect(coder.name).toBe("op");
3333
expect(coder.evmChainId).toBe(10);
34+
expect("isUnknownChain" in coder).toBeFalse();
3435
expect(coder.encode).toBeFunction();
3536
expect(coder.decode).toBeFunction();
3637
});
@@ -40,6 +41,17 @@ test("evm coin type", () => {
4041
expect(coder.coinType).toBe(2147483658);
4142
expect(coder.name).toBe("op");
4243
expect(coder.evmChainId).toBe(10);
44+
expect(coder.isUnknownChain).toBeFalse();
45+
expect(coder.encode).toBeFunction();
46+
expect(coder.decode).toBeFunction();
47+
});
48+
49+
test("unknown evm coin type", () => {
50+
const coder = getCoderByCoinType(2147483659);
51+
expect(coder.coinType).toBe(2147483659);
52+
expect(coder.name).toBe("Unknown Chain (11)");
53+
expect(coder.evmChainId).toBe(11);
54+
expect(coder.isUnknownChain).toBeTrue();
4355
expect(coder.encode).toBeFunction();
4456
expect(coder.decode).toBeFunction();
4557
});

src/index.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -72,22 +72,28 @@ export const getCoderByCoinType = <
7272
): GetCoderByCoinType<TCoinType> => {
7373
const names =
7474
coinTypeToNameMap[String(coinType) as keyof typeof coinTypeToNameMap];
75-
if (!names) {
76-
throw new Error(`Unsupported coin type: ${coinType}`);
77-
}
78-
const [name] = names;
75+
// https://docs.ens.domains/ens-improvement-proposals/ensip-11-evmchain-address-resolution
76+
7977
if (coinType >= SLIP44_MSB) {
8078
// EVM coin
8179
const evmChainId = coinTypeToEvmChainId(coinType);
80+
const isUnknownChain = !names;
81+
const name = isUnknownChain ? `Unknown Chain (${evmChainId})` : names[0]; // name is derivable
8282
const ethFormat = formats["eth"];
8383
return {
8484
name,
8585
coinType: coinType as EvmCoinType,
8686
evmChainId,
87+
isUnknownChain,
8788
encode: ethFormat.encode,
8889
decode: ethFormat.decode,
8990
} as GetCoderByCoinType<TCoinType>;
9091
}
92+
93+
if (!names) {
94+
throw new Error(`Unsupported coin type: ${coinType}`);
95+
}
96+
const [name] = names;
9197
const format = formats[name as keyof typeof formats];
9298
return format as GetCoderByCoinType<TCoinType>;
9399
};

src/types.ts

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { Subtract } from "ts-arithmetic";
1+
import type { GtOrEq, Subtract } from "ts-arithmetic";
22
import type * as formats from "./coins.js";
33
import type {
44
coinNameToTypeMap,
@@ -20,13 +20,15 @@ type NonEvmCoinTypeToFormat = {
2020
};
2121
export type CoinTypeToFormatMap = {
2222
[key in CoinType]: key extends EvmCoinType
23-
? Prettify<GetEvmCoin<CoinTypeToNameMap[`${key}`][0]>>
23+
? Prettify<GetEvmCoin<key>>
2424
: key extends keyof NonEvmCoinTypeToFormat
2525
? NonEvmCoinTypeToFormat[key]
2626
: never;
2727
};
2828
export type CoinNameToFormatMap = {
29-
[key in CoinName]: CoinTypeToFormatMap[CoinNameToTypeMap[key]];
29+
[key in CoinName]: Prettify<
30+
Omit<CoinTypeToFormatMap[CoinNameToTypeMap[key]], "isUnknownChain">
31+
>;
3032
};
3133

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

3739
export type GetEvmCoin<
38-
TEvmName extends EvmCoinName,
39-
TCoinType extends CoinNameToTypeMap[TEvmName] = CoinNameToTypeMap[TEvmName]
40+
TCoinType extends number,
41+
TChainId extends number = Subtract<TCoinType, typeof SLIP44_MSB>,
42+
TCoinName extends string = TCoinType extends EvmCoinType
43+
? CoinTypeToNameMap[`${TCoinType}`][0]
44+
: `Unknown Chain (${TChainId})`
4045
> = {
41-
name: TEvmName;
46+
name: TCoinName;
4247
coinType: TCoinType;
43-
evmChainId: Subtract<TCoinType, typeof SLIP44_MSB>;
48+
evmChainId: TChainId;
49+
isUnknownChain: TCoinType extends EvmCoinType ? false : true;
4450
encode: EncoderFunction;
4551
decode: DecoderFunction;
4652
};
@@ -52,6 +58,7 @@ export type CoinParameters = {
5258
name: string;
5359
coinType: number;
5460
evmChainId?: number;
61+
isUnknownChain?: boolean;
5562
};
5663

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

7481
export type GetCoderByCoinType<TCoinType extends CoinType | number> =
75-
TCoinType extends CoinType ? CoinTypeToFormatMap[TCoinType] : Coin;
82+
TCoinType extends CoinType
83+
? CoinTypeToFormatMap[TCoinType]
84+
: GtOrEq<TCoinType, typeof SLIP44_MSB> extends 1
85+
? Prettify<GetEvmCoin<TCoinType>>
86+
: Coin;
7687

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

src/utils/evm.test.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { describe, expect, test } from "bun:test";
2+
import {
3+
coinTypeToEvmChainId,
4+
evmChainIdToCoinType,
5+
isEvmCoinType,
6+
} from "./evm.js";
7+
8+
describe("isEvmCoinType()", () => {
9+
test("non evm coin type", () => {
10+
expect(isEvmCoinType(1000)).toBeFalse();
11+
});
12+
test("evm coin type", () => {
13+
expect(isEvmCoinType(2147483658)).toBeTrue();
14+
});
15+
});
16+
17+
describe("evmChainIdToCoinType()", () => {
18+
test("normal chainId", () => {
19+
expect(evmChainIdToCoinType(10)).toBe(2147483658);
20+
});
21+
test("chainId too large", () => {
22+
expect(() => evmChainIdToCoinType(2147483648)).toThrow("Invalid chainId");
23+
});
24+
});
25+
26+
describe("coinTypeToEvmChainId()", () => {
27+
test("non evm coin type", () => {
28+
expect(() => coinTypeToEvmChainId(1000)).toThrow(
29+
"Coin type is not an EVM chain"
30+
);
31+
});
32+
test("evm coin type", () => {
33+
expect(coinTypeToEvmChainId(2147483658)).toBe(10);
34+
});
35+
});

src/utils/evm.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,20 @@
1-
import type { Add, Lt, Subtract } from "ts-arithmetic";
1+
import type { Add, GtOrEq, Lt, Subtract } from "ts-arithmetic";
22
import type { EvmChainId, EvmCoinType } from "../types.js";
33

44
export const SLIP44_MSB = 0x80000000;
55

6+
export const isEvmCoinType = <
7+
TCoinType extends EvmCoinType | number = EvmCoinType | number
8+
>(
9+
coinType: TCoinType
10+
) =>
11+
((coinType & SLIP44_MSB) !== 0) as GtOrEq<
12+
TCoinType,
13+
typeof SLIP44_MSB
14+
> extends 1
15+
? true
16+
: false;
17+
618
type EvmChainIdToCoinType<
719
TChainId extends EvmChainId | number = EvmChainId | number
820
> = Lt<TChainId, typeof SLIP44_MSB> extends 1

src/utils/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ export {
4141
SLIP44_MSB,
4242
coinTypeToEvmChainId,
4343
evmChainIdToCoinType,
44+
isEvmCoinType,
4445
} from "./evm.js";
4546
export { validateFlowAddress } from "./flow.js";
4647
export { decodeLeb128, encodeLeb128 } from "./leb128.js";

0 commit comments

Comments
 (0)