dxs-stas-sdk 1.0.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/jest.config.js +6 -0
- package/package.json +38 -0
- package/rollup.config.ts +26 -0
- package/src/base.ts +4 -0
- package/src/bitcoin/address.ts +42 -0
- package/src/bitcoin/destination.ts +7 -0
- package/src/bitcoin/index.ts +15 -0
- package/src/bitcoin/mnemonic.ts +40 -0
- package/src/bitcoin/network.ts +11 -0
- package/src/bitcoin/op-codes.ts +143 -0
- package/src/bitcoin/out-point.ts +61 -0
- package/src/bitcoin/payment.ts +8 -0
- package/src/bitcoin/private-key.ts +57 -0
- package/src/bitcoin/script-type.ts +9 -0
- package/src/bitcoin/sig-hash-type.ts +7 -0
- package/src/bitcoin/token-scheme.ts +28 -0
- package/src/bitcoin/transaction-input.ts +33 -0
- package/src/bitcoin/transaction-output.ts +105 -0
- package/src/bitcoin/transaction.ts +31 -0
- package/src/bitcoin/wallet.ts +59 -0
- package/src/buffer/buffer-reader.ts +88 -0
- package/src/buffer/buffer-utils.ts +116 -0
- package/src/buffer/buffer-writer.ts +74 -0
- package/src/buffer/index.ts +3 -0
- package/src/hashes.ts +13 -0
- package/src/index.ts +8 -0
- package/src/script/build/null-data-builder.ts +21 -0
- package/src/script/build/p2pkh-builder.ts +34 -0
- package/src/script/build/p2stas-builder.ts +38 -0
- package/src/script/build/script-builder.ts +128 -0
- package/src/script/index.ts +10 -0
- package/src/script/read/script-reader.ts +79 -0
- package/src/script/script-samples.ts +27 -0
- package/src/script/script-token.ts +66 -0
- package/src/script/script-utils.ts +12 -0
- package/src/stas-bundle-factory.ts +367 -0
- package/src/transaction/build/input-builder.ts +270 -0
- package/src/transaction/build/output-builder.ts +25 -0
- package/src/transaction/build/transaction-builder.ts +163 -0
- package/src/transaction/index.ts +4 -0
- package/src/transaction/read/transaction-reader.ts +54 -0
- package/src/transaction-factory.ts +221 -0
- package/tests/script-build.test.ts +41 -0
- package/tests/script-read.test.ts +34 -0
- package/tests/stas-transactios.ts +41 -0
- package/tests/transaction-build.test.ts +394 -0
- package/tests/transaction-reader.test.ts +50 -0
- package/tsconfig.json +110 -0
- package/tslint.json +3 -0
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { mnemonicToSeedSync } from "@scure/bip39";
|
|
2
|
+
import { toBuffer } from "../buffer/buffer-utils";
|
|
3
|
+
import { HDKey, Versions } from "@scure/bip32";
|
|
4
|
+
import { PrivateKey } from "./private-key";
|
|
5
|
+
|
|
6
|
+
interface WalletOpt {
|
|
7
|
+
versions: Versions;
|
|
8
|
+
depth?: number;
|
|
9
|
+
index?: number;
|
|
10
|
+
parentFingerprint?: number;
|
|
11
|
+
chainCode: Uint8Array;
|
|
12
|
+
privateKey?: Uint8Array | bigint;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export class Wallet extends HDKey {
|
|
16
|
+
static fromMnemonic = (mnemonic: string) => {
|
|
17
|
+
const seed = mnemonicToSeedSync(mnemonic);
|
|
18
|
+
return Wallet.fromHdKey(Wallet.fromMasterSeed(seed));
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
static fromHdKey = ({
|
|
22
|
+
versions,
|
|
23
|
+
depth,
|
|
24
|
+
index,
|
|
25
|
+
parentFingerprint,
|
|
26
|
+
chainCode,
|
|
27
|
+
privateKey,
|
|
28
|
+
}: HDKey) =>
|
|
29
|
+
new Wallet({
|
|
30
|
+
versions,
|
|
31
|
+
depth,
|
|
32
|
+
index,
|
|
33
|
+
parentFingerprint,
|
|
34
|
+
chainCode: chainCode!,
|
|
35
|
+
privateKey: privateKey!,
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
private _pk: PrivateKey;
|
|
39
|
+
|
|
40
|
+
constructor(opt: WalletOpt) {
|
|
41
|
+
super(opt);
|
|
42
|
+
|
|
43
|
+
this._pk = new PrivateKey(toBuffer(this.privateKey!));
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
get Address() {
|
|
47
|
+
return this._pk.Address;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
get PublicKey() {
|
|
51
|
+
return this._pk.PublicKey;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
derive(path: string): Wallet {
|
|
55
|
+
return Wallet.fromHdKey(super.derive(path));
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
sign = (message: Uint8Array) => this._pk.sign(message);
|
|
59
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { ensureUInt, slice } from "./buffer-utils";
|
|
2
|
+
|
|
3
|
+
export class BufferReader {
|
|
4
|
+
buffer: Buffer;
|
|
5
|
+
offset: number;
|
|
6
|
+
|
|
7
|
+
constructor(buffer: Buffer, offset = 0) {
|
|
8
|
+
this.buffer = buffer;
|
|
9
|
+
this.offset = offset;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
readUInt8() {
|
|
13
|
+
const result = this.buffer.readUInt8(this.offset);
|
|
14
|
+
this.offset++;
|
|
15
|
+
|
|
16
|
+
return result;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
readUInt16() {
|
|
20
|
+
const result = this.buffer.readUInt16LE(this.offset);
|
|
21
|
+
this.offset += 2;
|
|
22
|
+
|
|
23
|
+
return result;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
readInt32() {
|
|
27
|
+
const result = this.buffer.readInt32LE(this.offset);
|
|
28
|
+
this.offset += 4;
|
|
29
|
+
|
|
30
|
+
return result;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
readUInt32() {
|
|
34
|
+
const result = this.buffer.readUInt32LE(this.offset);
|
|
35
|
+
this.offset += 4;
|
|
36
|
+
|
|
37
|
+
return result;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
readUInt64() {
|
|
41
|
+
const a = this.buffer.readUInt32LE(this.offset);
|
|
42
|
+
let b = this.buffer.readUInt32LE(this.offset + 4);
|
|
43
|
+
b *= 0x100000000;
|
|
44
|
+
|
|
45
|
+
const result = b + a;
|
|
46
|
+
ensureUInt(result, 0x001fffffffffffff);
|
|
47
|
+
|
|
48
|
+
this.offset += 8;
|
|
49
|
+
|
|
50
|
+
return result;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
readVarInt() {
|
|
54
|
+
var first = this.readUInt8();
|
|
55
|
+
|
|
56
|
+
// 8 bit
|
|
57
|
+
if (first < 0xfd) {
|
|
58
|
+
return first;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// 16 bit
|
|
62
|
+
if (first === 0xfd) {
|
|
63
|
+
return this.readUInt16();
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// 32 bit
|
|
67
|
+
if (first === 0xfe) {
|
|
68
|
+
return this.readUInt32();
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// 64 bit
|
|
72
|
+
return this.readUInt64();
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
readChunk(n: number) {
|
|
76
|
+
if (this.buffer.length < this.offset + n) {
|
|
77
|
+
throw new Error("Cannot read chunk out of bounds");
|
|
78
|
+
}
|
|
79
|
+
const result = slice(this.buffer, this.offset, this.offset + n);
|
|
80
|
+
this.offset += n;
|
|
81
|
+
|
|
82
|
+
return result;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
readVarChunk() {
|
|
86
|
+
return this.readChunk(this.readVarInt());
|
|
87
|
+
}
|
|
88
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import toBufferExternal from "typedarray-to-buffer";
|
|
2
|
+
import { OpCode } from "../bitcoin/op-codes";
|
|
3
|
+
|
|
4
|
+
export const OP_INT_BASE = OpCode.OP_RESERVED;
|
|
5
|
+
|
|
6
|
+
export const asMinimalOP = (buffer: Buffer) => {
|
|
7
|
+
if (buffer.length === 0) return OpCode.OP_0;
|
|
8
|
+
if (buffer.length !== 1) return;
|
|
9
|
+
if (buffer[0] >= 1 && buffer[0] <= 16) return OP_INT_BASE + buffer[0];
|
|
10
|
+
if (buffer[0] === 0x81) return OpCode.OP_1NEGATE;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export const ensureUInt = (value: number, max: number) => {
|
|
14
|
+
if (value < 0)
|
|
15
|
+
throw new Error("specified a negative value for writing an unsigned value");
|
|
16
|
+
|
|
17
|
+
if (value > max) throw new Error("RangeError: value out of range");
|
|
18
|
+
|
|
19
|
+
if (Math.floor(value) !== value)
|
|
20
|
+
throw new Error(`value has a fractional component: ${value}`);
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export const toBuffer = toBufferExternal;
|
|
24
|
+
|
|
25
|
+
export const toUtf8Buffer = (value: string) => Buffer.from(value, "utf8");
|
|
26
|
+
export const toHexBuffer = (value: string) => Buffer.from(value, "hex");
|
|
27
|
+
|
|
28
|
+
export const slice = (buffer: Buffer, offset: number, length: number) =>
|
|
29
|
+
toBuffer(Uint8Array.prototype.slice.call(buffer, offset, length));
|
|
30
|
+
|
|
31
|
+
export const reverseBuffer = (buffer: Buffer) => {
|
|
32
|
+
let j = buffer.length - 1;
|
|
33
|
+
let tmp = 0;
|
|
34
|
+
|
|
35
|
+
for (let i = 0; i < buffer.length / 2; i++) {
|
|
36
|
+
tmp = buffer[i];
|
|
37
|
+
buffer[i] = buffer[j];
|
|
38
|
+
buffer[j] = tmp;
|
|
39
|
+
j--;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return buffer;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export const cloneBuffer = (
|
|
46
|
+
source: Buffer,
|
|
47
|
+
targetStart: number = 0,
|
|
48
|
+
sourceStart?: number | undefined,
|
|
49
|
+
sourceEnd?: number | undefined
|
|
50
|
+
) => {
|
|
51
|
+
sourceStart = sourceStart ?? 0;
|
|
52
|
+
sourceEnd = sourceEnd ?? source.length;
|
|
53
|
+
|
|
54
|
+
const clone = Buffer.allocUnsafe(sourceEnd - sourceStart);
|
|
55
|
+
source.copy(clone, targetStart, sourceStart, sourceEnd);
|
|
56
|
+
|
|
57
|
+
return clone;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
export const splitBuffer = (source: Buffer, splitBy: Buffer): Buffer[] => {
|
|
61
|
+
let search = -1;
|
|
62
|
+
let move = 0;
|
|
63
|
+
const segments: Buffer[] = [];
|
|
64
|
+
|
|
65
|
+
while ((search = source.indexOf(splitBy)) > -1) {
|
|
66
|
+
const segment = slice(source, 0, search + move);
|
|
67
|
+
if (segment.length > 0) segments.push(segment);
|
|
68
|
+
|
|
69
|
+
source = slice(source, search + splitBy.length, source.length);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (source.length > 0) segments.push(source);
|
|
73
|
+
|
|
74
|
+
return segments;
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
export const getVarIntLength = (value: number): number =>
|
|
78
|
+
value < 0xfd ? 1 : value <= 0xffff ? 3 : value <= 0xffffffff ? 5 : 9;
|
|
79
|
+
|
|
80
|
+
export const getNumberSize = (data: number): number =>
|
|
81
|
+
data > 0 && data <= 16
|
|
82
|
+
? 1
|
|
83
|
+
: getVarIntLength(getMinimumRequiredByte(data)) +
|
|
84
|
+
getMinimumRequiredByte(data);
|
|
85
|
+
|
|
86
|
+
export const getMinimumRequiredByte = (value: number): number =>
|
|
87
|
+
value >= -128 && value <= 127
|
|
88
|
+
? 1
|
|
89
|
+
: value >= -32768 && value <= 32767
|
|
90
|
+
? 2
|
|
91
|
+
: value >= -8388608 && value <= 8388607
|
|
92
|
+
? 3
|
|
93
|
+
: value >= -2147483648 && value <= 2147483647
|
|
94
|
+
? 4
|
|
95
|
+
: value >= -549755813888 && value <= 549755813887
|
|
96
|
+
? 5
|
|
97
|
+
: value >= -140737488355328 && value <= 140737488355327
|
|
98
|
+
? 6
|
|
99
|
+
: value >= -36028797018963968 && value <= 36028797018963967 // TODO it's a bug MAX_SAFE_INTEGER = 9007199254740991
|
|
100
|
+
? 7
|
|
101
|
+
: 8;
|
|
102
|
+
|
|
103
|
+
export const getNumberBuffer = (value: number): Buffer => {
|
|
104
|
+
const size = getMinimumRequiredByte(value);
|
|
105
|
+
const buffer = Buffer.alloc(size);
|
|
106
|
+
|
|
107
|
+
buffer.writeIntLE(value, 0, size);
|
|
108
|
+
|
|
109
|
+
return buffer;
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
export const estimateChunkSize = (bufferSize: number) =>
|
|
113
|
+
getVarIntLength(bufferSize) + bufferSize;
|
|
114
|
+
|
|
115
|
+
export const getChunkSize = (buffer: Buffer) =>
|
|
116
|
+
estimateChunkSize(buffer.length);
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { ensureUInt } from "./buffer-utils";
|
|
2
|
+
|
|
3
|
+
export class BufferWriter {
|
|
4
|
+
buffer: Buffer;
|
|
5
|
+
offset: number;
|
|
6
|
+
|
|
7
|
+
constructor(buffer: Buffer, offset = 0) {
|
|
8
|
+
this.buffer = buffer;
|
|
9
|
+
this.offset = offset;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
static fromSize = (size: number) =>
|
|
13
|
+
new BufferWriter(Buffer.allocUnsafe(size));
|
|
14
|
+
|
|
15
|
+
writeUInt8(value: number) {
|
|
16
|
+
this.offset = this.buffer.writeUInt8(value, this.offset);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
writeUInt16(value: number) {
|
|
20
|
+
this.offset = this.buffer.writeUInt16LE(value, this.offset);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
writeUInt32(value: number) {
|
|
24
|
+
this.offset = this.buffer.writeUInt32LE(value, this.offset);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
writeUInt64(value: number) {
|
|
28
|
+
ensureUInt(value, 0x001fffffffffffff);
|
|
29
|
+
|
|
30
|
+
this.buffer.writeInt32LE(value & -1, this.offset);
|
|
31
|
+
this.buffer.writeUInt32LE(Math.floor(value / 0x100000000), this.offset + 4);
|
|
32
|
+
this.offset += 8;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
writeVarInt(value: number) {
|
|
36
|
+
// 8 bit
|
|
37
|
+
if (value <= 0xfc) {
|
|
38
|
+
this.writeUInt8(value);
|
|
39
|
+
|
|
40
|
+
// 16 bit
|
|
41
|
+
} else if (value <= 0xffff) {
|
|
42
|
+
this.writeUInt8(0xfd);
|
|
43
|
+
this.writeUInt16(value);
|
|
44
|
+
|
|
45
|
+
// 32 bit
|
|
46
|
+
} else if (value <= 0xffffffff) {
|
|
47
|
+
this.writeUInt8(0xfe);
|
|
48
|
+
this.writeUInt32(value);
|
|
49
|
+
|
|
50
|
+
// 64 bit
|
|
51
|
+
} else {
|
|
52
|
+
this.writeUInt8(0xff);
|
|
53
|
+
this.writeUInt64(value);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
writeChunk(chunk: Buffer) {
|
|
58
|
+
if (this.buffer.length < this.offset + chunk.length)
|
|
59
|
+
throw new Error(
|
|
60
|
+
`Cannot writte chunk out of bounds; total size: ${
|
|
61
|
+
this.buffer.length
|
|
62
|
+
}; position: ${this.offset}; excess: ${
|
|
63
|
+
this.offset + chunk.length - this.buffer.length
|
|
64
|
+
}`
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
this.offset += chunk.copy(this.buffer, this.offset);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
writeVarChunk(chunk: Buffer) {
|
|
71
|
+
this.writeVarInt(chunk.length);
|
|
72
|
+
this.writeChunk(chunk);
|
|
73
|
+
}
|
|
74
|
+
}
|
package/src/hashes.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { sha256 as nobleHash256 } from "@noble/hashes/sha256";
|
|
2
|
+
import { ripemd160 as nobleRipemd160 } from "@noble/hashes/ripemd160";
|
|
3
|
+
import { toBuffer } from "./buffer/buffer-utils";
|
|
4
|
+
|
|
5
|
+
export const sha256 = (message: Buffer): Buffer =>
|
|
6
|
+
toBuffer(nobleHash256(message));
|
|
7
|
+
|
|
8
|
+
export const ripemd160 = (message: Buffer): Buffer =>
|
|
9
|
+
toBuffer(nobleRipemd160(message));
|
|
10
|
+
|
|
11
|
+
export const hash160 = (buffer: Buffer) => ripemd160(sha256(buffer));
|
|
12
|
+
|
|
13
|
+
export const hash256 = (buffer: Buffer) => sha256(sha256(buffer));
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { OpCode } from "../../bitcoin/op-codes";
|
|
2
|
+
import { ScriptType } from "../../bitcoin/script-type";
|
|
3
|
+
import { nullDataTokens } from "../script-samples";
|
|
4
|
+
import { ScriptToken } from "../script-token";
|
|
5
|
+
import { ScriptBuilder } from "./script-builder";
|
|
6
|
+
|
|
7
|
+
export class NullDataBuilder extends ScriptBuilder {
|
|
8
|
+
constructor(data: Buffer[]) {
|
|
9
|
+
super(ScriptType.nullData);
|
|
10
|
+
|
|
11
|
+
for (var token of nullDataTokens) {
|
|
12
|
+
this._tokens.push(ScriptToken.fromScriptToken(token));
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
this.addOpCode(OpCode.OP_RETURN);
|
|
16
|
+
|
|
17
|
+
for (var segment of data) {
|
|
18
|
+
this.addData(segment);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { Address } from "../../bitcoin/address";
|
|
2
|
+
import { OpCode } from "../../bitcoin/op-codes";
|
|
3
|
+
import { ScriptType } from "../../bitcoin/script-type";
|
|
4
|
+
import { p2phkTokens } from "../script-samples";
|
|
5
|
+
import { ScriptToken } from "../script-token";
|
|
6
|
+
import { ScriptBuilder } from "./script-builder";
|
|
7
|
+
|
|
8
|
+
export class P2pkhBuilder extends ScriptBuilder {
|
|
9
|
+
private _isOpReturnAdded: boolean = false;
|
|
10
|
+
|
|
11
|
+
constructor(address: Address) {
|
|
12
|
+
super(ScriptType.p2pkh, address);
|
|
13
|
+
|
|
14
|
+
for (var token of p2phkTokens) {
|
|
15
|
+
if (token.IsReceiverId) {
|
|
16
|
+
const receiver = ScriptToken.fromBuffer(address.Hash160);
|
|
17
|
+
receiver.IsReceiverId = true;
|
|
18
|
+
|
|
19
|
+
this._tokens.push(receiver);
|
|
20
|
+
} else {
|
|
21
|
+
this._tokens.push(ScriptToken.fromScriptToken(token));
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
addReturnData(data: Buffer) {
|
|
27
|
+
if (!this._isOpReturnAdded) {
|
|
28
|
+
this.addOpCode(OpCode.OP_RETURN);
|
|
29
|
+
this._isOpReturnAdded = true;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
this.addData(data);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { Address } from "../../bitcoin/address";
|
|
2
|
+
import { OpCode } from "../../bitcoin/op-codes";
|
|
3
|
+
import { ScriptType } from "../../bitcoin/script-type";
|
|
4
|
+
import { getP2stasTokens } from "../script-samples";
|
|
5
|
+
import { ScriptToken } from "../script-token";
|
|
6
|
+
import { ScriptBuilder } from "./script-builder";
|
|
7
|
+
|
|
8
|
+
export class P2stasBuilder extends ScriptBuilder {
|
|
9
|
+
constructor(
|
|
10
|
+
address: Address,
|
|
11
|
+
tokenId: string,
|
|
12
|
+
symbol: string,
|
|
13
|
+
data: Buffer[] = []
|
|
14
|
+
) {
|
|
15
|
+
super(ScriptType.p2stas, address);
|
|
16
|
+
|
|
17
|
+
const stasTokens = getP2stasTokens();
|
|
18
|
+
|
|
19
|
+
for (var token of stasTokens) {
|
|
20
|
+
if (token.IsReceiverId) {
|
|
21
|
+
const receiver = ScriptToken.fromBuffer(address.Hash160);
|
|
22
|
+
receiver.IsReceiverId = true;
|
|
23
|
+
|
|
24
|
+
this._tokens.push(receiver);
|
|
25
|
+
} else {
|
|
26
|
+
this._tokens.push(ScriptToken.fromScriptToken(token));
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
this.addOpCode(OpCode.OP_RETURN);
|
|
31
|
+
this.addData(Buffer.from(tokenId, "hex"));
|
|
32
|
+
this.addData(Buffer.from(symbol, "utf8"));
|
|
33
|
+
|
|
34
|
+
for (const d of data) {
|
|
35
|
+
this.addData(d);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { getNumberBuffer } from "../../buffer/buffer-utils";
|
|
2
|
+
import { BufferWriter } from "../../buffer/buffer-writer";
|
|
3
|
+
import { Address } from "../../bitcoin/address";
|
|
4
|
+
import { OpCode } from "../../bitcoin/op-codes";
|
|
5
|
+
import { ScriptType } from "../../bitcoin/script-type";
|
|
6
|
+
import { ScriptToken } from "../script-token";
|
|
7
|
+
|
|
8
|
+
export class ScriptBuilder {
|
|
9
|
+
_tokens: ScriptToken[] = [];
|
|
10
|
+
|
|
11
|
+
ScriptType: ScriptType;
|
|
12
|
+
ToAddress?: Address;
|
|
13
|
+
|
|
14
|
+
constructor(scriptType: ScriptType, toAddress?: Address) {
|
|
15
|
+
this.ScriptType = scriptType;
|
|
16
|
+
this.ToAddress = toAddress;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
static fromTokens = (tokens: ScriptToken[], scriptType: ScriptType) => {
|
|
20
|
+
const builder = new ScriptBuilder(scriptType);
|
|
21
|
+
builder._tokens = tokens;
|
|
22
|
+
|
|
23
|
+
return builder;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
size = () => {
|
|
27
|
+
let size = 0;
|
|
28
|
+
|
|
29
|
+
for (const token of this._tokens) {
|
|
30
|
+
size += this.tokenSize(token);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return size;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
tokenSize = (token: ScriptToken) => {
|
|
37
|
+
let size = 1;
|
|
38
|
+
|
|
39
|
+
const opcodeNum = token.OpCodeNum;
|
|
40
|
+
const dataLength = token.DataLength;
|
|
41
|
+
const add =
|
|
42
|
+
opcodeNum > 0 && opcodeNum < OpCode.OP_PUSHDATA1
|
|
43
|
+
? dataLength
|
|
44
|
+
: opcodeNum === OpCode.OP_PUSHDATA1
|
|
45
|
+
? dataLength + 1
|
|
46
|
+
: opcodeNum === OpCode.OP_PUSHDATA2
|
|
47
|
+
? dataLength + 2
|
|
48
|
+
: opcodeNum === OpCode.OP_PUSHDATA4
|
|
49
|
+
? dataLength + 4
|
|
50
|
+
: 0;
|
|
51
|
+
|
|
52
|
+
return size + add;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
toBuffer = () => {
|
|
56
|
+
const buffer = Buffer.alloc(this.size());
|
|
57
|
+
const bufferWriter = new BufferWriter(buffer);
|
|
58
|
+
|
|
59
|
+
for (var token of this._tokens) {
|
|
60
|
+
bufferWriter.writeUInt8(token.OpCodeNum);
|
|
61
|
+
|
|
62
|
+
if (token.OpCodeNum > 0 && token.OpCodeNum < OpCode.OP_PUSHDATA1) {
|
|
63
|
+
bufferWriter.writeChunk(token.Data!);
|
|
64
|
+
} else if (token.OpCodeNum === OpCode.OP_PUSHDATA1) {
|
|
65
|
+
bufferWriter.writeUInt8(token.DataLength);
|
|
66
|
+
bufferWriter.writeChunk(token.Data!);
|
|
67
|
+
} else if (token.OpCodeNum === OpCode.OP_PUSHDATA2) {
|
|
68
|
+
bufferWriter.writeUInt16(token.DataLength);
|
|
69
|
+
bufferWriter.writeChunk(token.Data!);
|
|
70
|
+
} else if (token.OpCodeNum === OpCode.OP_PUSHDATA4) {
|
|
71
|
+
bufferWriter.writeUInt32(token.DataLength);
|
|
72
|
+
bufferWriter.writeChunk(token.Data!);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return buffer;
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
toHex = () => this.toBuffer().toString("hex");
|
|
80
|
+
|
|
81
|
+
addToken = (token: ScriptToken) => {
|
|
82
|
+
this._tokens.push(token);
|
|
83
|
+
|
|
84
|
+
return this;
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
addOpCode = (opCode: OpCode) => {
|
|
88
|
+
this._tokens.push(new ScriptToken(opCode, opCode));
|
|
89
|
+
|
|
90
|
+
return this;
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
addData = (data: Buffer) => {
|
|
94
|
+
this._tokens.push(ScriptToken.fromBuffer(data));
|
|
95
|
+
|
|
96
|
+
return this;
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
addDatas = (data: Buffer[]) => {
|
|
100
|
+
for (const chunk of data) this._tokens.push(ScriptToken.fromBuffer(chunk));
|
|
101
|
+
|
|
102
|
+
return this;
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
addNumber = (data: number) => {
|
|
106
|
+
if (data === 0) this.addOpCode(OpCode.OP_0);
|
|
107
|
+
else if (data <= 16) this.addOpCode(0x50 + data);
|
|
108
|
+
else this.addData(getNumberBuffer(data));
|
|
109
|
+
|
|
110
|
+
return this;
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
toAsm = () => {
|
|
114
|
+
const opCodeKeys = Object.keys(OpCode);
|
|
115
|
+
const opCodeValues = Object.values(OpCode);
|
|
116
|
+
|
|
117
|
+
let result = "";
|
|
118
|
+
|
|
119
|
+
for (const token of this._tokens) {
|
|
120
|
+
if (result.length > 0) result += " ";
|
|
121
|
+
|
|
122
|
+
if (token.Data) result += token.Data!.toString("hex");
|
|
123
|
+
else result += opCodeKeys[opCodeValues.indexOf(token.OpCodeNum)];
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return result;
|
|
127
|
+
};
|
|
128
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export * from "./build/null-data-builder";
|
|
2
|
+
export * from "./build/p2pkh-builder";
|
|
3
|
+
export * from "./build/p2stas-builder";
|
|
4
|
+
export * from "./build/script-builder";
|
|
5
|
+
|
|
6
|
+
export * from "./read/script-reader";
|
|
7
|
+
|
|
8
|
+
export * from "./script-samples";
|
|
9
|
+
export * from "./script-token";
|
|
10
|
+
export * from "./script-utils";
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { asMinimalOP, slice } from "../../buffer/buffer-utils";
|
|
2
|
+
import { OpCode } from "../../bitcoin/op-codes";
|
|
3
|
+
import { ScriptToken } from "../script-token";
|
|
4
|
+
|
|
5
|
+
export class ScriptReader {
|
|
6
|
+
static read = (source: Buffer) => {
|
|
7
|
+
const result = [];
|
|
8
|
+
|
|
9
|
+
let i = 0;
|
|
10
|
+
|
|
11
|
+
while (i < source.length) {
|
|
12
|
+
const byte = source[i];
|
|
13
|
+
|
|
14
|
+
// data chunk
|
|
15
|
+
if (byte > OpCode.OP_0 && byte <= OpCode.OP_PUSHDATA4) {
|
|
16
|
+
const d = ScriptReader.decode(source, i);
|
|
17
|
+
|
|
18
|
+
// did reading a pushDataInt fail?
|
|
19
|
+
if (d === null) return [];
|
|
20
|
+
|
|
21
|
+
i += d.size;
|
|
22
|
+
// attempt to read too much data?
|
|
23
|
+
|
|
24
|
+
if (i + d.number > source.length) return [];
|
|
25
|
+
|
|
26
|
+
const data = slice(source, i, i + d.number);
|
|
27
|
+
i += d.number;
|
|
28
|
+
|
|
29
|
+
// decompile minimally
|
|
30
|
+
const op = asMinimalOP(data);
|
|
31
|
+
|
|
32
|
+
if (op !== undefined) {
|
|
33
|
+
result.push(new ScriptToken(byte, op));
|
|
34
|
+
} else {
|
|
35
|
+
result.push(ScriptToken.fromBuffer(data));
|
|
36
|
+
}
|
|
37
|
+
// opcode
|
|
38
|
+
} else {
|
|
39
|
+
result.push(new ScriptToken(byte, byte));
|
|
40
|
+
|
|
41
|
+
i += 1;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return result;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
static decode = (buffer: Buffer, offset: number) => {
|
|
49
|
+
const opcode = buffer.readUInt8(offset);
|
|
50
|
+
let num;
|
|
51
|
+
let size;
|
|
52
|
+
// ~6 bit
|
|
53
|
+
if (opcode < OpCode.OP_PUSHDATA1) {
|
|
54
|
+
num = opcode;
|
|
55
|
+
size = 1;
|
|
56
|
+
// 8 bit
|
|
57
|
+
} else if (opcode === OpCode.OP_PUSHDATA1) {
|
|
58
|
+
if (offset + 2 > buffer.length) return null;
|
|
59
|
+
num = buffer.readUInt8(offset + 1);
|
|
60
|
+
size = 2;
|
|
61
|
+
// 16 bit
|
|
62
|
+
} else if (opcode === OpCode.OP_PUSHDATA2) {
|
|
63
|
+
if (offset + 3 > buffer.length) return null;
|
|
64
|
+
num = buffer.readUInt16LE(offset + 1);
|
|
65
|
+
size = 3;
|
|
66
|
+
// 32 bit
|
|
67
|
+
} else {
|
|
68
|
+
if (offset + 5 > buffer.length) return null;
|
|
69
|
+
if (opcode !== OpCode.OP_PUSHDATA4) throw new Error("Unexpected opcode");
|
|
70
|
+
num = buffer.readUInt32LE(offset + 1);
|
|
71
|
+
size = 5;
|
|
72
|
+
}
|
|
73
|
+
return {
|
|
74
|
+
opcode,
|
|
75
|
+
number: num,
|
|
76
|
+
size,
|
|
77
|
+
};
|
|
78
|
+
};
|
|
79
|
+
}
|