@helios-lang/effect 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/LICENSE +28 -0
  2. package/README.md +3 -0
  3. package/dist/Address.js +13 -0
  4. package/dist/Address.js.map +1 -0
  5. package/dist/Bech32.js +153 -0
  6. package/dist/Bech32.js.map +1 -0
  7. package/dist/Cbor.js +1171 -0
  8. package/dist/Cbor.js.map +1 -0
  9. package/dist/Uplc/Cek.js +3 -0
  10. package/dist/Uplc/Cek.js.map +1 -0
  11. package/dist/Uplc/Data.js +171 -0
  12. package/dist/Uplc/Data.js.map +1 -0
  13. package/dist/Uplc/DataSchema.js +118 -0
  14. package/dist/Uplc/DataSchema.js.map +1 -0
  15. package/dist/Uplc/Primitive.js +23 -0
  16. package/dist/Uplc/Primitive.js.map +1 -0
  17. package/dist/Uplc/index.js +4 -0
  18. package/dist/Uplc/index.js.map +1 -0
  19. package/dist/index.js +5 -0
  20. package/dist/index.js.map +1 -0
  21. package/dist/internal/Base32.js +201 -0
  22. package/dist/internal/Base32.js.map +1 -0
  23. package/dist/internal/BigEndian.js +56 -0
  24. package/dist/internal/BigEndian.js.map +1 -0
  25. package/dist/internal/Bits.js +300 -0
  26. package/dist/internal/Bits.js.map +1 -0
  27. package/dist/internal/Bytes.js +293 -0
  28. package/dist/internal/Bytes.js.map +1 -0
  29. package/dist/internal/Flat.js +298 -0
  30. package/dist/internal/Flat.js.map +1 -0
  31. package/dist/internal/Float.js +154 -0
  32. package/dist/internal/Float.js.map +1 -0
  33. package/dist/internal/Utf8.js +44 -0
  34. package/dist/internal/Utf8.js.map +1 -0
  35. package/eslint.config.mjs +30 -0
  36. package/package.json +36 -0
  37. package/src/Address.ts +20 -0
  38. package/src/Bech32.test.ts +117 -0
  39. package/src/Bech32.ts +198 -0
  40. package/src/Cbor.test.ts +1610 -0
  41. package/src/Cbor.ts +1704 -0
  42. package/src/Uplc/Cek.ts +92 -0
  43. package/src/Uplc/Data.ts +259 -0
  44. package/src/Uplc/DataSchema.test.ts +207 -0
  45. package/src/Uplc/DataSchema.ts +181 -0
  46. package/src/Uplc/Primitive.ts +56 -0
  47. package/src/Uplc/index.ts +3 -0
  48. package/src/index.ts +4 -0
  49. package/src/internal/Base32.test.ts +219 -0
  50. package/src/internal/Base32.ts +341 -0
  51. package/src/internal/BigEndian.test.ts +79 -0
  52. package/src/internal/BigEndian.ts +67 -0
  53. package/src/internal/Bits.test.ts +300 -0
  54. package/src/internal/Bits.ts +398 -0
  55. package/src/internal/Bytes.test.ts +369 -0
  56. package/src/internal/Bytes.ts +343 -0
  57. package/src/internal/Flat.test.ts +29 -0
  58. package/src/internal/Flat.ts +387 -0
  59. package/src/internal/Float.test.ts +51 -0
  60. package/src/internal/Float.ts +190 -0
  61. package/src/internal/Utf8.test.ts +69 -0
  62. package/src/internal/Utf8.ts +58 -0
  63. package/tsconfig.build.json +14 -0
  64. package/tsconfig.json +38 -0
