Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support unknown evm chains #399

Merged
merged 5 commits into from
Feb 15, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
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
>(
storywithoutend marked this conversation as resolved.
Show resolved Hide resolved
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
Loading