Skip to content

Commit

Permalink
Merge pull request #393 from ensdomains/rewrite/evm-update
Browse files Browse the repository at this point in the history
remove evm chains + fix incorrect coinTypes
  • Loading branch information
TateB authored Feb 1, 2024
2 parents 08dc8b8 + 7c5f4b1 commit 8151b1a
Show file tree
Hide file tree
Showing 180 changed files with 1,042 additions and 1,181 deletions.
23 changes: 22 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,27 @@ const encodedAddress = btcCoder.encode(decodedAddress);
// 1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa
```

### Hex String Encoding

If the data you are encoding is a hex string rather than `Uint8Array`, you can use the included `hexToBytes()` to convert it to the correct type.

```ts
import { getCoderByCoinName } from "@ensdomains/address-encoder";
import { hexToBytes } from "@ensdomains/address-encoder/utils";

const btcCoder = getCoderByCoinName("btc");

// Convert hex encoded data to bytes
const dataAsBytes = hexToBytes(
"0x76a91462e907b15cbf27d5425399ebf6f0fb50ebb88f1888ac"
);
// Uint8Array(25) [ 118, 169, 20, 98, 233, 7, 177, 92, 191, 39, 213, 66, 83, 153, 235, 246, 240, 251, 80, 235, 184, 143, 24, 136, 172 ]

// Pass bytes to encoder
const encodedAddress = btcCoder.encode(decodedAddress);
// 1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa
```

## Supported Cryptocurrencies

To view all the supported cryptocurrencies of this library, see [here](https://github.com/ensdomains/address-encoder/blob/master/docs/supported-cryptocurrencies.md).
Expand All @@ -45,7 +66,7 @@ To view all the supported cryptocurrencies of this library, see [here](https://g
```ts
import { getCoderByCoinNameAsync } from "@ensdomains/address-encoder/async";

