ecash-lib 3.1.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.
- package/README.md +1 -0
- package/dist/ffi/ecash_lib_wasm_bg_browser.js +11 -11
- package/dist/ffi/ecash_lib_wasm_bg_browser.wasm +0 -0
- package/dist/ffi/ecash_lib_wasm_bg_nodejs.wasm +0 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +16 -0
- package/dist/index.js.map +1 -1
- package/dist/io/bytes.d.ts +2 -0
- package/dist/io/bytes.d.ts.map +1 -1
- package/dist/io/bytes.js +10 -2
- package/dist/io/bytes.js.map +1 -1
- package/dist/payment/asn1.d.ts +61 -0
- package/dist/payment/asn1.d.ts.map +1 -0
- package/dist/payment/asn1.js +322 -0
- package/dist/payment/asn1.js.map +1 -0
- package/dist/payment/index.d.ts +3 -0
- package/dist/payment/index.d.ts.map +1 -0
- package/dist/payment/index.js +32 -0
- package/dist/payment/index.js.map +1 -0
- package/dist/payment/x509.d.ts +11 -0
- package/dist/payment/x509.d.ts.map +1 -0
- package/dist/payment/x509.js +17 -0
- package/dist/payment/x509.js.map +1 -0
- package/dist/token/alp.d.ts +6 -0
- package/dist/token/alp.d.ts.map +1 -1
- package/dist/token/alp.js +6 -2
- package/dist/token/alp.js.map +1 -1
- package/dist/token/alp.parse.d.ts +73 -0
- package/dist/token/alp.parse.d.ts.map +1 -0
- package/dist/token/alp.parse.js +168 -0
- package/dist/token/alp.parse.js.map +1 -0
- package/dist/token/common.d.ts +18 -0
- package/dist/token/common.d.ts.map +1 -1
- package/dist/token/common.js +23 -5
- package/dist/token/common.js.map +1 -1
- package/dist/token/empp.d.ts +10 -0
- package/dist/token/empp.d.ts.map +1 -1
- package/dist/token/empp.js +38 -1
- package/dist/token/empp.js.map +1 -1
- package/dist/token/slp.d.ts +12 -0
- package/dist/token/slp.d.ts.map +1 -1
- package/dist/token/slp.js +12 -2
- package/dist/token/slp.js.map +1 -1
- package/dist/token/slp.parse.d.ts +91 -0
- package/dist/token/slp.parse.d.ts.map +1 -0
- package/dist/token/slp.parse.js +251 -0
- package/dist/token/slp.parse.js.map +1 -0
- package/package.json +2 -2
- package/src/ffi/ecash_lib_wasm_bg_browser.js +11 -11
- package/src/ffi/ecash_lib_wasm_bg_browser.wasm +0 -0
- package/src/ffi/ecash_lib_wasm_bg_nodejs.wasm +0 -0
- package/src/index.ts +4 -0
- package/src/io/bytes.ts +11 -2
- package/src/payment/asn1.ts +440 -0
- package/src/payment/index.ts +6 -0
- package/src/payment/x509.ts +15 -0
- package/src/token/alp.parse.ts +258 -0
- package/src/token/alp.ts +10 -1
- package/src/token/common.ts +28 -4
- package/src/token/empp.ts +42 -1
- package/src/token/slp.parse.ts +383 -0
- package/src/token/slp.ts +22 -1
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(
|
|
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
|
/**
|
package/src/token/common.ts
CHANGED
|
@@ -4,10 +4,34 @@
|
|
|
4
4
|
|
|
5
5
|
import { strToBytes } from '../io/str.js';
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
export const
|
|
9
|
-
export const
|
|
10
|
-
|
|
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
|
+
}
|
|
@@ -0,0 +1,383 @@
|
|
|
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 { bytesToStr } from '../io/str.js';
|
|
6
|
+
import { toHex } from '../io/hex.js';
|
|
7
|
+
import { OP_RETURN } from '../opcode.js';
|
|
8
|
+
import { isPushOp } from '../op.js';
|
|
9
|
+
import { Script, ScriptOpIter } from '../script.js';
|
|
10
|
+
import {
|
|
11
|
+
BURN_STR,
|
|
12
|
+
GENESIS_STR,
|
|
13
|
+
GenesisInfo,
|
|
14
|
+
MAX_DECIMALS,
|
|
15
|
+
MINT_STR,
|
|
16
|
+
SEND_STR,
|
|
17
|
+
TOKEN_ID_NUM_BYTES,
|
|
18
|
+
UNKNOWN_STR,
|
|
19
|
+
} from './common.js';
|
|
20
|
+
import {
|
|
21
|
+
SLP_ATOMS_NUM_BYTES,
|
|
22
|
+
SLP_FUNGIBLE,
|
|
23
|
+
SLP_GENESIS_HASH_NUM_BYTES,
|
|
24
|
+
SLP_LOKAD_ID_STR,
|
|
25
|
+
SLP_MAX_SEND_OUTPUTS,
|
|
26
|
+
SLP_MINT_VAULT,
|
|
27
|
+
SLP_MINT_VAULT_SCRIPTHASH_NUM_BYTES,
|
|
28
|
+
SLP_NFT1_CHILD,
|
|
29
|
+
SLP_NFT1_GROUP,
|
|
30
|
+
SlpTokenType,
|
|
31
|
+
} from './slp.js';
|
|
32
|
+
|
|
33
|
+
/** Parsed SLP GENESIS OP_RETURN Script */
|
|
34
|
+
export interface SlpGenesis {
|
|
35
|
+
/** "GENESIS" */
|
|
36
|
+
txType: typeof GENESIS_STR;
|
|
37
|
+
/** Token type of the token to create */
|
|
38
|
+
tokenType: SlpTokenType;
|
|
39
|
+
/** Info about the token */
|
|
40
|
+
genesisInfo: GenesisInfo;
|
|
41
|
+
/** Number of token atoms to initially mint to out_idx=1 */
|
|
42
|
+
initialAtoms: bigint;
|
|
43
|
+
/** Output index to send the mint baton to, or undefined if none */
|
|
44
|
+
mintBatonOutIdx?: number;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Parsed SLP MINT (token type 0x01 and 0x81) OP_RETURN Script.
|
|
49
|
+
* Note: Token type 0x41 has no mint batons.
|
|
50
|
+
**/
|
|
51
|
+
export interface SlpMintClassic {
|
|
52
|
+
/** "MINT" */
|
|
53
|
+
txType: typeof MINT_STR;
|
|
54
|
+
/** Token type of the token to mint */
|
|
55
|
+
tokenType: typeof SLP_FUNGIBLE | typeof SLP_NFT1_GROUP;
|
|
56
|
+
/** Token ID of the token to mint */
|
|
57
|
+
tokenId: string;
|
|
58
|
+
/** Number of token atoms to mint to out_idx=1 */
|
|
59
|
+
additionalAtoms: bigint;
|
|
60
|
+
/** Output index to send the mint baton to, or undefined to destroy it */
|
|
61
|
+
mintBatonOutIdx?: number;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Parsed SLP MINT (token type 0x02) OP_RETURN Script */
|
|
65
|
+
export interface SlpMintVault {
|
|
66
|
+
/** "MINT" */
|
|
67
|
+
txType: typeof MINT_STR;
|
|
68
|
+
/** Token type of the token to mint (0x02) */
|
|
69
|
+
tokenType: typeof SLP_MINT_VAULT;
|
|
70
|
+
/** Token ID of the token to mint */
|
|
71
|
+
tokenId: string;
|
|
72
|
+
/** Array of the number of token atoms to mint to the outputs at 1 to N */
|
|
73
|
+
additionalAtomsArray: bigint[];
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** Parsed SLP MINT OP_RETURN Script */
|
|
77
|
+
export type SlpMint = SlpMintClassic | SlpMintVault;
|
|
78
|
+
|
|
79
|
+
/** Parsed SLP SEND OP_RETURN Script */
|
|
80
|
+
export interface SlpSend {
|
|
81
|
+
/** "SEND" */
|
|
82
|
+
txType: typeof SEND_STR;
|
|
83
|
+
/** Token type of the token to send */
|
|
84
|
+
tokenType: SlpTokenType;
|
|
85
|
+
/** Token ID of the token to send */
|
|
86
|
+
tokenId: string;
|
|
87
|
+
/** Array of the number of token atoms to send to the outputs at 1 to N */
|
|
88
|
+
sendAtomsArray: bigint[];
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** Parsed SLP BURN OP_RETURN Script */
|
|
92
|
+
export interface SlpBurn {
|
|
93
|
+
/** "BURN" */
|
|
94
|
+
txType: typeof BURN_STR;
|
|
95
|
+
/** Token type of the token to burn */
|
|
96
|
+
tokenType: SlpTokenType;
|
|
97
|
+
/** Token ID of the token to burn */
|
|
98
|
+
tokenId: string;
|
|
99
|
+
/** How many tokens should be burned */
|
|
100
|
+
burnAtoms: bigint;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** New unknown SLP token type or tx type */
|
|
104
|
+
export interface SlpUnknown {
|
|
105
|
+
/** Placeholder for unknown token type or tx type */
|
|
106
|
+
txType: typeof UNKNOWN_STR;
|
|
107
|
+
/** Token type number */
|
|
108
|
+
tokenType: number;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** Parsed SLP OP_RETURN Script */
|
|
112
|
+
export type SlpData = SlpGenesis | SlpMint | SlpSend | SlpBurn | SlpUnknown;
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Parse the given SLP OP_RETURN Script.
|
|
116
|
+
*
|
|
117
|
+
* For data that's clearly not SLP it will return `undefined`.
|
|
118
|
+
* For example, if the OP_RETURN or LOKAD ID is missing.
|
|
119
|
+
*
|
|
120
|
+
* For an unknown token type, it'll return SlpUnknown.
|
|
121
|
+
*
|
|
122
|
+
* For a known token type, it'll parse the remaining data, or throw an error if
|
|
123
|
+
* the format is invalid or if there's an unknown tx type.
|
|
124
|
+
*
|
|
125
|
+
* This behavior mirrors that of Chronik for consistency.
|
|
126
|
+
**/
|
|
127
|
+
export function parseSlp(opreturnScript: Script): SlpData | undefined {
|
|
128
|
+
const ops = opreturnScript.ops();
|
|
129
|
+
const opreturnOp = ops.next();
|
|
130
|
+
|
|
131
|
+
// Return undefined if not OP_RETURN
|
|
132
|
+
if (
|
|
133
|
+
opreturnOp === undefined ||
|
|
134
|
+
isPushOp(opreturnOp) ||
|
|
135
|
+
opreturnOp !== OP_RETURN
|
|
136
|
+
) {
|
|
137
|
+
return undefined;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Return undefined if LOKAD ID is not "SLP\0"
|
|
141
|
+
const lokadId = ops.next();
|
|
142
|
+
if (lokadId === undefined || !isPushOp(lokadId)) {
|
|
143
|
+
return undefined;
|
|
144
|
+
}
|
|
145
|
+
if (bytesToStr(lokadId.data) !== SLP_LOKAD_ID_STR) {
|
|
146
|
+
return undefined;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Parse token type
|
|
150
|
+
const tokenTypeBytes = nextBytes(ops);
|
|
151
|
+
if (tokenTypeBytes === undefined) {
|
|
152
|
+
throw new Error('Missing tokenType');
|
|
153
|
+
}
|
|
154
|
+
if (tokenTypeBytes.length !== 1) {
|
|
155
|
+
throw new Error('tokenType must be exactly 1 byte');
|
|
156
|
+
}
|
|
157
|
+
const tokenType = tokenTypeBytes[0];
|
|
158
|
+
if (
|
|
159
|
+
tokenType !== SLP_FUNGIBLE &&
|
|
160
|
+
tokenType !== SLP_MINT_VAULT &&
|
|
161
|
+
tokenType !== SLP_NFT1_GROUP &&
|
|
162
|
+
tokenType !== SLP_NFT1_CHILD
|
|
163
|
+
) {
|
|
164
|
+
return {
|
|
165
|
+
txType: UNKNOWN_STR,
|
|
166
|
+
tokenType,
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Parse tx type (GENESIS, MINT, SEND, BURN)
|
|
171
|
+
const txTypeBytes = nextBytes(ops);
|
|
172
|
+
if (txTypeBytes === undefined) {
|
|
173
|
+
throw new Error('Missing txType');
|
|
174
|
+
}
|
|
175
|
+
const txType = bytesToStr(txTypeBytes);
|
|
176
|
+
|
|
177
|
+
// Handle tx type specific parsing.
|
|
178
|
+
// Advances the `ops` Script iterator
|
|
179
|
+
switch (txType) {
|
|
180
|
+
case GENESIS_STR:
|
|
181
|
+
return nextGenesis(ops, tokenType);
|
|
182
|
+
case MINT_STR:
|
|
183
|
+
return nextMint(ops, tokenType);
|
|
184
|
+
case SEND_STR:
|
|
185
|
+
return nextSend(ops, tokenType);
|
|
186
|
+
case BURN_STR:
|
|
187
|
+
return nextBurn(ops, tokenType);
|
|
188
|
+
default:
|
|
189
|
+
throw new Error('Unknown txType');
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function nextGenesis(ops: ScriptOpIter, tokenType: SlpTokenType): SlpGenesis {
|
|
194
|
+
// Parse genesis info
|
|
195
|
+
const tokenTicker = bytesToStr(nextBytesRequired(ops, 'tokenTicker'));
|
|
196
|
+
const tokenName = bytesToStr(nextBytesRequired(ops, 'tokenName'));
|
|
197
|
+
const url = bytesToStr(nextBytesRequired(ops, 'url'));
|
|
198
|
+
const hash = nextBytesRequired(ops, 'hash');
|
|
199
|
+
if (hash.length !== 0 && hash.length !== SLP_GENESIS_HASH_NUM_BYTES) {
|
|
200
|
+
throw new Error(
|
|
201
|
+
`hash must be either 0 or ${SLP_GENESIS_HASH_NUM_BYTES} bytes`,
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
const decimalsBytes = nextBytesRequired(ops, 'decimals');
|
|
205
|
+
if (decimalsBytes.length !== 1) {
|
|
206
|
+
throw new Error('decimals must be exactly 1 byte');
|
|
207
|
+
}
|
|
208
|
+
const decimals = decimalsBytes[0];
|
|
209
|
+
if (decimals > MAX_DECIMALS) {
|
|
210
|
+
throw new Error(`decimals must be at most ${MAX_DECIMALS}`);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Parse mint data
|
|
214
|
+
let mintVaultScripthash: string | undefined = undefined;
|
|
215
|
+
let mintBatonOutIdx: number | undefined = undefined;
|
|
216
|
+
if (tokenType === SLP_MINT_VAULT) {
|
|
217
|
+
const scripthashBytes = nextBytesRequired(ops, 'mintVaultScripthash');
|
|
218
|
+
if (scripthashBytes.length !== SLP_MINT_VAULT_SCRIPTHASH_NUM_BYTES) {
|
|
219
|
+
throw new Error(
|
|
220
|
+
`mintVaultScripthash must be exactly ${SLP_MINT_VAULT_SCRIPTHASH_NUM_BYTES} ` +
|
|
221
|
+
'bytes long',
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
mintVaultScripthash = toHex(scripthashBytes);
|
|
225
|
+
} else {
|
|
226
|
+
mintBatonOutIdx = nextMintOutIdx(ops, tokenType);
|
|
227
|
+
}
|
|
228
|
+
const initialAtoms = parseSlpAtoms(nextBytesRequired(ops, 'initialAtoms'));
|
|
229
|
+
nextEnd(ops, 'GENESIS');
|
|
230
|
+
return {
|
|
231
|
+
txType: GENESIS_STR,
|
|
232
|
+
tokenType,
|
|
233
|
+
genesisInfo: {
|
|
234
|
+
tokenTicker,
|
|
235
|
+
tokenName,
|
|
236
|
+
url,
|
|
237
|
+
hash: hash.length !== 0 ? toHex(hash) : undefined,
|
|
238
|
+
mintVaultScripthash,
|
|
239
|
+
decimals,
|
|
240
|
+
},
|
|
241
|
+
initialAtoms,
|
|
242
|
+
mintBatonOutIdx,
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function nextMint(ops: ScriptOpIter, tokenType: SlpTokenType): SlpMint {
|
|
247
|
+
const tokenId = nextTokenId(ops);
|
|
248
|
+
if (tokenType === SLP_MINT_VAULT) {
|
|
249
|
+
const additionalAtomsArray = nextSlpAtomsArray(ops);
|
|
250
|
+
return {
|
|
251
|
+
txType: MINT_STR,
|
|
252
|
+
tokenType,
|
|
253
|
+
tokenId,
|
|
254
|
+
additionalAtomsArray,
|
|
255
|
+
};
|
|
256
|
+
} else if (tokenType === SLP_NFT1_CHILD) {
|
|
257
|
+
throw new Error('SLP_NFT1_CHILD cannot have MINT transactions');
|
|
258
|
+
} else {
|
|
259
|
+
const mintBatonOutIdx = nextMintOutIdx(ops, tokenType);
|
|
260
|
+
const additionalAtoms = parseSlpAtoms(
|
|
261
|
+
nextBytesRequired(ops, 'additionalAtoms'),
|
|
262
|
+
);
|
|
263
|
+
nextEnd(ops, 'MINT');
|
|
264
|
+
return {
|
|
265
|
+
txType: MINT_STR,
|
|
266
|
+
tokenType,
|
|
267
|
+
tokenId,
|
|
268
|
+
additionalAtoms,
|
|
269
|
+
mintBatonOutIdx,
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function nextSend(ops: ScriptOpIter, tokenType: SlpTokenType): SlpSend {
|
|
275
|
+
const tokenId = nextTokenId(ops);
|
|
276
|
+
const sendAtomsArray = nextSlpAtomsArray(ops);
|
|
277
|
+
return {
|
|
278
|
+
txType: SEND_STR,
|
|
279
|
+
tokenType,
|
|
280
|
+
tokenId,
|
|
281
|
+
sendAtomsArray,
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function nextBurn(ops: ScriptOpIter, tokenType: SlpTokenType): SlpBurn {
|
|
286
|
+
const tokenId = nextTokenId(ops);
|
|
287
|
+
const burnAtoms = parseSlpAtoms(nextBytesRequired(ops, 'burnAtoms'));
|
|
288
|
+
nextEnd(ops, 'BURN');
|
|
289
|
+
return {
|
|
290
|
+
txType: BURN_STR,
|
|
291
|
+
tokenType,
|
|
292
|
+
tokenId,
|
|
293
|
+
burnAtoms,
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function nextBytes(iter: ScriptOpIter): Uint8Array | undefined {
|
|
298
|
+
const op = iter.next();
|
|
299
|
+
if (op === undefined) {
|
|
300
|
+
return undefined;
|
|
301
|
+
}
|
|
302
|
+
if (!isPushOp(op)) {
|
|
303
|
+
throw new Error('SLP only supports push-ops');
|
|
304
|
+
}
|
|
305
|
+
return op.data;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
function nextBytesRequired(iter: ScriptOpIter, name: string): Uint8Array {
|
|
309
|
+
const bytes = nextBytes(iter);
|
|
310
|
+
if (bytes === undefined) {
|
|
311
|
+
throw new Error('Missing ' + name);
|
|
312
|
+
}
|
|
313
|
+
return bytes;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
function nextMintOutIdx(
|
|
317
|
+
iter: ScriptOpIter,
|
|
318
|
+
tokenType: number,
|
|
319
|
+
): number | undefined {
|
|
320
|
+
const outIdxBytes = nextBytesRequired(iter, 'mintBatonOutIdx');
|
|
321
|
+
if (outIdxBytes.length > 1) {
|
|
322
|
+
throw new Error('mintBatonOutIdx must be at most 1 byte long');
|
|
323
|
+
}
|
|
324
|
+
if (outIdxBytes.length === 1) {
|
|
325
|
+
if (tokenType === SLP_NFT1_CHILD) {
|
|
326
|
+
throw new Error('SLP_NFT1_CHILD cannot have a mint baton');
|
|
327
|
+
}
|
|
328
|
+
const mintBatonOutIdx = outIdxBytes[0];
|
|
329
|
+
if (mintBatonOutIdx < 2) {
|
|
330
|
+
throw new Error('mintBatonOutIdx must be at least 2');
|
|
331
|
+
}
|
|
332
|
+
return mintBatonOutIdx;
|
|
333
|
+
}
|
|
334
|
+
return undefined;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
function nextTokenId(iter: ScriptOpIter): string {
|
|
338
|
+
const tokenIdBytes = nextBytesRequired(iter, 'tokenId');
|
|
339
|
+
if (tokenIdBytes.length !== TOKEN_ID_NUM_BYTES) {
|
|
340
|
+
throw new Error(
|
|
341
|
+
`tokenId must be exactly ${TOKEN_ID_NUM_BYTES} bytes long`,
|
|
342
|
+
);
|
|
343
|
+
}
|
|
344
|
+
// Note: SLP token ID endianness is big-endian
|
|
345
|
+
return toHex(tokenIdBytes);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
function nextSlpAtomsArray(iter: ScriptOpIter): bigint[] {
|
|
349
|
+
const atomsArray = [];
|
|
350
|
+
let bytes: Uint8Array | undefined = undefined;
|
|
351
|
+
while ((bytes = nextBytes(iter)) !== undefined) {
|
|
352
|
+
atomsArray.push(parseSlpAtoms(bytes));
|
|
353
|
+
}
|
|
354
|
+
if (atomsArray.length === 0) {
|
|
355
|
+
throw new Error('atomsArray cannot be empty');
|
|
356
|
+
}
|
|
357
|
+
if (atomsArray.length > SLP_MAX_SEND_OUTPUTS) {
|
|
358
|
+
throw new Error(
|
|
359
|
+
`atomsArray can at most be ${SLP_MAX_SEND_OUTPUTS} items long`,
|
|
360
|
+
);
|
|
361
|
+
}
|
|
362
|
+
return atomsArray;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
function nextEnd(iter: ScriptOpIter, txType: string) {
|
|
366
|
+
if (iter.next() !== undefined) {
|
|
367
|
+
throw new Error(`Superfluous ${txType} bytes`);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
function parseSlpAtoms(bytes: Uint8Array): bigint {
|
|
372
|
+
if (bytes.length !== SLP_ATOMS_NUM_BYTES) {
|
|
373
|
+
throw new Error(
|
|
374
|
+
`SLP atoms must be exactly ${SLP_ATOMS_NUM_BYTES} bytes long`,
|
|
375
|
+
);
|
|
376
|
+
}
|
|
377
|
+
let number = 0n;
|
|
378
|
+
for (let i = 0; i < SLP_ATOMS_NUM_BYTES; ++i) {
|
|
379
|
+
number <<= 8n;
|
|
380
|
+
number |= BigInt(bytes[i]);
|
|
381
|
+
}
|
|
382
|
+
return number;
|
|
383
|
+
}
|
package/src/token/slp.ts
CHANGED
|
@@ -10,7 +10,9 @@ import { Script } from '../script.js';
|
|
|
10
10
|
import { BURN, GENESIS, GenesisInfo, MINT, SEND } from './common.js';
|
|
11
11
|
|
|
12
12
|
/** LOKAD ID for SLP */
|
|
13
|
-
export const
|
|
13
|
+
export const SLP_LOKAD_ID_STR = 'SLP\0';
|
|
14
|
+
/** LOKAD ID for SLP */
|
|
15
|
+
export const SLP_LOKAD_ID = strToBytes(SLP_LOKAD_ID_STR);
|
|
14
16
|
|
|
15
17
|
/** SLP fungible token type number */
|
|
16
18
|
export const SLP_FUNGIBLE = 1;
|
|
@@ -21,6 +23,25 @@ export const SLP_NFT1_CHILD = 0x41;
|
|
|
21
23
|
/** SLP NFT1 Group token type number */
|
|
22
24
|
export const SLP_NFT1_GROUP = 0x81;
|
|
23
25
|
|
|
26
|
+
/** How many bytes the GENESIS `hash` field must have (or 0) */
|
|
27
|
+
export const SLP_GENESIS_HASH_NUM_BYTES = 32;
|
|
28
|
+
|
|
29
|
+
/** How many bytes the GENESIS `mintVaultScripthash` field must have */
|
|
30
|
+
export const SLP_MINT_VAULT_SCRIPTHASH_NUM_BYTES = 20;
|
|
31
|
+
|
|
32
|
+
/** How many outputs a SEND can specify at most */
|
|
33
|
+
export const SLP_MAX_SEND_OUTPUTS = 19;
|
|
34
|
+
|
|
35
|
+
/** How many bytes every atoms amount has */
|
|
36
|
+
export const SLP_ATOMS_NUM_BYTES = 8;
|
|
37
|
+
|
|
38
|
+
/** Supported SLP token types */
|
|
39
|
+
export type SlpTokenType =
|
|
40
|
+
| typeof SLP_FUNGIBLE
|
|
41
|
+
| typeof SLP_MINT_VAULT
|
|
42
|
+
| typeof SLP_NFT1_CHILD
|
|
43
|
+
| typeof SLP_NFT1_GROUP;
|
|
44
|
+
|
|
24
45
|
/** Build an SLP GENESIS OP_RETURN, creating a new SLP token */
|
|
25
46
|
export function slpGenesis(
|
|
26
47
|
tokenType: number,
|