ecash-lib 3.0.0 → 3.2.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 (100) hide show
  1. package/README.md +2 -0
  2. package/dist/ecc.d.ts +18 -1
  3. package/dist/ecc.d.ts.map +1 -1
  4. package/dist/ecc.js +29 -0
  5. package/dist/ecc.js.map +1 -1
  6. package/dist/ffi/ecash_lib_wasm_bg_browser.js +1743 -1679
  7. package/dist/ffi/ecash_lib_wasm_bg_browser.wasm +0 -0
  8. package/dist/ffi/ecash_lib_wasm_bg_browser.wasm.d.ts +13 -11
  9. package/dist/ffi/ecash_lib_wasm_bg_nodejs.wasm +0 -0
  10. package/dist/ffi/ecash_lib_wasm_bg_nodejs.wasm.d.ts +13 -11
  11. package/dist/ffi/ecash_lib_wasm_browser.d.ts +27 -11
  12. package/dist/ffi/ecash_lib_wasm_browser.js +64 -14
  13. package/dist/ffi/ecash_lib_wasm_nodejs.d.ts +14 -0
  14. package/dist/ffi/ecash_lib_wasm_nodejs.js +64 -14
  15. package/dist/hdwallet.d.ts.map +1 -1
  16. package/dist/hdwallet.js +1 -0
  17. package/dist/hdwallet.js.map +1 -1
  18. package/dist/index.d.ts +4 -0
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/index.js +17 -0
  21. package/dist/index.js.map +1 -1
  22. package/dist/io/bytes.d.ts +2 -0
  23. package/dist/io/bytes.d.ts.map +1 -1
  24. package/dist/io/bytes.js +10 -2
  25. package/dist/io/bytes.js.map +1 -1
  26. package/dist/messages.d.ts +32 -0
  27. package/dist/messages.d.ts.map +1 -0
  28. package/dist/messages.js +102 -0
  29. package/dist/messages.js.map +1 -0
  30. package/dist/op.d.ts.map +1 -1
  31. package/dist/op.js +4 -3
  32. package/dist/op.js.map +1 -1
  33. package/dist/payment/asn1.d.ts +61 -0
  34. package/dist/payment/asn1.d.ts.map +1 -0
  35. package/dist/payment/asn1.js +322 -0
  36. package/dist/payment/asn1.js.map +1 -0
  37. package/dist/payment/index.d.ts +3 -0
  38. package/dist/payment/index.d.ts.map +1 -0
  39. package/dist/payment/index.js +32 -0
  40. package/dist/payment/index.js.map +1 -0
  41. package/dist/payment/x509.d.ts +11 -0
  42. package/dist/payment/x509.d.ts.map +1 -0
  43. package/dist/payment/x509.js +17 -0
  44. package/dist/payment/x509.js.map +1 -0
  45. package/dist/sigHashType.js.map +1 -1
  46. package/dist/token/alp.d.ts +6 -0
  47. package/dist/token/alp.d.ts.map +1 -1
  48. package/dist/token/alp.js +6 -2
  49. package/dist/token/alp.js.map +1 -1
  50. package/dist/token/alp.parse.d.ts +73 -0
  51. package/dist/token/alp.parse.d.ts.map +1 -0
  52. package/dist/token/alp.parse.js +168 -0
  53. package/dist/token/alp.parse.js.map +1 -0
  54. package/dist/token/common.d.ts +18 -0
  55. package/dist/token/common.d.ts.map +1 -1
  56. package/dist/token/common.js +23 -5
  57. package/dist/token/common.js.map +1 -1
  58. package/dist/token/empp.d.ts +10 -0
  59. package/dist/token/empp.d.ts.map +1 -1
  60. package/dist/token/empp.js +38 -1
  61. package/dist/token/empp.js.map +1 -1
  62. package/dist/token/slp.d.ts +12 -0
  63. package/dist/token/slp.d.ts.map +1 -1
  64. package/dist/token/slp.js +12 -2
  65. package/dist/token/slp.js.map +1 -1
  66. package/dist/token/slp.parse.d.ts +91 -0
  67. package/dist/token/slp.parse.d.ts.map +1 -0
  68. package/dist/token/slp.parse.js +251 -0
  69. package/dist/token/slp.parse.js.map +1 -0
  70. package/dist/txBuilder.js +1 -1
  71. package/dist/txBuilder.js.map +1 -1
  72. package/package.json +5 -7
  73. package/src/ecc.ts +54 -2
  74. package/src/ffi/ecash_lib_wasm_bg_browser.js +1743 -1679
  75. package/src/ffi/ecash_lib_wasm_bg_browser.wasm +0 -0
  76. package/src/ffi/ecash_lib_wasm_bg_browser.wasm.d.ts +13 -11
  77. package/src/ffi/ecash_lib_wasm_bg_nodejs.wasm +0 -0
  78. package/src/ffi/ecash_lib_wasm_bg_nodejs.wasm.d.ts +13 -11
  79. package/src/ffi/ecash_lib_wasm_browser.d.ts +27 -11
  80. package/src/ffi/ecash_lib_wasm_browser.js +64 -14
  81. package/src/ffi/ecash_lib_wasm_nodejs.d.ts +14 -0
  82. package/src/ffi/ecash_lib_wasm_nodejs.js +64 -14
  83. package/src/hdwallet.ts +1 -0
  84. package/src/index.ts +5 -0
  85. package/src/io/bytes.ts +11 -2
  86. package/src/messages.ts +125 -0
  87. package/src/op.ts +5 -3
  88. package/src/payment/asn1.ts +440 -0
  89. package/src/payment/index.ts +6 -0
  90. package/src/payment/x509.ts +15 -0
  91. package/src/sigHashType.ts +2 -2
  92. package/src/token/alp.parse.ts +258 -0
  93. package/src/token/alp.ts +10 -1
  94. package/src/token/common.ts +28 -4
  95. package/src/token/empp.ts +42 -1
  96. package/src/token/slp.parse.ts +383 -0
  97. package/src/token/slp.ts +22 -1
  98. package/src/txBuilder.ts +1 -1
  99. package/.eslintignore +0 -8
  100. package/eslint.config.js +0 -16