const btcCoder = await getCoderByCoinName("btc");
const btcCoder = await getCoderByCoinNameAsync("btc");
```

### Individual Coin Imports
Expand Down
Binary file modified bun.lockb
Binary file not shown.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@
"bun-types": "1.0.22",
"fs-extra": "^11.1.1",
"mitata": "^0.1.6",
"ts-arithmetic": "^0.1.1",
"typescript": "^5.1.6"
},
"dependencies": {
Expand Down
92 changes: 92 additions & 0 deletions scripts/addEvmCoin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import * as readline from "readline";
import { coinTypeToEvmChainId } from "../src/utils/evm.js";

const SLIP44_MSB = 0x80000000;
const evmChainIdToCoinType = (chainId: number) => {
if (chainId >= SLIP44_MSB) throw new Error("Invalid chainId");
return (SLIP44_MSB | chainId) >>> 0;
};

const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
const questionAsync = (question: string) =>
new Promise<string>((resolve) => {
rl.question(question, (answer) => {
resolve(answer);
});
});

let newCoins: [string, [string, string]][] = [];

async function addEvmCoin() {
const coinSymbol_ = await questionAsync("Coin Symbol: ");
const coinName_ = await questionAsync("Coin Name: ");
const chainId = await questionAsync("Chain ID: ").then((ans) =>
parseInt(ans)
);

const coinName = coinName_.trim();
const coinSymbol = coinSymbol_.toLowerCase().trim();

newCoins.push([`${evmChainIdToCoinType(chainId)}`, [coinSymbol, coinName]]);

const runAgain = await questionAsync("Add another coin? (y/n): ").then(
(ans) => ans.toLowerCase() === "y"
);

if (runAgain) return addEvmCoin();
}

await addEvmCoin();

console.log("Adding new coins:", newCoins.map(([, [c]]) => c).join(", "));

const { evmCoinTypeToNameMap, nonEvmCoinTypeToNameMap } = await import(
"../src/consts/coinTypeToNameMap.js"
);

const evmCoinsWithNew = [
...Object.entries(evmCoinTypeToNameMap),
...newCoins,
].sort(([a], [b]) => parseInt(a) - parseInt(b));

const evmCoinTypeToNameMapString = `export const evmCoinTypeToNameMap = Object.freeze({
${evmCoinsWithNew
.map(
([
coinType,
[coinSymbol, coinName],
]) => ` /* Chain ID: ${coinTypeToEvmChainId(parseInt(coinType))} */
"${coinType}": ["${coinSymbol}", "${coinName}"],`
)
.join("\n")}
} as const);`;

const nonEvmCoinTypeToNameMapString = `export const nonEvmCoinTypeToNameMap = Object.freeze({
${Object.entries(nonEvmCoinTypeToNameMap)
.map(
([coinType, [coinSymbol, coinName]]) =>
` "${coinType}": ["${coinSymbol}", "${coinName}"],`
)
.join("\n")}
} as const);`;

const coinTypeToNameMapString = `export const coinTypeToNameMap = Object.freeze({
...nonEvmCoinTypeToNameMap,
...evmCoinTypeToNameMap,
} as const);`;

const toWrite = `${evmCoinTypeToNameMapString}
${nonEvmCoinTypeToNameMapString}
${coinTypeToNameMapString}
`;

await Bun.write("./src/consts/coinTypeToNameMap.ts", toWrite);

console.log("Done!");

rl.close();
66 changes: 66 additions & 0 deletions src/async.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
import { expect, test } from "bun:test";
import { getCoderByCoinNameAsync, getCoderByCoinTypeAsync } from "./async.js";
import {
evmCoinNameToTypeMap,
nonEvmCoinNameToTypeMap,
} from "./consts/coinNameToTypeMap.js";
import {
evmCoinTypeToNameMap,
nonEvmCoinTypeToNameMap,
} from "./consts/coinTypeToNameMap.js";
import { coinTypeToEvmChainId } from "./utils/evm.js";

test("coin name", async () => {
const coder = await getCoderByCoinNameAsync("btc");
Expand All @@ -12,3 +21,60 @@ test("coin type", async () => {
expect(coder.coinType).toBe(0);
expect(coder.name).toBe("btc");
});

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

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);
});

const nonEvmCoinNames = Object.keys(nonEvmCoinNameToTypeMap);
const evmCoinNames = Object.keys(evmCoinNameToTypeMap);

test.each(nonEvmCoinNames)(
'getCoderByCoinNameAsync("%s")',
async (coinName) => {
const coder = await getCoderByCoinNameAsync(coinName);
expect(coder.name).toBe(coinName);
expect(coder.coinType).toBe(nonEvmCoinNameToTypeMap[coinName]);
expect(coder.encode).toBeFunction();
expect(coder.decode).toBeFunction();
}
);

test.each(evmCoinNames)('getCoderByCoinNameAsync("%s")', async (coinName) => {
const coder = await getCoderByCoinNameAsync(coinName);
expect(coder.name).toBe(coinName);
expect(coder.coinType).toBe(evmCoinNameToTypeMap[coinName]);
expect(coder.evmChainId).toBe(coinTypeToEvmChainId(coder.coinType));
expect(coder.encode).toBeFunction();
expect(coder.decode).toBeFunction();
});

const nonEvmCoinTypes = Object.values(nonEvmCoinNameToTypeMap);
const evmCoinTypes = Object.values(evmCoinNameToTypeMap);

test.each(nonEvmCoinTypes)("getCoderByCoinTypeAsync(%d)", async (coinType) => {
const coder = await getCoderByCoinTypeAsync(coinType);
expect(coder.name).toBe(nonEvmCoinTypeToNameMap[coinType][0]);
expect(coder.coinType).toBe(coinType);
expect(coder.encode).toBeFunction();
expect(coder.decode).toBeFunction();
});

test.each(evmCoinTypes)("getCoderByCoinTypeAsync(%d)", async (coinType) => {
const coder = await getCoderByCoinTypeAsync(coinType);
expect(coder.name).toBe(evmCoinTypeToNameMap[coinType][0]);
expect(coder.coinType).toBe(coinType);
expect(coder.evmChainId).toBe(coinTypeToEvmChainId(coinType));
expect(coder.encode).toBeFunction();
expect(coder.decode).toBeFunction();
});
69 changes: 50 additions & 19 deletions src/async.ts
Original file line number Diff line number Diff line change
@@ -1,38 +1,69 @@
import { coinTypeMap } from "./consts/coinTypeMap.js";
import { eth } from "./coins.js";
import { coinNameToTypeMap } from "./consts/coinNameToTypeMap.js";
import { coinTypeToNameMap } from "./consts/coinTypeToNameMap.js";
import type {
Coin,
CoinName,
CoinType,
CoinTypeInvertedReference,
Formats,
EvmCoinName,
EvmCoinType,
GetCoderByCoinName,
GetCoderByCoinType,
} from "./types.js";
import { SLIP44_MSB, coinTypeToEvmChainId } from "./utils/evm.js";

export const getCoderByCoinNameAsync = async <
TCoinName extends CoinName | string = string
TCoinName extends CoinName | string = CoinName | string
>(
name: TCoinName
): Promise<TCoinName extends CoinName ? Formats[TCoinName] : Coin> => {
const mod = await import(`./coin/${name}`);
if (!mod) {
throw new Error(`Unsupported coin: ${name}`);
): Promise<GetCoderByCoinName<TCoinName>> => {
const coinType = coinNameToTypeMap[name as CoinName];

if (coinType === undefined) throw new Error(`Unsupported coin: ${name}`);

if (coinType >= SLIP44_MSB) {
// EVM coin
const evmChainId = coinTypeToEvmChainId(coinType);
return {
name: name as EvmCoinName,
coinType,
evmChainId,
encode: eth.encode,
decode: eth.decode,
} as GetCoderByCoinName<TCoinName>;
}
const mod = await import(`./coin/${name}`);

if (!mod) throw new Error(`Failed to load coin: ${name}`);

return mod[name];
};

export const getCoderByCoinTypeAsync = async <
TCoinType extends CoinType | number = number
TCoinType extends CoinType | number = CoinType | number
>(
coinType: TCoinType
): Promise<
TCoinType extends CoinType ? CoinTypeInvertedReference[TCoinType] : Coin
> => {
const name = coinTypeMap[String(coinType) as keyof typeof coinTypeMap];
if (!name) {
throw new Error(`Unsupported coin type: ${coinType}`);
): Promise<GetCoderByCoinType<TCoinType>> => {
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);
return {
name,
coinType: coinType as EvmCoinType,
evmChainId,
encode: eth.encode,
decode: eth.decode,
} as GetCoderByCoinType<TCoinType>;
}
const mod = await import(`./coin/${name}`);
if (!mod) {
throw new Error(`Unsupported coin: ${name}`);
}

if (!mod) throw new Error(`Failed to load coin: ${name}`);

return mod[name];
};
15 changes: 15 additions & 0 deletions src/coders.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { expect, test } from "bun:test";
import * as coders from "./coders.js";
import { nonEvmCoinNameToTypeMap } from "./consts/coinNameToTypeMap.js";

const coinNames = Object.keys(nonEvmCoinNameToTypeMap);

const capitalise = (s: string) => s[0].toUpperCase() + s.slice(1);

test.each(coinNames)("coders.ts exports - %s", (coinName) => {
const coderSuffix = `${capitalise(coinName)}Address`;
const encoder = coders[`encode${coderSuffix}`];
const decoder = coders[`decode${coderSuffix}`];
expect(encoder).toBeFunction();
expect(decoder).toBeFunction();
});
Loading

0 comments on commit 8151b1a

Please sign in to comment.