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.
Files changed (49) hide show
  1. package/jest.config.js +6 -0
  2. package/package.json +38 -0
  3. package/rollup.config.ts +26 -0
  4. package/src/base.ts +4 -0
  5. package/src/bitcoin/address.ts +42 -0
  6. package/src/bitcoin/destination.ts +7 -0
  7. package/src/bitcoin/index.ts +15 -0
  8. package/src/bitcoin/mnemonic.ts +40 -0
  9. package/src/bitcoin/network.ts +11 -0
  10. package/src/bitcoin/op-codes.ts +143 -0
  11. package/src/bitcoin/out-point.ts +61 -0
  12. package/src/bitcoin/payment.ts +8 -0
  13. package/src/bitcoin/private-key.ts +57 -0
  14. package/src/bitcoin/script-type.ts +9 -0
  15. package/src/bitcoin/sig-hash-type.ts +7 -0
  16. package/src/bitcoin/token-scheme.ts +28 -0
  17. package/src/bitcoin/transaction-input.ts +33 -0
  18. package/src/bitcoin/transaction-output.ts +105 -0
  19. package/src/bitcoin/transaction.ts +31 -0
  20. package/src/bitcoin/wallet.ts +59 -0
  21. package/src/buffer/buffer-reader.ts +88 -0
  22. package/src/buffer/buffer-utils.ts +116 -0
  23. package/src/buffer/buffer-writer.ts +74 -0
  24. package/src/buffer/index.ts +3 -0
  25. package/src/hashes.ts +13 -0
  26. package/src/index.ts +8 -0
  27. package/src/script/build/null-data-builder.ts +21 -0
  28. package/src/script/build/p2pkh-builder.ts +34 -0
  29. package/src/script/build/p2stas-builder.ts +38 -0
  30. package/src/script/build/script-builder.ts +128 -0
  31. package/src/script/index.ts +10 -0
  32. package/src/script/read/script-reader.ts +79 -0
  33. package/src/script/script-samples.ts +27 -0
  34. package/src/script/script-token.ts +66 -0
  35. package/src/script/script-utils.ts +12 -0
  36. package/src/stas-bundle-factory.ts +367 -0
  37. package/src/transaction/build/input-builder.ts +270 -0
  38. package/src/transaction/build/output-builder.ts +25 -0
  39. package/src/transaction/build/transaction-builder.ts +163 -0
  40. package/src/transaction/index.ts +4 -0
  41. package/src/transaction/read/transaction-reader.ts +54 -0
  42. package/src/transaction-factory.ts +221 -0
  43. package/tests/script-build.test.ts +41 -0
  44. package/tests/script-read.test.ts +34 -0
  45. package/tests/stas-transactios.ts +41 -0
  46. package/tests/transaction-build.test.ts +394 -0
  47. package/tests/transaction-reader.test.ts +50 -0
  48. package/tsconfig.json +110 -0
  49. package/tslint.json +3 -0
