diff --git a/packages/faucet/src/pathbuilder.spec.ts b/packages/faucet/src/pathbuilder.spec.ts new file mode 100644 index 0000000000..f48ee97189 --- /dev/null +++ b/packages/faucet/src/pathbuilder.spec.ts @@ -0,0 +1,64 @@ +import { Slip10RawIndex } from "@cosmjs/crypto"; +import { makeCosmoshubPath } from "@cosmjs/proto-signing"; + +import { makePathBuilder, PathBuilder } from "./pathbuilder"; + +describe("pathbuilder", () => { + describe("PathBuilder", () => { + it("is compatible to makeCosmoshubPath", () => { + const _builder: PathBuilder = makeCosmoshubPath; + }); + }); + + describe("makePathBuilder", () => { + it("works", () => { + const hub = makePathBuilder("m/44'/118'/0'/0/a"); + expect(hub(0)).toEqual([ + Slip10RawIndex.hardened(44), + Slip10RawIndex.hardened(118), + Slip10RawIndex.hardened(0), + Slip10RawIndex.normal(0), + Slip10RawIndex.normal(0), + ]); + expect(hub(25)).toEqual([ + Slip10RawIndex.hardened(44), + Slip10RawIndex.hardened(118), + Slip10RawIndex.hardened(0), + Slip10RawIndex.normal(0), + Slip10RawIndex.normal(25), + ]); + + const stellar = makePathBuilder("m/44'/148'/a'"); + expect(stellar(0)).toEqual([ + Slip10RawIndex.hardened(44), + Slip10RawIndex.hardened(148), + Slip10RawIndex.hardened(0), + ]); + expect(stellar(42)).toEqual([ + Slip10RawIndex.hardened(44), + Slip10RawIndex.hardened(148), + Slip10RawIndex.hardened(42), + ]); + }); + + it("throws for invalid patterns", () => { + // No `a` + expect(() => makePathBuilder("m/44'/118'/0'/0/30")).toThrowError( + /Missing account index variable `a` in pattern./i, + ); + + // Two `a` + expect(() => makePathBuilder("m/44'/118'/a'/0/a")).toThrowError( + /More than one account index variable `a` in pattern/i, + ); + + // Stray character + expect(() => makePathBuilder("m/44'?/118'/0'/0/a")).toThrowError( + /Syntax error while reading path component/i, + ); + + // Missing m/ + expect(() => makePathBuilder("44'/118'/0'/0/a")).toThrowError(/Path string must start with 'm'/i); + }); + }); +}); diff --git a/packages/faucet/src/pathbuilder.ts b/packages/faucet/src/pathbuilder.ts new file mode 100644 index 0000000000..f108acaf79 --- /dev/null +++ b/packages/faucet/src/pathbuilder.ts @@ -0,0 +1,26 @@ +import { HdPath, stringToPath } from "@cosmjs/crypto"; + +export type PathBuilder = (account_index: number) => HdPath; + +/** + * Insert a BIP32 path that contains a valiable `a` for the numeric account index. + * This variable will be replaces when the path builder is used. + * + * @param pattern, e.g. m/44'/148'/a' for Stellar paths + */ +export function makePathBuilder(pattern: string): PathBuilder { + if (pattern.indexOf("a") === -1) throw new Error("Missing account index variable `a` in pattern."); + if (pattern.indexOf("a") !== pattern.lastIndexOf("a")) { + throw new Error("More than one account index variable `a` in pattern."); + } + + const builder: PathBuilder = function (a: number): HdPath { + const path = pattern.replace("a", a.toString()); + return stringToPath(path); + }; + + // test builder + const _path = builder(0); + + return builder; +}