@bsv/templates 1.8.0 → 1.9.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/dist/cjs/mod.js +7 -1
- package/dist/cjs/mod.js.map +1 -1
- package/dist/cjs/package.json +1 -1
- package/dist/cjs/src/Bsv21Token.js +194 -0
- package/dist/cjs/src/Bsv21Token.js.map +1 -0
- package/dist/cjs/src/DstasToken.js +66 -0
- package/dist/cjs/src/DstasToken.js.map +1 -0
- package/dist/cjs/src/StasToken.js +111 -0
- package/dist/cjs/src/StasToken.js.map +1 -0
- package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
- package/dist/esm/mod.js +3 -0
- package/dist/esm/mod.js.map +1 -1
- package/dist/esm/src/Bsv21Token.js +191 -0
- package/dist/esm/src/Bsv21Token.js.map +1 -0
- package/dist/esm/src/DstasToken.js +62 -0
- package/dist/esm/src/DstasToken.js.map +1 -0
- package/dist/esm/src/StasToken.js +106 -0
- package/dist/esm/src/StasToken.js.map +1 -0
- package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
- package/dist/types/mod.d.ts +6 -0
- package/dist/types/mod.d.ts.map +1 -1
- package/dist/types/src/Bsv21Token.d.ts +26 -0
- package/dist/types/src/Bsv21Token.d.ts.map +1 -0
- package/dist/types/src/DstasToken.d.ts +46 -0
- package/dist/types/src/DstasToken.d.ts.map +1 -0
- package/dist/types/src/MandalaAdmin.d.ts +1 -1
- package/dist/types/src/MandalaAdmin.d.ts.map +1 -1
- package/dist/types/src/StasToken.d.ts +40 -0
- package/dist/types/src/StasToken.d.ts.map +1 -0
- package/dist/types/tsconfig.types.tsbuildinfo +1 -1
- package/mod.ts +6 -0
- package/package.json +1 -1
- package/src/Bsv21Token.ts +197 -0
- package/src/DstasToken.ts +105 -0
- package/src/MandalaAdmin.ts +1 -1
- package/src/StasToken.ts +136 -0
- package/src/__tests/Bsv21Token.test.ts +56 -0
- package/src/__tests/DstasToken.test.ts +33 -0
- package/src/__tests/StasToken.test.ts +44 -0
- package/src/__tests/dstas-fixtures.ts +6 -0
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import { LockingScript, Utils } from '@bsv/sdk'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Bsv21Token — decoder for BSV-21 (1Sat ordinals-style fungible token)
|
|
5
|
+
* locking scripts. The token is an ord-inscription envelope carrying a
|
|
6
|
+
* BSV-20 JSON payload, followed by a standard P2PKH owner lock:
|
|
7
|
+
*
|
|
8
|
+
* OP_FALSE OP_IF "ord" OP_1 "application/bsv-20" OP_0 <json> OP_ENDIF
|
|
9
|
+
* OP_DUP OP_HASH160 <owner_pkh:20> OP_EQUALVERIFY OP_CHECKSIG
|
|
10
|
+
*
|
|
11
|
+
* JSON: {"p":"bsv-20","op":"transfer"|"deploy+mint","id":"<txid_vout>","amt":"<int>",...}
|
|
12
|
+
*
|
|
13
|
+
* BSV-21 amounts are divisible bigints carried as strings; ownership is plain
|
|
14
|
+
* P2PKH. Decode-only — building transfers is the wallet's job (see the 1sat
|
|
15
|
+
* inscription builder). Mirrors the wallet's `parseBsv21LockingScript`.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
const ORD_TAG_HEX = '6f7264' // "ord"
|
|
19
|
+
const CONTENT_TYPE = 'application/bsv-20'
|
|
20
|
+
const OP_FALSE_HEX = '00'
|
|
21
|
+
const OP_IF_HEX = '63'
|
|
22
|
+
const OP_ENDIF_HEX = '68'
|
|
23
|
+
const OP_DUP_HEX = '76'
|
|
24
|
+
const OP_HASH160_HEX = 'a9'
|
|
25
|
+
const OP_EQUALVERIFY_HEX = '88'
|
|
26
|
+
const OP_CHECKSIG_HEX = 'ac'
|
|
27
|
+
const PKH_PUSH_LEN_HEX = '14'
|
|
28
|
+
|
|
29
|
+
export interface Bsv21TokenDecoded {
|
|
30
|
+
/** Token id `<txid>_<vout>` of the deploy+mint (empty for the mint output itself). */
|
|
31
|
+
id: string
|
|
32
|
+
/** Raw token amount as a stringified bigint. */
|
|
33
|
+
amt: string
|
|
34
|
+
/** Decimals (deploy+mint only). */
|
|
35
|
+
dec?: number
|
|
36
|
+
/** Symbol / ticker. */
|
|
37
|
+
sym?: string
|
|
38
|
+
/** Icon outpoint / URL. */
|
|
39
|
+
icon?: string
|
|
40
|
+
/** True when this is the deploy+mint output (no id in payload). */
|
|
41
|
+
isMint: boolean
|
|
42
|
+
/** Trailing P2PKH owner hash160 (hex). */
|
|
43
|
+
ownerHash160: string
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
class HexReader {
|
|
47
|
+
pos = 0
|
|
48
|
+
constructor (public readonly hex: string) {}
|
|
49
|
+
readByteHex (): string | null {
|
|
50
|
+
if (this.pos + 2 > this.hex.length) return null
|
|
51
|
+
const b = this.hex.substring(this.pos, this.pos + 2)
|
|
52
|
+
this.pos += 2
|
|
53
|
+
return b
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
readBytesHex (n: number): string | null {
|
|
57
|
+
if (this.pos + n * 2 > this.hex.length) return null
|
|
58
|
+
const out = this.hex.substring(this.pos, this.pos + n * 2)
|
|
59
|
+
this.pos += n * 2
|
|
60
|
+
return out
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
readPushHex (): string | null {
|
|
64
|
+
const op = this.readByteHex()
|
|
65
|
+
if (op === null) return null
|
|
66
|
+
const code = Number.parseInt(op, 16)
|
|
67
|
+
if (code === 0) return ''
|
|
68
|
+
if (code >= 0x01 && code <= 0x4b) return this.readBytesHex(code)
|
|
69
|
+
if (code === 0x4c) {
|
|
70
|
+
const lenHex = this.readByteHex()
|
|
71
|
+
if (lenHex === null) return null
|
|
72
|
+
return this.readBytesHex(Number.parseInt(lenHex, 16))
|
|
73
|
+
}
|
|
74
|
+
if (code === 0x4d) {
|
|
75
|
+
const b1 = this.readByteHex(); const b2 = this.readByteHex()
|
|
76
|
+
if (b1 === null || b2 === null) return null
|
|
77
|
+
return this.readBytesHex(Number.parseInt(b2 + b1, 16))
|
|
78
|
+
}
|
|
79
|
+
if (code === 0x4e) {
|
|
80
|
+
const b1 = this.readByteHex(); const b2 = this.readByteHex()
|
|
81
|
+
const b3 = this.readByteHex(); const b4 = this.readByteHex()
|
|
82
|
+
if (b1 === null || b2 === null || b3 === null || b4 === null) return null
|
|
83
|
+
return this.readBytesHex(Number.parseInt(b4 + b3 + b2 + b1, 16))
|
|
84
|
+
}
|
|
85
|
+
return null
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function hexToUtf8 (hex: string): string {
|
|
90
|
+
if (hex === '') return ''
|
|
91
|
+
try {
|
|
92
|
+
return Utils.toUTF8(Utils.toArray(hex, 'hex'))
|
|
93
|
+
} catch {
|
|
94
|
+
return ''
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** Reads the ord-inscription envelope up to and including OP_ENDIF, returning its JSON payload. */
|
|
99
|
+
function readOrdEnvelope (r: HexReader): any {
|
|
100
|
+
if (r.readPushHex() !== ORD_TAG_HEX) throw new Error('not a BSV-21 script: missing "ord" tag')
|
|
101
|
+
|
|
102
|
+
// Content-type field id: accept canonical OP_1 (0x51) or non-minimal push-of-0x01.
|
|
103
|
+
const peek = r.hex.substring(r.pos, r.pos + 2)
|
|
104
|
+
if (peek === '51') {
|
|
105
|
+
r.pos += 2
|
|
106
|
+
} else if (r.readPushHex() !== '01') {
|
|
107
|
+
throw new Error('not a BSV-21 script: bad content-type field id')
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const ctHex = r.readPushHex()
|
|
111
|
+
if (ctHex === null || hexToUtf8(ctHex) !== CONTENT_TYPE) throw new Error('not a BSV-21 script: wrong content-type')
|
|
112
|
+
|
|
113
|
+
if (r.readByteHex() !== '00') throw new Error('not a BSV-21 script: missing OP_0 separator')
|
|
114
|
+
|
|
115
|
+
const contentHex = r.readPushHex()
|
|
116
|
+
if (contentHex === null) throw new Error('not a BSV-21 script: missing JSON payload')
|
|
117
|
+
let payload: any
|
|
118
|
+
try {
|
|
119
|
+
payload = JSON.parse(hexToUtf8(contentHex))
|
|
120
|
+
} catch {
|
|
121
|
+
throw new Error('not a BSV-21 script: invalid JSON payload')
|
|
122
|
+
}
|
|
123
|
+
if (payload?.p !== 'bsv-20') throw new Error('not a BSV-21 script: not bsv-20')
|
|
124
|
+
|
|
125
|
+
if (r.readByteHex() !== OP_ENDIF_HEX) throw new Error('not a BSV-21 script: missing OP_ENDIF')
|
|
126
|
+
return payload
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/** Reads the trailing standard P2PKH owner lock, returning the owner hash160 (hex). */
|
|
130
|
+
function readP2pkhOwner (r: HexReader): string {
|
|
131
|
+
const dup = r.readByteHex()
|
|
132
|
+
const hash160Op = r.readByteHex()
|
|
133
|
+
const pushLen = r.readByteHex()
|
|
134
|
+
if (dup !== OP_DUP_HEX || hash160Op !== OP_HASH160_HEX || pushLen !== PKH_PUSH_LEN_HEX) {
|
|
135
|
+
throw new Error('not a BSV-21 script: bad P2PKH owner lock')
|
|
136
|
+
}
|
|
137
|
+
const ownerHash160 = r.readBytesHex(20)
|
|
138
|
+
if (ownerHash160 === null) throw new Error('not a BSV-21 script: truncated owner hash')
|
|
139
|
+
if (r.readByteHex() !== OP_EQUALVERIFY_HEX || r.readByteHex() !== OP_CHECKSIG_HEX) {
|
|
140
|
+
throw new Error('not a BSV-21 script: bad P2PKH tail')
|
|
141
|
+
}
|
|
142
|
+
return ownerHash160
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/** Parses the optional `dec` field, accepted as a number or a digit string in [0, 18]. */
|
|
146
|
+
function parseDecimals (payload: any): number | undefined {
|
|
147
|
+
if (typeof payload.dec === 'number' && Number.isFinite(payload.dec)) return payload.dec
|
|
148
|
+
if (typeof payload.dec === 'string' && /^\d+$/.test(payload.dec)) {
|
|
149
|
+
const n = Number.parseInt(payload.dec, 10)
|
|
150
|
+
if (n >= 0 && n <= 18) return n
|
|
151
|
+
}
|
|
152
|
+
return undefined
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export class Bsv21Token {
|
|
156
|
+
static isBsv21 (script: LockingScript): boolean {
|
|
157
|
+
try {
|
|
158
|
+
Bsv21Token.decode(script)
|
|
159
|
+
return true
|
|
160
|
+
} catch {
|
|
161
|
+
return false
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Decodes a BSV-21 locking script.
|
|
167
|
+
* @throws if the script is not a recognisable BSV-21 output.
|
|
168
|
+
*/
|
|
169
|
+
static decode (script: LockingScript): Bsv21TokenDecoded {
|
|
170
|
+
const lower = script.toHex().toLowerCase()
|
|
171
|
+
if (lower.length < 60) throw new Error('not a BSV-21 script: too short')
|
|
172
|
+
if (!lower.startsWith(OP_FALSE_HEX + OP_IF_HEX)) throw new Error('not a BSV-21 script: missing OP_FALSE OP_IF')
|
|
173
|
+
|
|
174
|
+
const r = new HexReader(lower)
|
|
175
|
+
r.pos = 4 // past OP_FALSE OP_IF
|
|
176
|
+
|
|
177
|
+
const payload = readOrdEnvelope(r)
|
|
178
|
+
const ownerHash160 = readP2pkhOwner(r)
|
|
179
|
+
|
|
180
|
+
const amt: string | undefined = payload.amt
|
|
181
|
+
if (typeof amt !== 'string' || !/^\d+$/.test(amt)) throw new Error('not a BSV-21 script: bad amount')
|
|
182
|
+
|
|
183
|
+
const isMint = payload.op === 'deploy+mint'
|
|
184
|
+
const dec = parseDecimals(payload)
|
|
185
|
+
const id = !isMint && typeof payload.id === 'string' ? payload.id : ''
|
|
186
|
+
|
|
187
|
+
return {
|
|
188
|
+
id,
|
|
189
|
+
amt,
|
|
190
|
+
dec,
|
|
191
|
+
sym: typeof payload.sym === 'string' ? payload.sym : undefined,
|
|
192
|
+
icon: typeof payload.icon === 'string' ? payload.icon : undefined,
|
|
193
|
+
isMint,
|
|
194
|
+
ownerHash160
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { LockingScript } from '@bsv/sdk'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* DstasToken — decoder for DSTAS (Divisible STAS / STAS 3.0) locking scripts.
|
|
5
|
+
*
|
|
6
|
+
* DSTAS uses the dxs-bsv-token-sdk template:
|
|
7
|
+
*
|
|
8
|
+
* <owner pkh:20> <action data> [ENGINE ~2.9KB] OP_RETURN
|
|
9
|
+
* <redemption/protoID pkh:20 = tokenId> <flags> <service field per flag> <optional data...>
|
|
10
|
+
*
|
|
11
|
+
* Unlike the dxs SDK's full `LockingScriptReader` (which template-matches the
|
|
12
|
+
* whole body), this is a minimal *structural* recogniser sufficient for an
|
|
13
|
+
* overlay indexer: it extracts the owner, the tokenId (redemption pkh), the
|
|
14
|
+
* flags, and the frozen marker. DSTAS is satoshi-denominated, so the token
|
|
15
|
+
* amount is the containing output's satoshi value (read by the caller).
|
|
16
|
+
*
|
|
17
|
+
* Recognition signals (validated against real dxs SDK output):
|
|
18
|
+
* - the script opens with a 20-byte push (the owner) — `14 <20 bytes>`;
|
|
19
|
+
* - the body is large (the ~2.9KB engine);
|
|
20
|
+
* - `6a 14 <20 bytes>` (OP_RETURN + redemption push) appears once, near the
|
|
21
|
+
* end — the engine body contains no `6a14`.
|
|
22
|
+
*
|
|
23
|
+
* Decode-only; building DSTAS scripts is the dxs SDK's job.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
export interface DstasTokenDecoded {
|
|
27
|
+
/** Token identity for indexing + conservation — the redemption/protoID pkh. */
|
|
28
|
+
assetId: string
|
|
29
|
+
/** Same value as assetId; the redemption (protoID) pkh, hex. */
|
|
30
|
+
tokenId: string
|
|
31
|
+
/** Owner public-key hash (20-byte hex). */
|
|
32
|
+
ownerHash160: string
|
|
33
|
+
/** Flags byte(s), hex (bit 0x01 = freezable, 0x02 = confiscatable). */
|
|
34
|
+
flagsHex: string
|
|
35
|
+
freezeEnabled: boolean
|
|
36
|
+
confiscationEnabled: boolean
|
|
37
|
+
/** True when the action-data marker indicates a frozen UTXO. */
|
|
38
|
+
frozen: boolean
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const OWNER_PUSH_OP = '14' // push 20 bytes
|
|
42
|
+
// DSTAS scripts carry the ~2.9KB engine; anything smaller is not DSTAS.
|
|
43
|
+
const MIN_HEX_LEN = 4000
|
|
44
|
+
|
|
45
|
+
function isSinglePush (op: string): boolean {
|
|
46
|
+
const code = Number.parseInt(op, 16)
|
|
47
|
+
return code >= 0x01 && code <= 0x4b
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export class DstasToken {
|
|
51
|
+
static isDstas (script: LockingScript): boolean {
|
|
52
|
+
try {
|
|
53
|
+
DstasToken.decode(script)
|
|
54
|
+
return true
|
|
55
|
+
} catch {
|
|
56
|
+
return false
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Decodes a DSTAS locking script's identity fields.
|
|
62
|
+
* @throws if the script is not a recognisable DSTAS script.
|
|
63
|
+
*/
|
|
64
|
+
static decode (script: LockingScript): DstasTokenDecoded {
|
|
65
|
+
const hex = script.toHex().toLowerCase()
|
|
66
|
+
if (hex.length < MIN_HEX_LEN) throw new Error('not a DSTAS script: too short')
|
|
67
|
+
if (!hex.startsWith(OWNER_PUSH_OP)) throw new Error('not a DSTAS script: missing 20-byte owner push')
|
|
68
|
+
|
|
69
|
+
const ownerHash160 = hex.substring(2, 42)
|
|
70
|
+
|
|
71
|
+
// OP_RETURN (0x6a) + redemption push (0x14) — unique in a DSTAS body.
|
|
72
|
+
const ri = hex.lastIndexOf('6a14')
|
|
73
|
+
if (ri < 0) throw new Error('not a DSTAS script: missing OP_RETURN + redemption')
|
|
74
|
+
const tokenId = hex.substring(ri + 4, ri + 44)
|
|
75
|
+
if (tokenId.length !== 40) throw new Error('not a DSTAS script: truncated redemption')
|
|
76
|
+
|
|
77
|
+
// Flags push immediately follows the redemption push.
|
|
78
|
+
let flagsHex = ''
|
|
79
|
+
const flagsLenOp = hex.substring(ri + 44, ri + 46)
|
|
80
|
+
if (isSinglePush(flagsLenOp)) {
|
|
81
|
+
const len = Number.parseInt(flagsLenOp, 16)
|
|
82
|
+
flagsHex = hex.substring(ri + 46, ri + 46 + len * 2)
|
|
83
|
+
}
|
|
84
|
+
const flagsByte = flagsHex.length >= 2 ? Number.parseInt(flagsHex.substring(0, 2), 16) : 0
|
|
85
|
+
const freezeEnabled = (flagsByte & 0x01) !== 0
|
|
86
|
+
const confiscationEnabled = (flagsByte & 0x02) !== 0
|
|
87
|
+
|
|
88
|
+
// Action-data marker sits right after the owner push:
|
|
89
|
+
// OP_0 (00) = neutral; OP_2 (52) = frozen; push prefixed 0x02 = frozen.
|
|
90
|
+
const actionOp = hex.substring(42, 44)
|
|
91
|
+
const frozen =
|
|
92
|
+
actionOp === '52' ||
|
|
93
|
+
(isSinglePush(actionOp) && hex.substring(44, 46) === '02')
|
|
94
|
+
|
|
95
|
+
return {
|
|
96
|
+
assetId: tokenId,
|
|
97
|
+
tokenId,
|
|
98
|
+
ownerHash160,
|
|
99
|
+
flagsHex,
|
|
100
|
+
freezeEnabled,
|
|
101
|
+
confiscationEnabled,
|
|
102
|
+
frozen
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
package/src/MandalaAdmin.ts
CHANGED
|
@@ -9,7 +9,7 @@ import { createMinimallyEncodedScriptChunk } from './mandala-encoding.js'
|
|
|
9
9
|
export interface AssetMetadata { label: string, ticker?: string, decimals?: number, [k: string]: unknown }
|
|
10
10
|
|
|
11
11
|
export type MandalaActionKind =
|
|
12
|
-
| 'register' | 'issue' | 'redeem'
|
|
12
|
+
| 'register' | 'issue' | 'redeem'
|
|
13
13
|
| 'pause' | 'unpause'
|
|
14
14
|
| 'blockIdentity' | 'unblockIdentity'
|
|
15
15
|
| 'allowIdentity' | 'unallowIdentity'
|
package/src/StasToken.ts
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { LockingScript, Utils } from '@bsv/sdk'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* StasToken — decoder for **classic STAS** (legacy P2STAS / STAS 1.0) locking
|
|
5
|
+
* scripts. Unlike {@link MandalaToken}, classic STAS is satoshi-denominated:
|
|
6
|
+
* the token amount IS the output's satoshi value, so this template only
|
|
7
|
+
* recovers the on-chain *identity* fields (owner PKH + symbol). The amount is
|
|
8
|
+
* read from the containing output by the caller.
|
|
9
|
+
*
|
|
10
|
+
* Script shape produced by stas-js CreateContract:
|
|
11
|
+
*
|
|
12
|
+
* 76a914 <owner_pkh:20> 88ac69 <engine ~2.9KB> 6a <flags> <symbol> <data...>
|
|
13
|
+
*
|
|
14
|
+
* The owner hash160 sits at the well-known P2PKH-like prefix. The engine body
|
|
15
|
+
* is large and opaque (and may contain incidental `6a` bytes), so the OP_RETURN
|
|
16
|
+
* trailer is located by scanning from the end, matching the wallet's
|
|
17
|
+
* `parseClassicStasMetadata` source of truth.
|
|
18
|
+
*
|
|
19
|
+
* Building/unlocking classic STAS scripts is the stas-js engine's job; this
|
|
20
|
+
* template is decode-only.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
export interface StasTokenDecoded {
|
|
24
|
+
/** Token identity used for indexing + conservation grouping (the symbol). */
|
|
25
|
+
assetId: string
|
|
26
|
+
/** Token symbol parsed from the OP_RETURN trailer, or null if absent. */
|
|
27
|
+
symbol: string | null
|
|
28
|
+
/** Owner public-key hash (20-byte hex) from the P2PKH-like prefix. */
|
|
29
|
+
ownerHash160: string
|
|
30
|
+
/** Flags byte (hex) from the OP_RETURN trailer, or null if absent. */
|
|
31
|
+
flagsHex: string | null
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const P2PKH_PREFIX = '76a914'
|
|
35
|
+
const STAS_MARKER = '88ac69'
|
|
36
|
+
|
|
37
|
+
interface PushLength { len: number, dataStart: number }
|
|
38
|
+
|
|
39
|
+
/** Resolves a push opcode's payload length + data offset, or null for a non-push opcode. */
|
|
40
|
+
function pushDataLength (scriptHex: string, opcode: number, pos: number): PushLength | null {
|
|
41
|
+
if (opcode >= 0x01 && opcode <= 0x4b) return { len: opcode, dataStart: pos }
|
|
42
|
+
if (opcode === 0x4c) {
|
|
43
|
+
if (pos + 2 > scriptHex.length) return null
|
|
44
|
+
return { len: Number.parseInt(scriptHex.substring(pos, pos + 2), 16), dataStart: pos + 2 }
|
|
45
|
+
}
|
|
46
|
+
if (opcode === 0x4d) {
|
|
47
|
+
if (pos + 4 > scriptHex.length) return null
|
|
48
|
+
const b1 = scriptHex.substring(pos, pos + 2)
|
|
49
|
+
const b2 = scriptHex.substring(pos + 2, pos + 4)
|
|
50
|
+
return { len: Number.parseInt(b2 + b1, 16), dataStart: pos + 4 }
|
|
51
|
+
}
|
|
52
|
+
if (opcode === 0x4e) {
|
|
53
|
+
if (pos + 8 > scriptHex.length) return null
|
|
54
|
+
const b1 = scriptHex.substring(pos, pos + 2)
|
|
55
|
+
const b2 = scriptHex.substring(pos + 2, pos + 4)
|
|
56
|
+
const b3 = scriptHex.substring(pos + 4, pos + 6)
|
|
57
|
+
const b4 = scriptHex.substring(pos + 6, pos + 8)
|
|
58
|
+
return { len: Number.parseInt(b4 + b3 + b2 + b1, 16), dataStart: pos + 8 }
|
|
59
|
+
}
|
|
60
|
+
return null
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Reads push-data slots starting at a hex offset (after OP_RETURN). */
|
|
64
|
+
function readPushes (scriptHex: string, startPos: number, max = 8): string[] {
|
|
65
|
+
const pushes: string[] = []
|
|
66
|
+
let pos = startPos
|
|
67
|
+
while (pos < scriptHex.length && pushes.length < max) {
|
|
68
|
+
if (pos + 2 > scriptHex.length) break
|
|
69
|
+
const opcode = Number.parseInt(scriptHex.substring(pos, pos + 2), 16)
|
|
70
|
+
if (Number.isNaN(opcode)) break
|
|
71
|
+
pos += 2
|
|
72
|
+
if (opcode === 0) {
|
|
73
|
+
pushes.push('')
|
|
74
|
+
continue
|
|
75
|
+
}
|
|
76
|
+
const push = pushDataLength(scriptHex, opcode, pos)
|
|
77
|
+
if (push === null) break // non-push opcode (or truncated length) after OP_RETURN — stop
|
|
78
|
+
pushes.push(scriptHex.substring(push.dataStart, push.dataStart + push.len * 2))
|
|
79
|
+
pos = push.dataStart + push.len * 2
|
|
80
|
+
}
|
|
81
|
+
return pushes
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function hexToUtf8 (hex: string): string {
|
|
85
|
+
if (hex === '') return ''
|
|
86
|
+
try {
|
|
87
|
+
return Utils.toUTF8(Utils.toArray(hex, 'hex'))
|
|
88
|
+
} catch {
|
|
89
|
+
return ''
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export class StasToken {
|
|
94
|
+
/** True if the script carries the classic STAS prefix + marker. */
|
|
95
|
+
static isStas (script: LockingScript): boolean {
|
|
96
|
+
const hex = script.toHex()
|
|
97
|
+
return hex.startsWith(P2PKH_PREFIX) && hex.substring(46, 52) === STAS_MARKER
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Decodes a classic STAS locking script into its identity fields.
|
|
102
|
+
* @throws if the script is not a classic STAS script.
|
|
103
|
+
*/
|
|
104
|
+
static decode (script: LockingScript): StasTokenDecoded {
|
|
105
|
+
const hex = script.toHex()
|
|
106
|
+
if (hex.length < 56) throw new Error('not a STAS script: too short')
|
|
107
|
+
if (!hex.startsWith(P2PKH_PREFIX)) throw new Error('not a STAS script: missing P2PKH prefix')
|
|
108
|
+
if (hex.substring(46, 52) !== STAS_MARKER) throw new Error('not a STAS script: missing STAS marker')
|
|
109
|
+
|
|
110
|
+
const ownerHash160 = hex.substring(6, 46)
|
|
111
|
+
|
|
112
|
+
// OP_RETURN (0x6a) is placed by CreateContract as the last opcode before
|
|
113
|
+
// the data region. The engine body may contain incidental 0x6a bytes, so
|
|
114
|
+
// scan from the back.
|
|
115
|
+
const opReturnIdx = hex.lastIndexOf('6a')
|
|
116
|
+
let symbol: string | null = null
|
|
117
|
+
let flagsHex: string | null = null
|
|
118
|
+
if (opReturnIdx >= 0) {
|
|
119
|
+
const pushes = readPushes(hex, opReturnIdx + 2)
|
|
120
|
+
// Layout after OP_RETURN: [flagsByte, symbol, data, ...].
|
|
121
|
+
flagsHex = pushes[0]?.length === 2 ? pushes[0] : null
|
|
122
|
+
const symbolHex = pushes[1] ?? null
|
|
123
|
+
symbol = (symbolHex != null && symbolHex !== '')
|
|
124
|
+
? (hexToUtf8(symbolHex).replace(/[\x00- ]/g, '').trim() || null)
|
|
125
|
+
: null
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// assetId groups inputs/outputs of the same token for conservation. The
|
|
129
|
+
// symbol is the only identity carried in a classic STAS script; tokens
|
|
130
|
+
// with no symbol fall back to the owner-agnostic script tail hash so the
|
|
131
|
+
// grouping is still stable within a single transfer.
|
|
132
|
+
const assetId = symbol ?? `stas:${hex.substring(52, 68)}`
|
|
133
|
+
|
|
134
|
+
return { assetId, symbol, ownerHash160, flagsHex }
|
|
135
|
+
}
|
|
136
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { Bsv21Token } from '../Bsv21Token'
|
|
2
|
+
import { LockingScript, Utils } from '@bsv/sdk'
|
|
3
|
+
|
|
4
|
+
const OWNER = 'ab'.repeat(20)
|
|
5
|
+
|
|
6
|
+
function utf8ToHex (s: string): string {
|
|
7
|
+
return Utils.toArray(s, 'utf8').map(b => b.toString(16).padStart(2, '0')).join('')
|
|
8
|
+
}
|
|
9
|
+
function push (bytesHex: string): string {
|
|
10
|
+
const len = bytesHex.length / 2
|
|
11
|
+
if (len === 0) return '00'
|
|
12
|
+
if (len <= 0x4b) return len.toString(16).padStart(2, '0') + bytesHex
|
|
13
|
+
if (len <= 0xff) return '4c' + len.toString(16).padStart(2, '0') + bytesHex
|
|
14
|
+
const lo = len & 0xff; const hi = (len >> 8) & 0xff
|
|
15
|
+
return '4d' + lo.toString(16).padStart(2, '0') + hi.toString(16).padStart(2, '0') + bytesHex
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// Build a BSV-21 envelope with the given JSON payload + P2PKH owner tail.
|
|
19
|
+
function bsv21Script (payload: Record<string, string>, owner = OWNER): LockingScript {
|
|
20
|
+
const json = utf8ToHex(JSON.stringify(payload))
|
|
21
|
+
const envelope =
|
|
22
|
+
'00' + '63' + // OP_FALSE OP_IF
|
|
23
|
+
push(utf8ToHex('ord')) +
|
|
24
|
+
'51' + // OP_1 content-type tag
|
|
25
|
+
push(utf8ToHex('application/bsv-20')) +
|
|
26
|
+
'00' + // OP_0 separator
|
|
27
|
+
push(json) +
|
|
28
|
+
'68' // OP_ENDIF
|
|
29
|
+
const p2pkh = '76a914' + owner + '88ac'
|
|
30
|
+
return LockingScript.fromHex(envelope + p2pkh)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
describe('Bsv21Token.decode', () => {
|
|
34
|
+
it('decodes a transfer output', () => {
|
|
35
|
+
const id = `${'cd'.repeat(32)}_0`
|
|
36
|
+
const d = Bsv21Token.decode(bsv21Script({ p: 'bsv-20', op: 'transfer', id, amt: '500' }))
|
|
37
|
+
expect(d).toMatchObject({ id, amt: '500', isMint: false, ownerHash160: OWNER })
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
it('decodes a deploy+mint output (no id in payload)', () => {
|
|
41
|
+
const d = Bsv21Token.decode(bsv21Script({ p: 'bsv-20', op: 'deploy+mint', amt: '21000000', dec: '8', sym: 'TIK' }))
|
|
42
|
+
expect(d).toMatchObject({ id: '', amt: '21000000', dec: 8, sym: 'TIK', isMint: true })
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
it('isBsv21 is false for plain P2PKH', () => {
|
|
46
|
+
expect(Bsv21Token.isBsv21(LockingScript.fromHex(`76a914${OWNER}88ac`))).toBe(false)
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
it('throws on a non-bsv-20 inscription protocol', () => {
|
|
50
|
+
expect(() => Bsv21Token.decode(bsv21Script({ p: 'bsv-21', op: 'transfer', id: 'x', amt: '1' }))).toThrow(/bsv-20/)
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
it('throws on a missing/invalid amount', () => {
|
|
54
|
+
expect(() => Bsv21Token.decode(bsv21Script({ p: 'bsv-20', op: 'transfer', id: 'x' } as any))).toThrow(/amount/)
|
|
55
|
+
})
|
|
56
|
+
})
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { DstasToken } from '../DstasToken'
|
|
2
|
+
import { LockingScript } from '@bsv/sdk'
|
|
3
|
+
import { DSTAS_PLAIN_HEX, DSTAS_FROZEN_HEX, DSTAS_OWNER, DSTAS_TOKEN_ID } from './dstas-fixtures'
|
|
4
|
+
|
|
5
|
+
describe('DstasToken.decode (against real dxs-bsv-token-sdk output)', () => {
|
|
6
|
+
it('recovers owner, tokenId, flags from a real DSTAS script', () => {
|
|
7
|
+
const d = DstasToken.decode(LockingScript.fromHex(DSTAS_PLAIN_HEX))
|
|
8
|
+
expect(d.ownerHash160).toBe(DSTAS_OWNER)
|
|
9
|
+
expect(d.tokenId).toBe(DSTAS_TOKEN_ID)
|
|
10
|
+
expect(d.assetId).toBe(DSTAS_TOKEN_ID)
|
|
11
|
+
expect(d.flagsHex).toBe('03')
|
|
12
|
+
expect(d.freezeEnabled).toBe(true)
|
|
13
|
+
expect(d.confiscationEnabled).toBe(true)
|
|
14
|
+
expect(d.frozen).toBe(false)
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
it('detects the frozen marker (OP_2 action data)', () => {
|
|
18
|
+
const d = DstasToken.decode(LockingScript.fromHex(DSTAS_FROZEN_HEX))
|
|
19
|
+
expect(d.frozen).toBe(true)
|
|
20
|
+
expect(d.tokenId).toBe(DSTAS_TOKEN_ID)
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
it('isDstas is true for DSTAS, false for plain P2PKH and classic STAS', () => {
|
|
24
|
+
expect(DstasToken.isDstas(LockingScript.fromHex(DSTAS_PLAIN_HEX))).toBe(true)
|
|
25
|
+
expect(DstasToken.isDstas(LockingScript.fromHex(`76a914${DSTAS_OWNER}88ac`))).toBe(false)
|
|
26
|
+
// classic STAS prefix is 76a914… not a 20-byte owner push, and short.
|
|
27
|
+
expect(DstasToken.isDstas(LockingScript.fromHex(`76a914${DSTAS_OWNER}88ac69` + 'ac'.repeat(8)))).toBe(false)
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
it('throws on a short / non-DSTAS script', () => {
|
|
31
|
+
expect(() => DstasToken.decode(LockingScript.fromHex(`14${DSTAS_OWNER}00`))).toThrow(/DSTAS/)
|
|
32
|
+
})
|
|
33
|
+
})
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { StasToken } from '../StasToken'
|
|
2
|
+
import { LockingScript } from '@bsv/sdk'
|
|
3
|
+
|
|
4
|
+
// Build a synthetic classic STAS script matching stas-js CreateContract shape:
|
|
5
|
+
// 76a914 <owner_pkh:20> 88ac69 <engine> 6a <flags push> <symbol push> <data>
|
|
6
|
+
const ownerHash160 = 'ab'.repeat(20)
|
|
7
|
+
const engine = 'ac'.repeat(8) // opaque filler, deliberately free of 0x6a bytes
|
|
8
|
+
const flagsPush = '0100' // push 1 byte: flags = 0x00
|
|
9
|
+
const symbolPush = '04' + '54455354' // push 4 bytes: "TEST"
|
|
10
|
+
const stasHex = `76a914${ownerHash160}88ac69${engine}6a${flagsPush}${symbolPush}`
|
|
11
|
+
|
|
12
|
+
describe('StasToken.decode', () => {
|
|
13
|
+
it('recovers owner, symbol, flags, and assetId from a classic STAS script', () => {
|
|
14
|
+
const decoded = StasToken.decode(LockingScript.fromHex(stasHex))
|
|
15
|
+
expect(decoded.ownerHash160).toBe(ownerHash160)
|
|
16
|
+
expect(decoded.symbol).toBe('TEST')
|
|
17
|
+
expect(decoded.assetId).toBe('TEST')
|
|
18
|
+
expect(decoded.flagsHex).toBe('00')
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
it('isStas is true for a STAS script and false for plain P2PKH', () => {
|
|
22
|
+
const p2pkh = `76a914${ownerHash160}88ac`
|
|
23
|
+
expect(StasToken.isStas(LockingScript.fromHex(stasHex))).toBe(true)
|
|
24
|
+
expect(StasToken.isStas(LockingScript.fromHex(p2pkh))).toBe(false)
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
it('throws when the STAS marker is absent (long P2PKH-like script)', () => {
|
|
28
|
+
// ≥56 hex chars, starts with the P2PKH prefix but lacks the 88ac69 marker.
|
|
29
|
+
const notStas = `76a914${ownerHash160}88accccccccccc`
|
|
30
|
+
expect(() => StasToken.decode(LockingScript.fromHex(notStas))).toThrow(/STAS marker/)
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
it('throws when the P2PKH prefix is absent', () => {
|
|
34
|
+
expect(() => StasToken.decode(LockingScript.fromHex('6a0048656c6c6f'))).toThrow(/STAS/)
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
it('falls back to a script-derived assetId when no symbol is present', () => {
|
|
38
|
+
// OP_RETURN with only a flags push, no symbol slot.
|
|
39
|
+
const noSymbol = `76a914${ownerHash160}88ac69${engine}6a${flagsPush}`
|
|
40
|
+
const decoded = StasToken.decode(LockingScript.fromHex(noSymbol))
|
|
41
|
+
expect(decoded.symbol).toBeNull()
|
|
42
|
+
expect(decoded.assetId).toMatch(/^stas:/)
|
|
43
|
+
})
|
|
44
|
+
})
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// Real DSTAS locking scripts generated by dxs-bsv-token-sdk (buildDstasLockingScript).
|
|
2
|
+
// owner=2f2ec98d… redemption(tokenId)=b4ab0fff… flags=0x03 (freeze+confiscation).
|
|
3
|
+
export const DSTAS_PLAIN_HEX = '142f2ec98dfa6429a028536a6c9451f702daa3a333006d82736301218763007b7b517c6e5667766b517f786b517f73637c7f68517f73637c7f68517f73637c7f68517f73637c7f68517f73637c7f68766c936c7c5493686751687652937a76aa607f5f7f7c5e7f7c5d7f7c5c7f7c5b7f7c5a7f7c597f7c587f7c577f7c567f7c557f7c547f7c537f7c527f7c517f7c7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7c5f7f7c5e7f7c5d7f7c5c7f7c5b7f7c5a7f7c597f7c587f7c577f7c567f7c557f7c547f7c537f7c527f7c517f7c7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e011f7f7d7e01007e8111414136d08c5ed2bf3ba048afe6dcaebafe01005f80837e01007e7652967b537a7601ff877c0100879b7d648b6752799368537a7d9776547aa06394677768263044022079be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f8179802207c607f5f7f7c5e7f7c5d7f7c5c7f7c5b7f7c5a7f7c597f7c587f7c577f7c567f7c557f7c547f7c537f7c527f7c517f7c7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7c5f7f7c5e7f7c5d7f7c5c7f7c5b7f7c5a7f7c597f7c587f7c577f7c567f7c557f7c547f7c537f7c527f7c517f7c7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e01417e7c6421038ff83d8cf12121491609c4939dc11c4aa35503508fe432dc5a5c1905608b92186721023635954789a02e39fb7e54440b6f528d53efd65635ddad7f3c4085f97fdbdc4868ad547f7701207f01207f7701247f517f7801007e02fd00a063546752687f7801007e817f727e7b517f7c01147d887f517f7c01007e817601619f6976014ea063517c7b6776014ba06376014da063755467014d9c6352675168687f7c01007e81687f007b7b687602540b7f7701147f7c5579876b826475020100686b587a5893766b7a765155a569005379736382013ca07c517f7c51877b9a6352795487637101007c7e717101207f01147f7577776775785387646c766b8b8b7951886868677568686c6c7c6b517f7c817f788273638c7f776775010068518463517f7c01147d887f547952876372777c717c767663517f756852875779766352790152879a689b63517f77567a7567527c7681014f0161a5587a9a63015094687e68746c766b5c9388748c76795879888c8c7978886777717c567a5679538764780152879a787663517f756852879b745394768b797663517f756852877c6c766b5c936ea0637c8c768b797663517f75685287726b9b7c6c686ea0637c5394768b797663517f75685287726b9b7c6c686ea063755494797663517f756852879b676d689b63006968677568687c717167567a7568788273638c7f776775010068528463517f7c01147d887f547953876372777c677768686d6c75787653877c52879b636c75006b687c518763755279685879a9886b6b6b6b6b6b6b827763af686c6c6c6c6c6c6c547a577a7664577a577a587a597a786354807e7e676d68aa8800677b7c7651876375577a7c587a67007c68765258a569765187645294597a53795b7a7e7e78637c8c7c53797e5a7a7e6878637c8c7c53797e5a7a7e6878637c8c7c53797e5a7a7e6878637c8c7c53797e5a7a7e6878637c8c7c53797e5a7a7e68687276647572677772755168537a76aa5a7a7d54807e597a5b7a5c7a786354807e6f7e7eaa727c7e676d6e7eaa7c687b7eaa5a7a7d877663516752687c72879b69537a6491687c7b547f77517f7853a0916901247f77517f7c01007e817602fc00a06302fd00a063546752687f7c01007e816854937f77788c6301247f77517f7c01007e817602fc00a06302fd00a063546752687f7c01007e816854937f777852946301247f77517f7c01007e817602fc00a06302fd00a063546752687f7c01007e816854937f77686877517f7c52797d8b9f7c53a09b91697c76638c7c587f77517f7c01007e817602fc00a06302fd00a063546752687f7c01007e81687f777c6876638c7c587f77517f7c01007e817602fc00a06302fd00a063546752687f7c01007e81687f777c6863587f77517f7c01007e817602fc00a06302fd00a063546752687f7c01007e81687f7768587f517f7801007e817602fc00a06302fd00a063546752687f7801007e81727e7b7b687f75517f7c01147d887f517f7c01007e817601619f6976014ea0637c6776014ba06376014da063755467014d9c6352675168687f7c01007e81687f68557964577988756d67716881687863567a677b68587f7c8153796353795287637b6b537a6b717c6b6b537a6b676b577a6b597a6b587a6b577a6b7c68677b93687c547f7701207f75748c7a7669765880044676a914780114748c7a76727b748c7a768291788251877c764f877c81510111a59b9a9b648276014ba1647602ff00a16351014c677603ffff00a16352014d6754014e68687b7b7f757e687c7e67736301509367010068685c795c79636c766b7363517f7c51876301207f7c5279a8877c011c7f5579877c01147f755679879a9a6967756868687e777e7e827602fc00a0637603ffff00a06301fe7c82546701fd7c8252687da0637f756780687e67517f75687c7e7e0a888201218763ac67517f07517f73637c7f6876767e767e7e02ae687e7e7c557a00740111a063005a79646b7c748c7a76697d937b7b58807e6c91677c748c7a7d58807e6c6c6c557a680114748c7a748c7a768291788251877c764f877c81510111a59b9a9b648276014ba1647602ff00a16351014c677603ffff00a16352014d6754014e68687b7b7f757e687c7e67736301509367010068685479635f79676c766b0115797363517f7c51876301207f7c5279a8877c011c7f5579877c01147f755679879a9a6967756868687e777e7e827602fc00a0637603ffff00a06301fe7c82546701fd7c8252687da0637f756780687e67517f75687c7e7c637e677c6b7c6b7c6b7e7c6b68685979636c6c766b786b7363517f7c51876301347f77547f547f75786352797b01007e81957c01007e81965379a169676d68677568685c797363517f7c51876301347f77547f547f75786354797b01007e81957c01007e819678a169676d68677568687568740111a063748c7a76697d58807e00005c79635e79768263517f756851876c6c766b7c6b768263517f756851877b6e9b63789c6375745294797b78877b7b877d9b69637c917c689167745294797c638777637c917c91686777876391677c917c686868676d6d68687863537a6c936c6c6c567a567a54795479587a676b72937b7b5c795e796c68748c7a748c7a7b636e717b7b877b7b879a6967726d6801147b7e7c8291788251877c764f877c81510111a59b9a9b648276014ba1647602ff00a16351014c677603ffff00a16352014d6754014e68687b7b7f757e687c7e67736301509367010068687e7c636c766b7e726b6b726b6b675b797e68827602fc00a0637603ffff00a06301fe7c82546701fd7c8252687da0637f756780687e67517f75687c7e7e68740111a063748c7a76697d58807e00005c79635e79768263517f756851876c6c766b7c6b768263517f756851877b6e9b63789c6375745294797b78877b7b877d9b69637c917c689167745294797c638777637c917c91686777876391677c917c686868676d6d68687863537a6c936c6c6c567a567a54795479587a676b72937b7b5c795e796c68748c7a748c7a7b636e717b7b877b7b879a6967726d6801147b7e7c8291788251877c764f877c81510111a59b9a9b648276014ba1647602ff00a16351014c677603ffff00a16352014d6754014e68687b7b7f757e687c7e67736301509367010068687e7c636c766b7e726b6b726b6b675b797e68827602fc00a0637603ffff00a06301fe7c82546701fd7c8252687da0637f756780687e67517f75687c7e7e68597a636c6c6c6d6c6c6d6c9d687c587a9d7d7e5c79635d795880041976a9145e797e0288ac7e7e6700687d7e5c7a766302006a7c7e827602fc00a06301fd7c7e536751687f757c7e0058807c7e687d7eaa6b7e7e7e7e7e7eaa78877c6c877c6c9a9b726d726d77776a14b4ab0fffa02223a8a40d9e7f7823e61b3862538201031400112233445566778899aabbccddeeff00112233148899aabbccddeeff00112233445566778899aabb'
|
|
4
|
+
export const DSTAS_FROZEN_HEX = '142f2ec98dfa6429a028536a6c9451f702daa3a333526d82736301218763007b7b517c6e5667766b517f786b517f73637c7f68517f73637c7f68517f73637c7f68517f73637c7f68517f73637c7f68766c936c7c5493686751687652937a76aa607f5f7f7c5e7f7c5d7f7c5c7f7c5b7f7c5a7f7c597f7c587f7c577f7c567f7c557f7c547f7c537f7c527f7c517f7c7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7c5f7f7c5e7f7c5d7f7c5c7f7c5b7f7c5a7f7c597f7c587f7c577f7c567f7c557f7c547f7c537f7c527f7c517f7c7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e011f7f7d7e01007e8111414136d08c5ed2bf3ba048afe6dcaebafe01005f80837e01007e7652967b537a7601ff877c0100879b7d648b6752799368537a7d9776547aa06394677768263044022079be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f8179802207c607f5f7f7c5e7f7c5d7f7c5c7f7c5b7f7c5a7f7c597f7c587f7c577f7c567f7c557f7c547f7c537f7c527f7c517f7c7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7c5f7f7c5e7f7c5d7f7c5c7f7c5b7f7c5a7f7c597f7c587f7c577f7c567f7c557f7c547f7c537f7c527f7c517f7c7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e01417e7c6421038ff83d8cf12121491609c4939dc11c4aa35503508fe432dc5a5c1905608b92186721023635954789a02e39fb7e54440b6f528d53efd65635ddad7f3c4085f97fdbdc4868ad547f7701207f01207f7701247f517f7801007e02fd00a063546752687f7801007e817f727e7b517f7c01147d887f517f7c01007e817601619f6976014ea063517c7b6776014ba06376014da063755467014d9c6352675168687f7c01007e81687f007b7b687602540b7f7701147f7c5579876b826475020100686b587a5893766b7a765155a569005379736382013ca07c517f7c51877b9a6352795487637101007c7e717101207f01147f7577776775785387646c766b8b8b7951886868677568686c6c7c6b517f7c817f788273638c7f776775010068518463517f7c01147d887f547952876372777c717c767663517f756852875779766352790152879a689b63517f77567a7567527c7681014f0161a5587a9a63015094687e68746c766b5c9388748c76795879888c8c7978886777717c567a5679538764780152879a787663517f756852879b745394768b797663517f756852877c6c766b5c936ea0637c8c768b797663517f75685287726b9b7c6c686ea0637c5394768b797663517f75685287726b9b7c6c686ea063755494797663517f756852879b676d689b63006968677568687c717167567a7568788273638c7f776775010068528463517f7c01147d887f547953876372777c677768686d6c75787653877c52879b636c75006b687c518763755279685879a9886b6b6b6b6b6b6b827763af686c6c6c6c6c6c6c547a577a7664577a577a587a597a786354807e7e676d68aa8800677b7c7651876375577a7c587a67007c68765258a569765187645294597a53795b7a7e7e78637c8c7c53797e5a7a7e6878637c8c7c53797e5a7a7e6878637c8c7c53797e5a7a7e6878637c8c7c53797e5a7a7e6878637c8c7c53797e5a7a7e68687276647572677772755168537a76aa5a7a7d54807e597a5b7a5c7a786354807e6f7e7eaa727c7e676d6e7eaa7c687b7eaa5a7a7d877663516752687c72879b69537a6491687c7b547f77517f7853a0916901247f77517f7c01007e817602fc00a06302fd00a063546752687f7c01007e816854937f77788c6301247f77517f7c01007e817602fc00a06302fd00a063546752687f7c01007e816854937f777852946301247f77517f7c01007e817602fc00a06302fd00a063546752687f7c01007e816854937f77686877517f7c52797d8b9f7c53a09b91697c76638c7c587f77517f7c01007e817602fc00a06302fd00a063546752687f7c01007e81687f777c6876638c7c587f77517f7c01007e817602fc00a06302fd00a063546752687f7c01007e81687f777c6863587f77517f7c01007e817602fc00a06302fd00a063546752687f7c01007e81687f7768587f517f7801007e817602fc00a06302fd00a063546752687f7801007e81727e7b7b687f75517f7c01147d887f517f7c01007e817601619f6976014ea0637c6776014ba06376014da063755467014d9c6352675168687f7c01007e81687f68557964577988756d67716881687863567a677b68587f7c8153796353795287637b6b537a6b717c6b6b537a6b676b577a6b597a6b587a6b577a6b7c68677b93687c547f7701207f75748c7a7669765880044676a914780114748c7a76727b748c7a768291788251877c764f877c81510111a59b9a9b648276014ba1647602ff00a16351014c677603ffff00a16352014d6754014e68687b7b7f757e687c7e67736301509367010068685c795c79636c766b7363517f7c51876301207f7c5279a8877c011c7f5579877c01147f755679879a9a6967756868687e777e7e827602fc00a0637603ffff00a06301fe7c82546701fd7c8252687da0637f756780687e67517f75687c7e7e0a888201218763ac67517f07517f73637c7f6876767e767e7e02ae687e7e7c557a00740111a063005a79646b7c748c7a76697d937b7b58807e6c91677c748c7a7d58807e6c6c6c557a680114748c7a748c7a768291788251877c764f877c81510111a59b9a9b648276014ba1647602ff00a16351014c677603ffff00a16352014d6754014e68687b7b7f757e687c7e67736301509367010068685479635f79676c766b0115797363517f7c51876301207f7c5279a8877c011c7f5579877c01147f755679879a9a6967756868687e777e7e827602fc00a0637603ffff00a06301fe7c82546701fd7c8252687da0637f756780687e67517f75687c7e7c637e677c6b7c6b7c6b7e7c6b68685979636c6c766b786b7363517f7c51876301347f77547f547f75786352797b01007e81957c01007e81965379a169676d68677568685c797363517f7c51876301347f77547f547f75786354797b01007e81957c01007e819678a169676d68677568687568740111a063748c7a76697d58807e00005c79635e79768263517f756851876c6c766b7c6b768263517f756851877b6e9b63789c6375745294797b78877b7b877d9b69637c917c689167745294797c638777637c917c91686777876391677c917c686868676d6d68687863537a6c936c6c6c567a567a54795479587a676b72937b7b5c795e796c68748c7a748c7a7b636e717b7b877b7b879a6967726d6801147b7e7c8291788251877c764f877c81510111a59b9a9b648276014ba1647602ff00a16351014c677603ffff00a16352014d6754014e68687b7b7f757e687c7e67736301509367010068687e7c636c766b7e726b6b726b6b675b797e68827602fc00a0637603ffff00a06301fe7c82546701fd7c8252687da0637f756780687e67517f75687c7e7e68740111a063748c7a76697d58807e00005c79635e79768263517f756851876c6c766b7c6b768263517f756851877b6e9b63789c6375745294797b78877b7b877d9b69637c917c689167745294797c638777637c917c91686777876391677c917c686868676d6d68687863537a6c936c6c6c567a567a54795479587a676b72937b7b5c795e796c68748c7a748c7a7b636e717b7b877b7b879a6967726d6801147b7e7c8291788251877c764f877c81510111a59b9a9b648276014ba1647602ff00a16351014c677603ffff00a16352014d6754014e68687b7b7f757e687c7e67736301509367010068687e7c636c766b7e726b6b726b6b675b797e68827602fc00a0637603ffff00a06301fe7c82546701fd7c8252687da0637f756780687e67517f75687c7e7e68597a636c6c6c6d6c6c6d6c9d687c587a9d7d7e5c79635d795880041976a9145e797e0288ac7e7e6700687d7e5c7a766302006a7c7e827602fc00a06301fd7c7e536751687f757c7e0058807c7e687d7eaa6b7e7e7e7e7e7eaa78877c6c877c6c9a9b726d726d77776a14b4ab0fffa02223a8a40d9e7f7823e61b3862538201031400112233445566778899aabbccddeeff00112233148899aabbccddeeff00112233445566778899aabb'
|
|
5
|
+
export const DSTAS_OWNER = '2f2ec98dfa6429a028536a6c9451f702daa3a333'
|
|
6
|
+
export const DSTAS_TOKEN_ID = 'b4ab0fffa02223a8a40d9e7f7823e61b38625382'
|