@@ -0,0 +1,258 @@
1
+ // Copyright (c) 2025 The Bitcoin developers
2
+ // Distributed under the MIT software license, see the accompanying
3
+ // file COPYING or http://www.opensource.org/licenses/mit-license.php.
4
+
5
+ import { toHex, toHexRev } from '../io/hex.js';
6
+ import { bytesToStr } from '../io/str.js';
7
+ import { Bytes } from '../io/bytes.js';
8
+ import {
9
+ ALP_LOKAD_ID,
10
+ ALP_MAX_SIZE,
11
+ ALP_STANDARD,
12
+ AlpTokenType,
13
+ MintData,
14
+ } from './alp.js';
15
+ import {
16
+ BURN_STR,
17
+ GENESIS_STR,
18
+ GenesisInfo,
19
+ MINT_STR,
20
+ SEND_STR,
21
+ TOKEN_ID_NUM_BYTES,
22
+ UNKNOWN_STR,
23
+ } from './common.js';
24
+
25
+ /**
26
+ * Parsed ALP GENESIS pushdata.
27
+ * Note: This must always be the first eMPP pushdata.
28
+ **/
29
+ export interface AlpGenesis {
30
+ /** "GENESIS" */
31
+ txType: typeof GENESIS_STR;
32
+ /** Token type of the token to create */
33
+ tokenType: AlpTokenType;
34
+ /** Info about the token */
35
+ genesisInfo: GenesisInfo;
36
+ /** Which tokens/mint baton to create initially */
37
+ mintData: MintData;
38
+ }
39
+
40
+ /** Parsed ALP MINT pushdata. */
41
+ export interface AlpMint {
42
+ /** "MINT" */
43
+ txType: typeof MINT_STR;
44
+ /** Token type of the token to mint */
45
+ tokenType: AlpTokenType;
46
+ /** Token ID of the token to mint */
47
+ tokenId: string;
48
+ /** Which tokens/mint baton to create */
49
+ mintData: MintData;
50
+ }
51
+
52
+ /** Parsed ALP SEND pushdata. */
53
+ export interface AlpSend {
54
+ /** "SEND" */
55
+ txType: typeof SEND_STR;
56
+ /** Token type of the token to send */
57
+ tokenType: AlpTokenType;
58
+ /** Token ID of the token to send */
59
+ tokenId: string;
60
+ /** Array of the number of token atoms to send to the outputs at 1 to N */
61
+ sendAtomsArray: bigint[];
62
+ }
63
+
64
+ /** Parsed ALP BURN pushdata */
65
+ export interface AlpBurn {
66
+ /** "BURN" */
67
+ txType: typeof BURN_STR;
68
+ /** Token type of the token to burn */
69
+ tokenId: string;
70
+ /** Token ID of the token to burn */
71
+ tokenType: AlpTokenType;
72
+ /** How many tokens should be burned */
73
+ burnAtoms: bigint;
74
+ }
75
+
76
+ /** New unknown ALP token type */
77
+ export interface AlpUnknown {
78
+ /** Placeholder for unknown token type or tx type */
79
+ txType: typeof UNKNOWN_STR;
80
+ /** Token type number */
81
+ tokenType: number;
82
+ }
83
+
84
+ /** Parsed ALP pushdata */
85
+ export type AlpData = AlpGenesis | AlpMint | AlpSend | AlpBurn | AlpUnknown;
86
+
87
+ /**
88
+ * Parse the given ALP pushdata. eMPP allows multiple pushdata per OP_RETURN.
89
+ *
90
+ * For data that's clearly not ALP (i.e. doesn't start with LOKAD ID "SLP2"),
91
+ * it will return `undefined`.
92
+ *
93
+ * For an unknown token type, it'll return AlpUnknown.
94
+ *
95
+ * For a known token type, it'll parse the remaining data, or throw an error if
96
+ * the format is invalid or if there's an unknown tx type.
97
+ *
98
+ * This behavior mirrors that of Chronik for consistency.
99
+ **/
100
+ export function parseAlp(pushdata: Uint8Array): AlpData | undefined {
101
+ // Must have at least 4 bytes for the LOKAD ID
102
+ if (pushdata.length < ALP_LOKAD_ID.length) {
103
+ return undefined;
104
+ }
105
+
106
+ const bytes = new Bytes(pushdata);
107
+
108
+ // If the pushdata doesn't start with "SLP2" (ALP's LOKAD ID), return undefined
109
+ const lokadId = bytesToStr(bytes.readBytes(ALP_LOKAD_ID.length));
110
+ if (lokadId != bytesToStr(ALP_LOKAD_ID)) {
111
+ return undefined;
112
+ }
113
+
114
+ // Return UNKNOWN for unknown token types (only "STANDARD" known so far)
115
+ const tokenType = readU8(bytes, 'tokenType');
116
+ if (tokenType != ALP_STANDARD) {
117
+ return {
118
+ txType: 'UNKNOWN',
119
+ tokenType,
120
+ };
121
+ }
122
+
123
+ // Parse tx type (GENESIS, MINT, SEND, BURN)
124
+ const txType = bytesToStr(readVarBytes(bytes, 'txType'));
125
+
126
+ // Handle tx type specific parsing
127
+ switch (txType) {
128
+ case GENESIS_STR:
129
+ return readGenesis(bytes, tokenType);
130
+ case MINT_STR:
131
+ return readMint(bytes, tokenType);
132
+ case SEND_STR:
133
+ return readSend(bytes, tokenType);
134
+ case BURN_STR:
135
+ return readBurn(bytes, tokenType);
136
+ default:
137
+ throw new Error('Unknown txType');
138
+ }
139
+ }
140
+
141
+ function readGenesis(bytes: Bytes, tokenType: AlpTokenType): AlpGenesis {
142
+ const tokenTicker = bytesToStr(readVarBytes(bytes, 'tokenTicker'));
143
+ const tokenName = bytesToStr(readVarBytes(bytes, 'tokenName'));
144
+ const url = bytesToStr(readVarBytes(bytes, 'url'));
145
+ const data = readVarBytes(bytes, 'data');
146
+ const authPubkey = readVarBytes(bytes, 'authPubkey');
147
+ const decimals = readU8(bytes, 'decimals');
148
+ const mintData = readMintData(bytes);
149
+ ensureEnd(bytes, 'GENESIS');
150
+ return {
151
+ txType: GENESIS_STR,
152
+ tokenType,
153
+ genesisInfo: {
154
+ tokenTicker,
155
+ tokenName,
156
+ url,
157
+ data: toHex(data),
158
+ authPubkey: toHex(authPubkey),
159
+ decimals,
160
+ },
161
+ mintData,
162
+ };
163
+ }
164
+
165
+ function readMint(bytes: Bytes, tokenType: AlpTokenType): AlpMint {
166
+ const tokenId = readTokenId(bytes);
167
+ const mintData = readMintData(bytes);
168
+ ensureEnd(bytes, 'MINT');
169
+ return {
170
+ txType: MINT_STR,
171
+ tokenType,
172
+ tokenId,
173
+ mintData,
174
+ };
175
+ }
176
+
177
+ function readSend(bytes: Bytes, tokenType: AlpTokenType): AlpSend {
178
+ const tokenId = readTokenId(bytes);
179
+ const sendAtomsArray = readAtomsArray(bytes, 'sendAtomsArray');
180
+ ensureEnd(bytes, 'SEND');
181
+ return {
182
+ txType: SEND_STR,
183
+ tokenType,
184
+ tokenId,
185
+ sendAtomsArray,
186
+ };
187
+ }
188
+
189
+ function readBurn(bytes: Bytes, tokenType: AlpTokenType): AlpBurn {
190
+ const tokenId = readTokenId(bytes);
191
+ const burnAtoms = readU48(bytes, 'burnAtoms');
192
+ ensureEnd(bytes, 'BURN');
193
+ return {
194
+ txType: BURN_STR,
195
+ tokenType,
196
+ tokenId,
197
+ burnAtoms,
198
+ };
199
+ }
200
+
201
+ function readU8(bytes: Bytes, name: string): number {
202
+ if (bytes.idx >= bytes.data.length) {
203
+ throw new Error(`Missing ${name}`);
204
+ }
205
+ return bytes.readU8();
206
+ }
207
+
208
+ function readU48(bytes: Bytes, name: string): bigint {
209
+ if (bytes.idx >= bytes.data.length) {
210
+ throw new Error(`Missing ${name}`);
211
+ }
212
+ return bytes.readU48();
213
+ }
214
+
215
+ function readTokenId(bytes: Bytes): string {
216
+ // Note: ALP token ID endianness is little-endian (like in prevOut)
217
+ return toHexRev(bytes.readBytes(TOKEN_ID_NUM_BYTES));
218
+ }
219
+
220
+ function readSize(bytes: Bytes, name: string): number {
221
+ const size = readU8(bytes, name);
222
+ if (size > ALP_MAX_SIZE) {
223
+ throw new Error(`Size must be between 0 and ${ALP_MAX_SIZE}`);
224
+ }
225
+ return size;
226
+ }
227
+
228
+ function readVarBytes(bytes: Bytes, name: string): Uint8Array {
229
+ const numBytes = readSize(bytes, name);
230
+ return bytes.readBytes(numBytes);
231
+ }
232
+
233
+ function readAtomsArray(bytes: Bytes, name: string): bigint[] {
234
+ const numAtoms = readSize(bytes, name);
235
+ const atomsArray = [];
236
+ for (let i = 0; i < numAtoms; ++i) {
237
+ atomsArray.push(bytes.readU48());
238
+ }
239
+ return atomsArray;
240
+ }
241
+
242
+ function readMintData(bytes: Bytes): MintData {
243
+ const atomsArray = readAtomsArray(bytes, 'atomsArray');
244
+ const numBatons = readU8(bytes, 'numBatons');
245
+ if (numBatons > ALP_MAX_SIZE) {
246
+ throw new Error(`numBatons must be between 0 and ${ALP_MAX_SIZE}`);
247
+ }
248
+ return {
249
+ atomsArray,
250
+ numBatons,
251
+ };
252
+ }
253
+
254
+ function ensureEnd(bytes: Bytes, txType: string) {
255
+ if (bytes.idx < bytes.data.length) {
256
+ throw new Error(`Superfluous ${txType} bytes`);
257
+ }
258
+ }
package/src/token/alp.ts CHANGED
@@ -9,12 +9,21 @@ import { WriterBytes } from '../io/writerbytes.js';
9
9
  import { WriterLength } from '../io/writerlength.js';