@@ -0,0 +1,117 @@
1
+ import { describe, expect, it } from "bun:test"
2
+ import { Either } from "effect"
3
+ import * as Bech32 from "./Bech32.js"
4
+ import * as Utf8 from "./internal/Utf8.js"
5
+
6
+ describe("Bech32.encode", () => {
7
+ it("fails with empty human readable part", () => {
8
+ expect(() => Bech32.encode("", [])).toThrow()
9
+ })
10
+
11
+ it('returns "foo1vehk7cnpwgry9h96" for "foobar" with "foo" human-readable-part', () => {
12
+ expect(Bech32.encode("foo", Utf8.encode("foobar"))).toBe(
13
+ "foo1vehk7cnpwgry9h96"
14
+ )
15
+ })
16
+
17
+ it('returns "addr_test1wz54prcptnaullpa3zkyc8ynfddc954m9qw5v3nj7mzf2wggs2uld" for #70a9508f015cfbcffc3d88ac4c1c934b5b82d2bb281d464672f6c49539 with "addr_test" human-readable-part', () => {
18
+ expect(
19
+ Bech32.encode(
20
+ "addr_test",
21
+ "70a9508f015cfbcffc3d88ac4c1c934b5b82d2bb281d464672f6c49539"
22
+ )
23
+ ).toBe("addr_test1wz54prcptnaullpa3zkyc8ynfddc954m9qw5v3nj7mzf2wggs2uld")
24
+ })
25
+ })
26
+
27
+ describe("Bech32.decode", () => {
28
+ it("fails for empty string", () => {
29
+ expect(Bech32.decode("")._tag).toBe("Left")
30
+ })
31
+
32
+ it("fails for random string", () => {
33
+ expect(Bech32.decode("balbalbal")._tag).toBe("Left")
34
+ })
35
+
36
+ it('returns #70a9508f015cfbcffc3d88ac4c1c934b5b82d2bb281d464672f6c49539 for "addr_test1wz54prcptnaullpa3zkyc8ynfddc954m9qw5v3nj7mzf2wggs2uld"', () => {
37
+ expect(
38
+ Bech32.decode(
39
+ "addr_test1wz54prcptnaullpa3zkyc8ynfddc954m9qw5v3nj7mzf2wggs2uld"
40
+ )
41
+ ).toEqual(
42
+ Either.right({
43
+ prefix: "addr_test",
44
+ bytes: [
45
+ 0x70, 0xa9, 0x50, 0x8f, 0x01, 0x5c, 0xfb, 0xcf, 0xfc, 0x3d, 0x88,
46
+ 0xac, 0x4c, 0x1c, 0x93, 0x4b, 0x5b, 0x82, 0xd2, 0xbb, 0x28, 0x1d,
47
+ 0x46, 0x46, 0x72, 0xf6, 0xc4, 0x95, 0x39
48
+ ]
49
+ })
50
+ )
51
+ })
52
+
53
+ it("for script1agrmwv7exgffcdu27cn5xmnuhsh0p0ukuqpkhdgm800xksw7e2w", () => {
54
+ expect(
55
+ Bech32.decode(
56
+ "script1agrmwv7exgffcdu27cn5xmnuhsh0p0ukuqpkhdgm800xksw7e2w"
57
+ )
58
+ ).toEqual(
59
+ Either.right({
60
+ prefix: "script",
61
+ bytes: [
62
+ 0xea, 0x07, 0xb7, 0x33, 0xd9, 0x32, 0x12, 0x9c, 0x37, 0x8a, 0xf6,
63
+ 0x27, 0x43, 0x6e, 0x7c, 0xbc, 0x2e, 0xf0, 0xbf, 0x96, 0xe0, 0x03,
64
+ 0x6b, 0xb5, 0x1b, 0x3b, 0xde, 0x6b
65
+ ]
66
+ })
67
+ )
68
+ })
69
+ })
70
+
71
+ const testVector: [string, boolean][] = [
72
+ ["", false],
73
+ ["blablabla", false],
74
+ ["addr_test1wz54prcptnaullpa3zkyc8ynfddc954m9qw5v3nj7mzf2wggs2uld", true],
75
+ ["foo1vehk7cnpwgry9h96", true],
76
+ ["foo1vehk7cnpwgry9h97", false],
77
+ ["a12uel5l", true],
78
+ ["mm1crxm3i", false],
79
+ ["A1G7SGD8", false],
80
+ ["abcdef1qpzry9x8gf2tvdw0s3jn54khce6mua7lmqqqxw", true],
81
+ ["?1ezyfcl", true],
82
+ ["addr_test1wz54prcptnaullpa3zkyc8ynfddc954m9qw5v3nj7mzf2wggs2uld", true]
83
+ ]
84
+
85
+ describe("Bech32.isValid", () => {
86
+ testVector.forEach(([encoded, expected]) => {
87
+ it(`returns ${expected} for "${encoded}"`, () => {
88
+ expect(Bech32.isValid(encoded)).toBe(expected)
89
+ })
90
+ })
91
+ })
92
+
93
+ describe("Bech32.decode()/Bech32.encode() roundtrip", () => {
94
+ const roundtrip = (encoded: string): string => {
95
+ const decodeResult = Bech32.decode(encoded)
96
+ if (decodeResult._tag == "Left") {
97
+ // eslint-disable-next-line @typescript-eslint/only-throw-error
98
+ throw decodeResult.left
99
+ }
100
+
101
+ const { prefix, bytes } = decodeResult.right
102
+
103
+ return Bech32.encode(prefix, bytes)
104
+ }
105
+
106
+ testVector.forEach(([encoded, expected]) => {
107
+ if (expected) {
108
+ it(`ok for "${encoded}"`, () => {
109
+ expect(roundtrip(encoded)).toBe(encoded)
110
+ })
111
+ } else {
112
+ it(`fails for "${encoded}"`, () => {
113
+ expect(() => roundtrip(encoded)).toThrow()
114
+ })
115
+ }
116
+ })
117
+ })
package/src/Bech32.ts ADDED
@@ -0,0 +1,198 @@
1
+ import { Either, Encoding } from "effect"
2
+ import * as Base32 from "./internal/Base32.js"
3
+
4
+ /**
5
+ * Bech32 base32 alphabet
6
+ */
7
+ const ALPHABET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l" as const
8
+
9
+ const PAYLOAD_CODEC = /* @__PURE__ */ Base32.make({
10
+ alphabet: ALPHABET
11
+ })
12
+
13
+ /**
14
+ * Decomposes a Bech32 checksummed string (eg. a Cardano address), and returns the human readable part and the original bytes
15
+ * Throws an error if checksum is invalid.
16
+ * @param encoded
17
+ * @returns
18
+ * `prefix` part is the human-readable part, `bytes` part is a list containing the underlying bytes.
19
+ */
20
+ export function decode(
21
+ encoded: string
22
+ ): Either.Either<
23
+ { prefix: string; bytes: number[] },
24
+ Encoding.DecodeException
25
+ > {
26
+ const [prefix, payload] = split(encoded)
27
+
28
+ if (!verifySplit(prefix, payload)) {
29
+ return Either.left(
30
+ Encoding.DecodeException(encoded, "invalid bech32 encoding")
31
+ )
32
+ }
33
+
34
+ const bytes = PAYLOAD_CODEC.decode(payload.slice(0, payload.length - 6))
35
+
36
+ if (bytes._tag == "Left") {
37
+ return Either.left(bytes.left)
38
+ }
39
+
40
+ return Either.right({ prefix, bytes: Array.from(bytes.right) })
41
+ }
42
+
43
+ /**
44
+ * Creates a Bech32 checksummed string (eg. used to represent Cardano addresses).
45
+ * @param prefix
46
+ * human-readable part (eg. "addr")
47
+ * @param payload
48
+ * Hex encoded or a list of uint8 bytes
49
+ * @returns
50
+ * @throws
51
+ * If prefix is empty
52
+ */
53
+ export function encode(
54
+ prefix: string,
55
+ payload: string | number[] | Uint8Array
56
+ ): string {
57
+ if (prefix.length == 0) {
58
+ throw new Error("human-readable-part must have non-zero length")
59
+ }
60
+
61
+ payload = PAYLOAD_CODEC.encodeRaw(payload)
62
+
63
+ const chkSum = calcChecksum(prefix, payload)
64
+
65
+ return (
66
+ prefix +
67
+ "1" +
68
+ payload
69
+ .concat(chkSum)
70
+ .map((i) => ALPHABET[i])
71
+ .join("")
72
+ )
73
+ }
74
+
75
+ /**
76
+ * Verifies a Bech32 checksum. Prefix must be checked externally
77
+ * @param {string} encoded
78
+ * @returns {boolean}
79
+ */
80
+ export function isValid(encoded: string): boolean {
81
+ const [prefix, payload] = split(encoded)
82
+
83
+ return verifySplit(prefix, payload)
84
+ }
85
+
86
+ /**
87
+ * Expand human readable prefix of the bech32 encoding so it can be used in the checkSum.
88
+ * @param prefix
89
+ * @returns
90
+ */
91
+ function expandPrefix(prefix: string): number[] {
92
+ const bytes = []
93
+ for (const c of prefix) {
94
+ bytes.push(c.charCodeAt(0) >> 5)
95
+ }
96
+
97
+ bytes.push(0)
98
+
99
+ for (const c of prefix) {
100
+ bytes.push(c.charCodeAt(0) & 31)
101
+ }
102
+
103
+ return bytes
104
+ }
105
+
106
+ /**
107
+ * Split bech32 encoded string into human-readable-part and payload part.
108
+ * @param encoded
109
+ * @returns
110
+ * First item is human-readable-part, second part is payload part
111
+ */
112
+ function split(encoded: string): [string, string] {
113
+ const i = encoded.indexOf("1")
114
+
115
+ if (i == -1 || i == 0) {
116
+ return ["", encoded]
117
+ } else {
118
+ return [encoded.slice(0, i), encoded.slice(i + 1)]
119
+ }
120
+ }
121
+
122
+ /**
123
+ * Used as part of the bech32 checksum.
124
+ * @param bytes
125
+ * @returns
126
+ */
127
+ function polymod(bytes: number[]): number {
128
+ const GEN = [0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3]
129
+
130
+ let chk = 1
131
+ for (const b of bytes) {
132
+ const c = chk >> 25
133
+ chk = ((chk & 0x1fffffff) << 5) ^ b
134
+
135
+ for (let i = 0; i < 5; i++) {
136
+ if (((c >> i) & 1) != 0) {
137
+ chk ^= GEN[i]
138
+ }
139
+ }
140
+ }
141
+
142
+ return chk
143
+ }
144
+
145
+ /**
146
+ * Generate the bech32 checksum.
147
+ * @param prefix
148
+ * @param payload
149
+ * numbers between 0 and 32
150
+ * @returns
151
+ * 6 numbers between 0 and 32
152
+ */
153
+ function calcChecksum(prefix: string, payload: number[]): number[] {
154
+ const bytes = expandPrefix(prefix).concat(payload)
155
+
156
+ const chk = polymod(bytes.concat([0, 0, 0, 0, 0, 0])) ^ 1
157
+
158
+ const chkSum: number[] = []
159
+ for (let i = 0; i < 6; i++) {
160
+ chkSum.push((chk >> (5 * (5 - i))) & 31)
161
+ }
162
+
163
+ return chkSum
164
+ }
165
+
166
+ /**
167
+ * @param prefix
168
+ * @param payload
169
+ * @returns
170
+ */
171
+ function verifySplit(prefix: string, payload: string): boolean {
172
+ if (prefix.length == 0) {
173
+ return false
174
+ }
175
+
176
+ const data: number[] = []
177
+
178
+ for (const c of payload) {
179
+ const j = ALPHABET.indexOf(c)
180
+ if (j == -1) {
181
+ return false
182
+ }
183
+
184
+ data.push(j)
185
+ }
186
+
187
+ const chkSumA = data.slice(data.length - 6)
188
+
189
+ const chkSumB = calcChecksum(prefix, data.slice(0, data.length - 6))
190
+
191
+ for (let j = 0; j < 6; j++) {
192
+ if (chkSumA[j] != chkSumB[j]) {
193
+ return false
194
+ }
195
+ }
196
+
197
+ return true
198
+ }