diff --git a/packages/nns/src/icp.spec.ts b/packages/nns/src/icp.spec.ts new file mode 100644 index 00000000..0f6078a2 --- /dev/null +++ b/packages/nns/src/icp.spec.ts @@ -0,0 +1,56 @@ +import { FromStringToTokenError } from "@dfinity/utils"; +import { describe, expect, it } from "@jest/globals"; +import { ICP } from "./icp"; + +describe("ICP", () => { + it("can be initialized from a whole number string", () => { + expect(ICP.fromString("1")).toEqual(ICP.fromE8s(BigInt(100000000))); + expect(ICP.fromString("1234")).toEqual(ICP.fromE8s(BigInt(123400000000))); + expect(ICP.fromString("000001234")).toEqual( + ICP.fromE8s(BigInt(123400000000)), + ); + expect(ICP.fromString(" 1")).toEqual(ICP.fromE8s(BigInt(100000000))); + expect(ICP.fromString("1,000")).toEqual(ICP.fromE8s(BigInt(100000000000))); + expect(ICP.fromString("1'000")).toEqual(ICP.fromE8s(BigInt(100000000000))); + expect(ICP.fromString("1'000'000")).toEqual( + ICP.fromE8s(BigInt(100000000000000)), + ); + }); + + it("can be initialized from a fractional number string", () => { + expect(ICP.fromString("0.1")).toEqual(ICP.fromE8s(BigInt(10000000))); + expect(ICP.fromString("0.0001")).toEqual(ICP.fromE8s(BigInt(10000))); + expect(ICP.fromString("0.00000001")).toEqual(ICP.fromE8s(BigInt(1))); + expect(ICP.fromString("0.0000000001")).toEqual( + FromStringToTokenError.FractionalMoreThan8Decimals, + ); + expect(ICP.fromString(".01")).toEqual(ICP.fromE8s(BigInt(1000000))); + }); + + it("can be initialized from a mixed string", () => { + expect(ICP.fromString("1.1")).toEqual(ICP.fromE8s(BigInt(110000000))); + expect(ICP.fromString("1.1")).toEqual(ICP.fromE8s(BigInt(110000000))); + expect(ICP.fromString("12,345.00000001")).toEqual( + ICP.fromE8s(BigInt(1234500000001)), + ); + expect(ICP.fromString("12'345.00000001")).toEqual( + ICP.fromE8s(BigInt(1234500000001)), + ); + expect(ICP.fromString("12345.00000001")).toEqual( + ICP.fromE8s(BigInt(1234500000001)), + ); + }); + + it("returns an error on invalid formats", () => { + expect(ICP.fromString("1.1.1")).toBe(FromStringToTokenError.InvalidFormat); + expect(ICP.fromString("a")).toBe(FromStringToTokenError.InvalidFormat); + expect(ICP.fromString("3.a")).toBe(FromStringToTokenError.InvalidFormat); + expect(ICP.fromString("123asdf$#@~!")).toBe( + FromStringToTokenError.InvalidFormat, + ); + }); + + it("rejects negative numbers", () => { + expect(ICP.fromString("-1")).toBe(FromStringToTokenError.InvalidFormat); + }); +}); diff --git a/packages/nns/src/icp.ts b/packages/nns/src/icp.ts new file mode 100644 index 00000000..bdf4680d --- /dev/null +++ b/packages/nns/src/icp.ts @@ -0,0 +1,50 @@ +import type { ICPTs } from "@dfinity/nns-proto"; +import { + ICPToken, + convertStringToE8s, + type FromStringToTokenError, + type Token, +} from "@dfinity/utils"; +import { importNnsProto } from "./utils/proto.utils"; + +/** + * We don't extend to keep `fromE8s` and `fromString` as backwards compatible. + * @deprecated + */ +export class ICP { + private constructor( + private e8s: bigint, + public token: Token, + ) {} + + public static fromE8s(amount: bigint): ICP { + return new ICP(amount, ICPToken); + } + + /** + * Initialize from a string. Accepted formats: + * + * 1234567.8901 + * 1'234'567.8901 + * 1,234,567.8901 + */ + public static fromString(amount: string): ICP | FromStringToTokenError { + const e8s = convertStringToE8s(amount); + if (typeof e8s === "bigint") { + return new ICP(e8s, ICPToken); + } + return e8s; + } + + public toE8s(): bigint { + return this.e8s; + } + + public async toProto(): Promise { + const { ICPTs: ICPTsConstructor } = await importNnsProto(); + + const proto = new ICPTsConstructor(); + proto.setE8s(this.e8s.toString()); + return proto; + } +} diff --git a/packages/nns/src/index.ts b/packages/nns/src/index.ts index c6cca6f3..50a7e71b 100644 --- a/packages/nns/src/index.ts +++ b/packages/nns/src/index.ts @@ -4,6 +4,7 @@ export * from "./enums/governance.enums"; export * from "./errors/governance.errors"; export { GenesisTokenCanister } from "./genesis_token.canister"; export { GovernanceCanister } from "./governance.canister"; +export { ICP } from "./icp"; export { SnsWasmCanister } from "./sns_wasm.canister"; export * from "./types/common"; export * from "./types/governance.options";