10
10
  import { BURN, GENESIS, GenesisInfo, MINT, SEND } from './common.js';
11
11
 
12
+ /** LOKAD ID for ALP as string */
13
+ export const ALP_LOKAD_ID_STR = 'SLP2';
14
+
12
15
  /** LOKAD ID for ALP */
13
- export const ALP_LOKAD_ID = strToBytes('SLP2');
16
+ export const ALP_LOKAD_ID = strToBytes(ALP_LOKAD_ID_STR);
14
17
 
15
18
  /** ALP standard token type number */
16
19
  export const ALP_STANDARD = 0;
17
20
 
21
+ /** Supported ALP token types */
22
+ export type AlpTokenType = typeof ALP_STANDARD;
23
+
24
+ /** ALP limits lengths/sizes to this number, e.g. the number of outputs */
25
+ export const ALP_MAX_SIZE = 127;
26
+
18
27
  /** Mint data specifying mint amounts (in atoms) and batons of a GENESIS/MINT tx */
19
28
  export interface MintData {
20
29
  /**
@@ -4,10 +4,34 @@
4
4
 
5
5
  import { strToBytes } from '../io/str.js';
6
6
 
7
- export const GENESIS = strToBytes('GENESIS');
8
- export const MINT = strToBytes('MINT');
9
- export const SEND = strToBytes('SEND');
10
- export const BURN = strToBytes('BURN');
7
+ /** GENESIS tx type: Creates a new token ID */
8
+ export const GENESIS_STR = 'GENESIS';
9
+ export const GENESIS = strToBytes(GENESIS_STR);
10
+
11
+ /** MINT tx type: Mints more of a token ID */
12
+ export const MINT_STR = 'MINT';
13
+ export const MINT = strToBytes(MINT_STR);
14
+
15
+ /** SEND tx type: Moves existing tokens to different outputs */
16
+ export const SEND_STR = 'SEND';
17
+ export const SEND = strToBytes(SEND_STR);
18
+
19
+ /** BURN tx type: Destroys existing tokens */
20
+ export const BURN_STR = 'BURN';
21
+ export const BURN = strToBytes(BURN_STR);
22
+
23
+ /**
24
+ * UNKNOWN: Placeholder for unknown token types.
25
+ * Note: These may hold valuable tokens, but which aren't recognized.
26
+ * They should be excluded from UTXO selection.
27
+ **/
28
+ export const UNKNOWN_STR = 'UNKNOWN';
29
+
30
+ /** Number of bytes in a token ID */
31
+ export const TOKEN_ID_NUM_BYTES = 32;
32
+
33
+ /** How many decimals a token can have at most (SLP/ALP) */
34
+ export const MAX_DECIMALS = 9;
11
35
 
12
36
  /** Genesis info found in GENESIS txs of tokens */
13
37
  export interface GenesisInfo {
package/src/token/empp.ts CHANGED
@@ -2,7 +2,7 @@
2
2
  // Distributed under the MIT software license, see the accompanying
3
3
  // file COPYING or http://www.opensource.org/licenses/mit-license.php.
4
4
 
5
- import { Op, pushBytesOp } from '../op.js';
5
+ import { Op, isPushOp, pushBytesOp } from '../op.js';
6
6
  import { OP_PUSHDATA1, OP_RESERVED, OP_RETURN } from '../opcode.js';
7
7
  import { Script } from '../script.js';
8
8
 
@@ -27,3 +27,44 @@ function pushdataOpEmpp(pushdata: Uint8Array): Op {
27
27
  }
28
28
  return pushBytesOp(pushdata);
29
29
  }
30
+
31
+ /**
32
+ * Parse a script for EMPP push(es)
33
+ *
34
+ * EMPP may encode multiple pushdatas in a single OP_RETURN script
35
+ *
36
+ * input script is a valid EMPP OP_RETURN => returns an array of EMPP pushdata(s)
37
+ * input script is not an EMPP OP_RETURN => returns undefined
38
+ * input script is an invalid EMPP OP_RETURN => throws
39
+ */
40
+ export function parseEmppScript(script: Script): Uint8Array[] | undefined {
41
+ const ops = script.ops();
42
+ const opreturnOp = ops.next();
43
+ if (
44
+ opreturnOp === undefined ||
45
+ isPushOp(opreturnOp) ||
46
+ opreturnOp !== OP_RETURN
47
+ ) {
48
+ return undefined;
49
+ }
50
+ const opreservedOp = ops.next();
51
+ if (
52
+ opreservedOp === undefined ||
53
+ isPushOp(opreservedOp) ||
54
+ opreservedOp !== OP_RESERVED
55
+ ) {
56
+ return undefined;
57
+ }
58
+ const pushdata: Uint8Array[] = [];
59
+ let op: Op | undefined = undefined;
60
+ while ((op = ops.next()) !== undefined) {
61
+ if (!isPushOp(op)) {
62
+ throw new Error('eMPP allows only push ops');
63
+ }
64
+ if (op.data.length === 0) {
65
+ throw new Error("eMPP doesn't allow empty pushdata");
66
+ }
67
+ pushdata.push(op.data);
68
+ }
69
+ return pushdata;
70
+ }