@@ -0,0 +1,270 @@
1
+ import {
2
+ cloneBuffer,
3
+ estimateChunkSize,
4
+ getChunkSize,
5
+ getNumberSize,
6
+ reverseBuffer,
7
+ splitBuffer,
8
+ } from "../../buffer/buffer-utils";
9
+ import { BufferWriter } from "../../buffer/buffer-writer";
10
+ import { OpCode } from "../../bitcoin/op-codes";
11
+ import { OutPoint } from "../../bitcoin/out-point";
12
+ import { PrivateKey } from "../../bitcoin/private-key";
13
+ import { ScriptType } from "../../bitcoin/script-type";
14
+ import { SignatureHashType } from "../../bitcoin/sig-hash-type";
15
+ import { hash256 } from "../../hashes";
16
+ import { ScriptBuilder } from "../../script/build/script-builder";
17
+ import { TransactionBuilder } from "./transaction-builder";
18
+ import { Wallet } from "../../bitcoin";
19
+
20
+ export class InputBilder {
21
+ protected TxBuilder: TransactionBuilder;
22
+ protected Owner: PrivateKey | Wallet;
23
+ protected Idx: number;
24
+
25
+ OutPoint: OutPoint;
26
+ Merge: Boolean;
27
+ UnlockingScript?: Buffer;
28
+ Sequence = TransactionBuilder.DefaultSequence;
29
+
30
+ private _mergeVout: number = 0;
31
+ private _mergeSegments: Buffer[] = [];
32
+
33
+ constructor(
34
+ txBuilder: TransactionBuilder,
35
+ outPoint: OutPoint,
36
+ signer: PrivateKey | Wallet,
37
+ merge: boolean
38
+ ) {
39
+ this.TxBuilder = txBuilder;
40
+ this.Idx = txBuilder.Inputs.length;
41
+ this.OutPoint = outPoint;
42
+ this.Owner = signer;
43
+ this.Merge = merge;
44
+ }
45
+
46
+ sign = () => {
47
+ const preimage = this.preimage(TransactionBuilder.DefaultSighashType);
48
+ const hashedPreimage = hash256(preimage);
49
+ const der = this.Owner.sign(hashedPreimage);
50
+ const derWithSigHashType = Buffer.alloc(der.length + 1);
51
+
52
+ der.copy(derWithSigHashType);
53
+ derWithSigHashType.writeInt8(
54
+ TransactionBuilder.DefaultSighashType,
55
+ der.length
56
+ );
57
+
58
+ if (this.OutPoint.ScriptType === ScriptType.p2pkh) {
59
+ const size =
60
+ getChunkSize(derWithSigHashType) + getChunkSize(this.Owner.PublicKey);
61
+ const buffer = Buffer.alloc(size);
62
+ const bufferWriter = new BufferWriter(buffer);
63
+
64
+ bufferWriter.writeVarChunk(derWithSigHashType);
65
+ bufferWriter.writeVarChunk(this.Owner.PublicKey);
66
+
67
+ this.UnlockingScript = buffer;
68
+ } else if (this.OutPoint.ScriptType === ScriptType.p2stas) {
69
+ this.prepareMergeInfo();
70
+
71
+ const script = new ScriptBuilder(ScriptType.p2stas);
72
+ let hasNote = false;
73
+
74
+ for (const output of this.TxBuilder.Outputs) {
75
+ if (output.LockingScript.ScriptType === ScriptType.nullData) {
76
+ const nulldata = output.LockingScript.toBuffer();
77
+ const payload = Buffer.alloc(nulldata.length - 2);
78
+
79
+ nulldata.copy(payload, 0, 2);
80
+ script.addData(payload);
81
+
82
+ hasNote = true;
83
+ } else {
84
+ script
85
+ .addNumber(output.Satoshis)
86
+ .addData(output.LockingScript.ToAddress!.Hash160);
87
+ }
88
+ }
89
+
90
+ if (!hasNote) script.addOpCode(OpCode.OP_0);
91
+
92
+ var fundingInput =
93
+ this.TxBuilder.Inputs[this.TxBuilder.Inputs.length - 1];
94
+
95
+ script
96
+ .addNumber(fundingInput.OutPoint.Vout)
97
+ .addData(reverseBuffer(Buffer.from(fundingInput.OutPoint.TxId, "hex")));
98
+
99
+ if (this.Merge) {
100
+ script
101
+ .addNumber(this._mergeVout)
102
+ .addDatas(this._mergeSegments)
103
+ .addNumber(this._mergeSegments.length);
104
+ } else {
105
+ script.addOpCode(OpCode.OP_0);
106
+ }
107
+
108
+ script
109
+ .addData(preimage)
110
+ .addData(derWithSigHashType)
111
+ .addData(this.Owner.PublicKey);
112
+
113
+ this.UnlockingScript = script.toBuffer();
114
+ }
115
+ };
116
+
117
+ writeTo(bufferWriter: BufferWriter) {
118
+ bufferWriter.writeChunk(
119
+ reverseBuffer(Buffer.from(this.OutPoint.TxId, "hex"))
120
+ );
121
+ bufferWriter.writeUInt32(this.OutPoint.Vout);
122
+ bufferWriter.writeVarChunk(this.UnlockingScript!);
123
+ bufferWriter.writeUInt32(this.Sequence);
124
+ }
125
+
126
+ size = () =>
127
+ 32 + // TX.Id
128
+ 4 + // Vout
129
+ this.unlockingScriptSize() +
130
+ 4; // Sequence
131
+
132
+ preimageLength = (): number =>
133
+ 4 + // Tx version
134
+ 32 + // Prevout hash
135
+ 32 + // Sequence hash
136
+ 32 + // Output Tx id
137
+ 4 + // VOUT ;
138
+ getChunkSize(this.OutPoint.LockignScript) +
139
+ 8 + // Satoshis
140
+ 4 + // Sequence
141
+ 32 + //Outputs hash
142
+ 4 + // Lock time
143
+ 4; // Signature type
144
+
145
+ stasNullDataLength = () => {
146
+ const nullDataOutput = this.TxBuilder.Outputs.find(
147
+ (x) => x.LockingScript.ScriptType === ScriptType.nullData
148
+ );
149
+
150
+ if (!nullDataOutput) return 1;
151
+
152
+ return estimateChunkSize(nullDataOutput.LockingScript.size() - 2);
153
+ };
154
+
155
+ prevoutHashLength = () => (32 + 4) * this.TxBuilder.Inputs.length;
156
+
157
+ unlockingScriptSize = (): number => {
158
+ if (this.UnlockingScript !== undefined) {
159
+ return estimateChunkSize(this.UnlockingScript.length);
160
+ }
161
+
162
+ let size =
163
+ 1 + // OP_PUSH
164
+ 73 + // DER-encoded signature (70-73 bytes)
165
+ 1 + // OP_PUSH
166
+ 33; // Public Key
167
+
168
+ if (this.OutPoint.ScriptType === ScriptType.p2stas) {
169
+ this.prepareMergeInfo();
170
+
171
+ const fundingIdx = this.TxBuilder.Inputs.length - 1;
172
+ const fundingOutpoint = this.TxBuilder.Inputs[fundingIdx].OutPoint;
173
+
174
+ size += this.stasNullDataLength();
175
+ size += this.TxBuilder.Outputs.reduce((a, x) => {
176
+ if (x.LockingScript.ScriptType === ScriptType.nullData) return a;
177
+
178
+ return a + getNumberSize(x.Satoshis) + 21;
179
+ }, 0);
180
+
181
+ size += getNumberSize(fundingOutpoint.Vout);
182
+ size += estimateChunkSize(32); // Funding Tx vout
183
+ size += estimateChunkSize(this.preimageLength());
184
+
185
+ if (!this.Merge) {
186
+ size += 1; // OP_0
187
+ } else {
188
+ size += getNumberSize(this._mergeVout);
189
+ size += getNumberSize(this._mergeSegments.length);
190
+ size += this._mergeSegments.reduce((a, x) => getChunkSize(x) + a, 0);
191
+ }
192
+ }
193
+
194
+ return estimateChunkSize(size);
195
+ };
196
+
197
+ /// <summary>
198
+ /// Only SIGHASH_ALL|FORK_ID implemented
199
+ /// </summary>
200
+ preimage = (signatureHashType: SignatureHashType) => {
201
+ const size = this.preimageLength();
202
+ const buffer = Buffer.alloc(size);
203
+ var writer = new BufferWriter(buffer);
204
+
205
+ writer.writeUInt32(this.TxBuilder.Version); // 4
206
+ this.writePrevoutHash(writer); // 32
207
+ this.writeSequenceHash(writer); // 32
208
+ writer.writeChunk(reverseBuffer(Buffer.from(this.OutPoint.TxId, "hex"))); // 32
209
+ writer.writeUInt32(this.OutPoint.Vout); // 4
210
+ writer.writeVarChunk(this.OutPoint.LockignScript);
211
+ writer.writeUInt64(this.OutPoint.Satoshis); // 8
212
+ writer.writeUInt32(this.Sequence); // 4
213
+ this.writeOutputsHash(writer); // 32
214
+ writer.writeUInt32(this.TxBuilder.LockTime); // 4
215
+ writer.writeUInt32(signatureHashType); // 4
216
+
217
+ return buffer;
218
+ };
219
+
220
+ private writePrevoutHash = (bufferWriter: BufferWriter) => {
221
+ const size = this.prevoutHashLength();
222
+ const buffer = Buffer.alloc(size);
223
+ const writer = new BufferWriter(buffer);
224
+
225
+ for (const input of this.TxBuilder.Inputs) {
226
+ writer.writeChunk(reverseBuffer(Buffer.from(input.OutPoint.TxId, "hex")));
227
+ writer.writeUInt32(input.OutPoint.Vout);
228
+ }
229
+
230
+ bufferWriter.writeChunk(hash256(buffer));
231
+ };
232
+
233
+ private writeSequenceHash = (bufferWriter: BufferWriter) => {
234
+ const buffer = Buffer.alloc(4 * this.TxBuilder.Inputs.length);
235
+ const writer = new BufferWriter(buffer);
236
+
237
+ for (const input of this.TxBuilder.Inputs)
238
+ writer.writeUInt32(input.Sequence);
239
+
240
+ bufferWriter.writeChunk(hash256(buffer));
241
+ };
242
+
243
+ private writeOutputsHash = (bufferWriter: BufferWriter) => {
244
+ var size = this.TxBuilder.Outputs.reduce((a, x) => a + x.size(), 0);
245
+
246
+ const buffer = Buffer.alloc(size);
247
+ const writer = new BufferWriter(buffer);
248
+
249
+ for (const output of this.TxBuilder.Outputs) {
250
+ writer.writeUInt64(output.Satoshis);
251
+ writer.writeVarChunk(output.LockingScript.toBuffer());
252
+ }
253
+
254
+ bufferWriter.writeChunk(hash256(buffer));
255
+ };
256
+
257
+ private prepareMergeInfo = () => {
258
+ if (!this.Merge || this._mergeSegments.length > 0) return;
259
+
260
+ const lockingScript = this.TxBuilder.Inputs[0].OutPoint.LockignScript;
261
+ const scriptToCut = cloneBuffer(lockingScript, 0, 23);
262
+ const mergeUtxo = this.TxBuilder.Inputs[this.Idx === 0 ? 1 : 0];
263
+
264
+ this._mergeVout = mergeUtxo.OutPoint.Vout;
265
+ this._mergeSegments = splitBuffer(
266
+ mergeUtxo.OutPoint.Transaction!.Raw,
267
+ scriptToCut
268
+ ).reverse();
269
+ };
270
+ }
@@ -0,0 +1,25 @@
1
+ import { estimateChunkSize } from "../../buffer/buffer-utils";
2
+ import { BufferWriter } from "../../buffer/buffer-writer";
3
+ import { ScriptBuilder } from "../../script/build/script-builder";
4
+
5
+ export class OutputBuilder {
6
+ Satoshis: number;
7
+ LockingScript: ScriptBuilder;
8
+
9
+ constructor(lockingScript: ScriptBuilder, satoshis: number) {
10
+ this.LockingScript = lockingScript;
11
+ this.Satoshis = satoshis;
12
+ }
13
+
14
+ size() {
15
+ return (
16
+ 8 + // satoshis Size, always 8 bytes
17
+ estimateChunkSize(this.LockingScript.size())
18
+ );
19
+ }
20
+
21
+ writeTo(bufferWriter: BufferWriter) {
22
+ bufferWriter.writeUInt64(this.Satoshis);
23
+ bufferWriter.writeVarChunk(this.LockingScript.toBuffer());
24
+ }
25
+ }
@@ -0,0 +1,163 @@
1
+ import { getVarIntLength } 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 { OutPoint } from "../../bitcoin/out-point";
6
+ import { PrivateKey } from "../../bitcoin/private-key";
7
+ import { SignatureHashType } from "../../bitcoin/sig-hash-type";
8
+ import { TokenScheme } from "../../bitcoin/token-scheme";
9
+ import { NullDataBuilder } from "../../script/build/null-data-builder";
10
+ import { P2pkhBuilder } from "../../script/build/p2pkh-builder";
11
+ import { P2stasBuilder } from "../../script/build/p2stas-builder";
12
+ import { ScriptReader } from "../../script/read/script-reader";
13
+ import { InputBilder } from "./input-builder";
14
+ import { OutputBuilder } from "./output-builder";
15
+ import { Wallet } from "../../bitcoin";
16
+
17
+ export class TransactionBuilder {
18
+ static DefaultSequence = 0xffffffff;
19
+ static DefaultSighashType =
20
+ SignatureHashType.SIGHASH_ALL | SignatureHashType.SIGHASH_FORKID;
21
+
22
+ Inputs: InputBilder[] = [];
23
+ Outputs: OutputBuilder[] = [];
24
+
25
+ Version = 1;
26
+ LockTime = 0;
27
+
28
+ static init = () => new TransactionBuilder();
29
+
30
+ size = () =>
31
+ 4 + // version
32
+ 4 + // locktime
33
+ getVarIntLength(this.Inputs.length) +
34
+ this.Inputs.reduce((a, x) => a + x.size(), 0) +
35
+ getVarIntLength(this.Outputs.length) +
36
+ this.Outputs.reduce((a, x) => a + x.size(), 0);
37
+
38
+ getFee = (satoshisPerByte: number) =>
39
+ Math.ceil(this.size() * satoshisPerByte);
40
+
41
+ addInput = (outPoint: OutPoint, signer: PrivateKey | Wallet) => {
42
+ this.Inputs.push(new InputBilder(this, outPoint, signer, false));
43
+
44
+ return this;
45
+ };
46
+
47
+ addStasMergeInput = (outPoint: OutPoint, signer: PrivateKey | Wallet) => {
48
+ this.Inputs.push(new InputBilder(this, outPoint, signer, true));
49
+
50
+ return this;
51
+ };
52
+
53
+ addP2PkhOutput = (value: number, to: Address, data: Buffer[] = []) => {
54
+ const script = new P2pkhBuilder(to);
55
+
56
+ for (const d of data) {
57
+ script.addReturnData(d);
58
+ }
59
+
60
+ this.Outputs.push(new OutputBuilder(script, value));
61
+
62
+ return this;
63
+ };
64
+
65
+ addNullDataOutput(data: Buffer[]) {
66
+ const script = new NullDataBuilder(data);
67
+
68
+ this.Outputs.push(new OutputBuilder(script, 0));
69
+
70
+ return this;
71
+ }
72
+
73
+ addChangeOutputWithFee(
74
+ to: Address,
75
+ change: number,
76
+ satoshisPerByte: number,
77
+ idx: number | null = null
78
+ ) {
79
+ const script = new P2pkhBuilder(to);
80
+ const output = new OutputBuilder(script, change);
81
+
82
+ if (idx !== null) this.Outputs.splice(idx, 0, output);
83
+ else this.Outputs.push(output);
84
+
85
+ let fee = this.getFee(satoshisPerByte);
86
+
87
+ if (fee >= change) throw new Error("Insufficient satoshis to pay fee");
88
+
89
+ output.Satoshis = change - fee;
90
+
91
+ return this;
92
+ }
93
+
94
+ addStasOutputByScheme = (
95
+ schema: TokenScheme,
96
+ satoshis: number,
97
+ to: Address,
98
+ data: Buffer[] = []
99
+ ) => {
100
+ const script = new P2stasBuilder(to, schema.TokenId, schema.Symbol);
101
+
102
+ for (const d of data) {
103
+ script.addData(d);
104
+ }
105
+
106
+ this.Outputs.push(new OutputBuilder(script, satoshis));
107
+
108
+ return this;
109
+ };
110
+
111
+ addStasOutputByPrevLockingScript = (
112
+ satoshis: number,
113
+ to: Address,
114
+ prevStasLockingScript: Buffer
115
+ ) => {
116
+ const prevScriptTokens = ScriptReader.read(prevStasLockingScript);
117
+ const opReturnIdx = prevScriptTokens.findIndex(
118
+ (x) => x.OpCodeNum === OpCode.OP_RETURN
119
+ );
120
+
121
+ const toknenId = prevScriptTokens[opReturnIdx + 1].Data!.toString("hex");
122
+ const symbol = prevScriptTokens[opReturnIdx + 2].Data!.toString("utf8");
123
+ const data: Buffer[] = [];
124
+
125
+ for (let i = opReturnIdx + 3; i < prevScriptTokens.length; i++) {
126
+ data.push(prevScriptTokens[i].Data!);
127
+ }
128
+
129
+ const script = new P2stasBuilder(to, toknenId, symbol, data);
130
+
131
+ this.Outputs.push(new OutputBuilder(script, satoshis));
132
+
133
+ return this;
134
+ };
135
+
136
+ sign = () => {
137
+ for (const input of this.Inputs) {
138
+ input.sign();
139
+ }
140
+
141
+ return this;
142
+ };
143
+
144
+ toBuffer = () => {
145
+ const size = this.size();
146
+ const buffer = Buffer.alloc(size);
147
+ const bufferWriter = new BufferWriter(buffer);
148
+
149
+ bufferWriter.writeUInt32(this.Version);
150
+
151
+ bufferWriter.writeVarInt(this.Inputs.length);
152
+ for (const input of this.Inputs) input.writeTo(bufferWriter);
153
+
154
+ bufferWriter.writeVarInt(this.Outputs.length);
155
+ for (const output of this.Outputs) output.writeTo(bufferWriter);
156
+
157
+ bufferWriter.writeUInt32(this.LockTime);
158
+
159
+ return buffer;
160
+ };
161
+
162
+ toHex = () => this.toBuffer().toString("hex");
163
+ }
@@ -0,0 +1,4 @@
1
+ export * from "./build/input-builder";
2
+ export * from "./build/output-builder";
3
+ export * from "./build/transaction-builder";
4
+ export * from "./read/transaction-reader";
@@ -0,0 +1,54 @@
1
+ import { BufferReader } from "../../buffer/buffer-reader";
2
+ import { reverseBuffer } from "../../buffer/buffer-utils";
3
+ import { Transaction } from "../../bitcoin/transaction";
4
+ import { TransactionInput } from "../../bitcoin/transaction-input";
5
+ import { TransactionOutput } from "../../bitcoin/transaction-output";
6
+
7
+ export class TransactionReader {
8
+ static readHex = (raw: string) =>
9
+ TransactionReader.readBuffer(Buffer.from(raw, "hex"));
10
+
11
+ static readBuffer = (buffer: Buffer) => {
12
+ const reader = new BufferReader(buffer);
13
+
14
+ const version = reader.readUInt32();
15
+ const inputCount = reader.readVarInt();
16
+ const inputs = [];
17
+
18
+ for (let i = 0; i < inputCount; i++) {
19
+ inputs.push(TransactionReader.readInput(reader));
20
+ }
21
+
22
+ const outputCount = reader.readVarInt();
23
+ const outputs = [];
24
+
25
+ for (let i = 0; i < outputCount; i++) {
26
+ outputs.push(TransactionReader.readOutput(reader));
27
+ }
28
+
29
+ const lockTime = reader.readUInt32();
30
+
31
+ return new Transaction(buffer, inputs, outputs, version, lockTime);
32
+ };
33
+
34
+ static readInput = (reader: BufferReader): TransactionInput => {
35
+ const txId = reverseBuffer(reader.readChunk(32));
36
+ const vout = reader.readUInt32();
37
+ const unlockingScript = reader.readVarChunk();
38
+ const sequence = reader.readUInt32();
39
+
40
+ return new TransactionInput(
41
+ txId.toString("hex"),
42
+ vout,
43
+ unlockingScript,
44
+ sequence
45
+ );
46
+ };
47
+
48
+ static readOutput = (reader: BufferReader): TransactionOutput => {
49
+ const satoshis = reader.readUInt64();
50
+ const lockignScript = reader.readVarChunk();
51
+
52
+ return new TransactionOutput(satoshis, lockignScript);
53
+ };
54
+ }