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,27 @@
|
|
|
1
|
+
import { OpCode } from "../bitcoin/op-codes";
|
|
2
|
+
import { ScriptReader } from "./read/script-reader";
|
|
3
|
+
import { ScriptToken } from "./script-token";
|
|
4
|
+
|
|
5
|
+
export const nullDataTokens = [ScriptToken.forSample(OpCode.OP_0)];
|
|
6
|
+
|
|
7
|
+
export const p2phkTokens = [
|
|
8
|
+
new ScriptToken(OpCode.OP_DUP),
|
|
9
|
+
new ScriptToken(OpCode.OP_HASH160),
|
|
10
|
+
ScriptToken.forSample(20, 20, true),
|
|
11
|
+
new ScriptToken(OpCode.OP_EQUALVERIFY),
|
|
12
|
+
new ScriptToken(OpCode.OP_CHECKSIG),
|
|
13
|
+
];
|
|
14
|
+
|
|
15
|
+
export const p2stasSampleHex =
|
|
16
|
+
"76a914001122334455667788990011223344556677889988ac6976aa607f5f7f7c5e7f7c5d7f7c5c7f7c5b7f7c5a7f7c597f7c587f7c577f7c567f7c557f7c547f7c537f7c527f7c517f7c7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7c5f7f7c5e7f7c5d7f7c5c7f7c5b7f7c5a7f7c597f7c587f7c577f7c567f7c557f7c547f7c537f7c527f7c517f7c7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e011f7f7d7e01007e8111414136d08c5ed2bf3ba048afe6dcaebafe01005f80837e01007e7652967b537a7601ff877c0100879b7d648b6752799368537a7d9776547aa06394677768263044022079be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f8179802207c607f5f7f7c5e7f7c5d7f7c5c7f7c5b7f7c5a7f7c597f7c587f7c577f7c567f7c557f7c547f7c537f7c527f7c517f7c7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7c5f7f7c5e7f7c5d7f7c5c7f7c5b7f7c5a7f7c597f7c587f7c577f7c567f7c557f7c547f7c537f7c527f7c517f7c7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e01417e7c6421038ff83d8cf12121491609c4939dc11c4aa35503508fe432dc5a5c1905608b92186721023635954789a02e39fb7e54440b6f528d53efd65635ddad7f3c4085f97fdbdc4868ad547f7701207f01207f7701247f517f7801007e02fd00a063546752687f7801007e817f727e7b537f7701147f76020c057f7701147f757b876b7b557a766471567a577a786354807e7e676d68aa880067765158a569765187645294567a5379587a7e7e78637c8c7c53797e577a7e6878637c8c7c53797e577a7e6878637c8c7c53797e577a7e6878637c8c7c53797e577a7e6878637c8c7c53797e577a7e6867567a6876aa587a7d54807e577a597a5a7a786354807e6f7e7eaa727c7e676d6e7eaa7c687b7eaa587a7d877663516752687c72879b69537a6491687c7b547f77517f7853a0916901247f77517f7c01007e817602fc00a06302fd00a063546752687f7c01007e816854937f77788c6301247f77517f7c01007e817602fc00a06302fd00a063546752687f7c01007e816854937f777852946301247f77517f7c01007e817602fc00a06302fd00a063546752687f7c01007e816854937f77686877517f7c52797d8b9f7c53a09b91697c76638c7c587f77517f7c01007e817602fc00a06302fd00a063546752687f7c01007e81687f777c6876638c7c587f77517f7c01007e817602fc00a06302fd00a063546752687f7c01007e81687f777c6863587f77517f7c01007e817602fc00a06302fd00a063546752687f7c01007e81687f7768587f517f7801007e817602fc00a06302fd00a063546752687f7801007e81727e7b7b687f75537f7c0376a9148801147f775379645579887567726881687863547a677b68587f7c815379635379528763547a6b547a6b7b6b67567a6b567a6b6b7c68677b93687c547f7701207f75748c7a7669765880041976a9147858790376a9147e7e748c7a7d7e5879727e0288ac727e547a00587a64745da0637c748c7a76697d937b7b58807e59790376a9147e748c7a7e59797e7e68676c766976748c7a9d58807e6c0376a9147e748c7a7e6c7e7e68745da0637c748c7a76697d937b7b58807e59790376a9147e748c7a7e59797e7e68745da0637c748c7a76697d937b7b58807e59790376a9147e748c7a7e59797e7e687c577a9d7d7e5979635a795880041976a9145b797e0288ac7e7e6700687d7e597a766302006a7c7e827602fc00a06301fd7c7e536751687f757c7e0058807c7e687d7eaa6b7e7e7e7e7eaa78877c6c877c6c9a9b726d7777";
|
|
17
|
+
|
|
18
|
+
let stasTokens: ScriptToken[] | null = null;
|
|
19
|
+
|
|
20
|
+
export const getP2stasTokens = () => {
|
|
21
|
+
if (stasTokens === null) {
|
|
22
|
+
stasTokens = ScriptReader.read(Buffer.from(p2stasSampleHex, "hex"));
|
|
23
|
+
stasTokens[2].IsReceiverId = true;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return stasTokens;
|
|
27
|
+
};
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { OpCode } from "../bitcoin/op-codes";
|
|
2
|
+
import { isOpCode } from "./script-utils";
|
|
3
|
+
|
|
4
|
+
export class ScriptToken {
|
|
5
|
+
OpCodeNum: number;
|
|
6
|
+
OpCode?: OpCode;
|
|
7
|
+
Data?: Buffer;
|
|
8
|
+
DataLength: number = 0;
|
|
9
|
+
IsReceiverId: boolean = false;
|
|
10
|
+
|
|
11
|
+
constructor(opCodeNum: number, opCode?: OpCode) {
|
|
12
|
+
this.OpCode = opCode;
|
|
13
|
+
this.OpCodeNum = opCodeNum;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
static fromBuffer(buffer: Buffer) {
|
|
17
|
+
const opCodeNum =
|
|
18
|
+
buffer.length === 0
|
|
19
|
+
? -1
|
|
20
|
+
: buffer.length < 76
|
|
21
|
+
? buffer.length
|
|
22
|
+
: buffer.length <= 255
|
|
23
|
+
? OpCode.OP_PUSHDATA1
|
|
24
|
+
: buffer.length <= 65535
|
|
25
|
+
? OpCode.OP_PUSHDATA2
|
|
26
|
+
: buffer.length <= 4294967295
|
|
27
|
+
? OpCode.OP_PUSHDATA4
|
|
28
|
+
: -1;
|
|
29
|
+
|
|
30
|
+
if (opCodeNum === -1) throw new Error(`No data provided: ${buffer.length}`);
|
|
31
|
+
|
|
32
|
+
const token = new ScriptToken(opCodeNum);
|
|
33
|
+
|
|
34
|
+
token.Data = buffer;
|
|
35
|
+
token.DataLength = buffer.length;
|
|
36
|
+
|
|
37
|
+
return token;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
static fromScriptToken(from: ScriptToken) {
|
|
41
|
+
const token = from.Data
|
|
42
|
+
? ScriptToken.fromBuffer(from.Data)
|
|
43
|
+
: new ScriptToken(from.OpCodeNum, from.OpCode);
|
|
44
|
+
|
|
45
|
+
token.IsReceiverId = from.IsReceiverId;
|
|
46
|
+
|
|
47
|
+
return token;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
static forSample(
|
|
51
|
+
opCodeNum: number,
|
|
52
|
+
dataLength: number = 0,
|
|
53
|
+
isReceiverId: boolean = false
|
|
54
|
+
) {
|
|
55
|
+
const token = new ScriptToken(opCodeNum);
|
|
56
|
+
|
|
57
|
+
const { valid, opCode } = isOpCode(opCodeNum);
|
|
58
|
+
|
|
59
|
+
if (valid === false) token.OpCode = opCode;
|
|
60
|
+
|
|
61
|
+
token.DataLength = dataLength;
|
|
62
|
+
token.IsReceiverId = isReceiverId;
|
|
63
|
+
|
|
64
|
+
return token;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { OpCode } from "../bitcoin/op-codes";
|
|
2
|
+
|
|
3
|
+
export const isOpCode = (opCodeNum: number) => {
|
|
4
|
+
if (
|
|
5
|
+
opCodeNum === OpCode.OP_0 ||
|
|
6
|
+
(opCodeNum >= OpCode.OP_PUSHDATA1 && opCodeNum <= OpCode.OP_INVALIDOPCODE)
|
|
7
|
+
) {
|
|
8
|
+
return { valid: true, opCode: opCodeNum };
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
return { valid: false, opCode: OpCode.OP_INVALIDOPCODE };
|
|
12
|
+
};
|
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Address,
|
|
3
|
+
OutPoint,
|
|
4
|
+
ScriptType,
|
|
5
|
+
TDestination,
|
|
6
|
+
TokenScheme,
|
|
7
|
+
TPayment,
|
|
8
|
+
Transaction,
|
|
9
|
+
Wallet,
|
|
10
|
+
} from "./bitcoin";
|
|
11
|
+
import { TransactionBuilder, TransactionReader } from "./transaction";
|
|
12
|
+
import {
|
|
13
|
+
BuildMergeTx,
|
|
14
|
+
BuildSplitTx,
|
|
15
|
+
BuildTransferTx,
|
|
16
|
+
FeeRate,
|
|
17
|
+
} from "./transaction-factory";
|
|
18
|
+
|
|
19
|
+
export const AvgFeeForMerge = 500;
|
|
20
|
+
|
|
21
|
+
export type TGetUtxoFunction = (satoshis?: number) => Promise<OutPoint[]>;
|
|
22
|
+
export type TGetTransactionsFunction = (
|
|
23
|
+
ids: string[]
|
|
24
|
+
) => Promise<Record<string, Transaction>>;
|
|
25
|
+
export type TStasPayoutBundle = {
|
|
26
|
+
transactions?: string[];
|
|
27
|
+
feeSatoshis: number;
|
|
28
|
+
message?: string;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export class StasBundleFactory {
|
|
32
|
+
constructor(
|
|
33
|
+
private readonly tokenScheme: TokenScheme,
|
|
34
|
+
private readonly stasWallet: Wallet,
|
|
35
|
+
private readonly feeWallet: Wallet,
|
|
36
|
+
private readonly getFeeUtxoSet: TGetUtxoFunction,
|
|
37
|
+
private readonly getStasUtxoSet: TGetUtxoFunction,
|
|
38
|
+
private readonly getTransactions: TGetTransactionsFunction
|
|
39
|
+
) {}
|
|
40
|
+
|
|
41
|
+
public createBundle = async (
|
|
42
|
+
amount: number,
|
|
43
|
+
to: Address,
|
|
44
|
+
note?: Buffer[]
|
|
45
|
+
): Promise<TStasPayoutBundle> => {
|
|
46
|
+
const satoshisToSend = Math.round(
|
|
47
|
+
amount * this.tokenScheme.SatoshisPerToken
|
|
48
|
+
);
|
|
49
|
+
const stasUtxoSet = (await this.getStasUtxoSet(satoshisToSend)).sort(
|
|
50
|
+
(a, b) => a.Satoshis - b.Satoshis
|
|
51
|
+
);
|
|
52
|
+
const availableSatoshis = stasUtxoSet.reduce((a, x) => a + x.Satoshis, 0);
|
|
53
|
+
|
|
54
|
+
if (availableSatoshis < satoshisToSend)
|
|
55
|
+
return {
|
|
56
|
+
message: "Insufficient STAS tokens balance",
|
|
57
|
+
feeSatoshis: 0,
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const stasUtxos = this.getStasUtxo(stasUtxoSet, satoshisToSend);
|
|
61
|
+
|
|
62
|
+
let { feeSatoshis: estimatedFee } = await this._createBundle(
|
|
63
|
+
[],
|
|
64
|
+
stasUtxos,
|
|
65
|
+
satoshisToSend,
|
|
66
|
+
new OutPoint(
|
|
67
|
+
"4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b",
|
|
68
|
+
0,
|
|
69
|
+
Buffer.from(
|
|
70
|
+
"76a91462e907b15cbf27d5425399ebf6f0fb50ebb88f1888ac",
|
|
71
|
+
"hex"
|
|
72
|
+
),
|
|
73
|
+
5000000000,
|
|
74
|
+
this.feeWallet.Address,
|
|
75
|
+
ScriptType.p2pkh
|
|
76
|
+
),
|
|
77
|
+
to,
|
|
78
|
+
note
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
estimatedFee =
|
|
82
|
+
estimatedFee + stasUtxos.length * 9 + 1; /* Fee for fee transactio */
|
|
83
|
+
const feeUtxoSet = await this.getFeeUtxoSet();
|
|
84
|
+
const feeSatoshis = feeUtxoSet.reduce((a, x) => a + x.Satoshis, 0);
|
|
85
|
+
|
|
86
|
+
if (estimatedFee > feeSatoshis) {
|
|
87
|
+
return {
|
|
88
|
+
message: "Insufficient balance to pay fee",
|
|
89
|
+
feeSatoshis: 0,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const transactions: string[] = [];
|
|
94
|
+
const { feeTransaction, feeUtxo } = this.buildFeeTransaction(
|
|
95
|
+
feeUtxoSet,
|
|
96
|
+
estimatedFee
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
if (feeTransaction) transactions.push(feeTransaction);
|
|
100
|
+
|
|
101
|
+
return await this._createBundle(
|
|
102
|
+
transactions,
|
|
103
|
+
stasUtxos,
|
|
104
|
+
satoshisToSend,
|
|
105
|
+
feeUtxo,
|
|
106
|
+
to,
|
|
107
|
+
note
|
|
108
|
+
);
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
private _createBundle = async (
|
|
112
|
+
transactions: string[],
|
|
113
|
+
stasUtxos: OutPoint[],
|
|
114
|
+
satoshisToSend: number,
|
|
115
|
+
feeUtxo: OutPoint,
|
|
116
|
+
to: Address,
|
|
117
|
+
note?: Buffer[]
|
|
118
|
+
) => {
|
|
119
|
+
const { mergeTransactions, mergeFeeUtxo, stasUtxo } =
|
|
120
|
+
await this.mergeStasTransactions(stasUtxos, satoshisToSend, feeUtxo);
|
|
121
|
+
|
|
122
|
+
if (mergeTransactions) {
|
|
123
|
+
for (const mergeTx of mergeTransactions) {
|
|
124
|
+
transactions.push(mergeTx);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (stasUtxo.Satoshis === satoshisToSend) {
|
|
129
|
+
transactions.push(
|
|
130
|
+
this.buildTransferTransaction(stasUtxo, mergeFeeUtxo, to, note)
|
|
131
|
+
);
|
|
132
|
+
} else {
|
|
133
|
+
transactions.push(
|
|
134
|
+
this.buildSplitTransaction(
|
|
135
|
+
stasUtxo,
|
|
136
|
+
satoshisToSend,
|
|
137
|
+
to,
|
|
138
|
+
mergeFeeUtxo,
|
|
139
|
+
note
|
|
140
|
+
)
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const transferTx = TransactionReader.readHex(
|
|
145
|
+
transactions[transactions.length - 1]
|
|
146
|
+
);
|
|
147
|
+
const feeUtxoIdx = note
|
|
148
|
+
? transferTx.Outputs.length - 2
|
|
149
|
+
: transferTx.Outputs.length - 1;
|
|
150
|
+
const paidFee = feeUtxo.Satoshis - transferTx.Outputs[feeUtxoIdx].Satoshis;
|
|
151
|
+
|
|
152
|
+
return { transactions, feeSatoshis: paidFee };
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
private getStasUtxo = (utxos: OutPoint[], satoshis: number): OutPoint[] => {
|
|
156
|
+
const exactOrGreater = utxos.find((x) => x.Satoshis >= satoshis);
|
|
157
|
+
|
|
158
|
+
if (exactOrGreater && exactOrGreater.Satoshis === satoshis) {
|
|
159
|
+
return [exactOrGreater];
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const result: OutPoint[] = [];
|
|
163
|
+
let accumulated = 0;
|
|
164
|
+
for (const utxo of utxos) {
|
|
165
|
+
result.push(utxo);
|
|
166
|
+
accumulated += utxo.Satoshis;
|
|
167
|
+
|
|
168
|
+
if (accumulated >= satoshis) return result;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return [exactOrGreater!];
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
private buildFeeTransaction = (
|
|
175
|
+
utxos: OutPoint[],
|
|
176
|
+
satoshis: number
|
|
177
|
+
): {
|
|
178
|
+
feeTransaction?: string;
|
|
179
|
+
feeUtxo: OutPoint;
|
|
180
|
+
} => {
|
|
181
|
+
if (utxos.length === 1)
|
|
182
|
+
return {
|
|
183
|
+
feeUtxo: utxos[0],
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
const txBuilder = TransactionBuilder.init().addP2PkhOutput(
|
|
187
|
+
0,
|
|
188
|
+
this.feeWallet.Address
|
|
189
|
+
);
|
|
190
|
+
let accumulated = 0;
|
|
191
|
+
|
|
192
|
+
for (const utxo of utxos) {
|
|
193
|
+
txBuilder.addInput(utxo, this.feeWallet);
|
|
194
|
+
|
|
195
|
+
const fee = txBuilder.getFee(FeeRate);
|
|
196
|
+
|
|
197
|
+
accumulated += utxo.Satoshis;
|
|
198
|
+
|
|
199
|
+
if (accumulated - fee >= satoshis) break;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
txBuilder.Outputs = [];
|
|
203
|
+
|
|
204
|
+
const result = txBuilder
|
|
205
|
+
.addChangeOutputWithFee(this.feeWallet.Address, accumulated, FeeRate)
|
|
206
|
+
.sign()
|
|
207
|
+
.toHex();
|
|
208
|
+
|
|
209
|
+
return { feeTransaction: result, feeUtxo: OutPoint.fromHex(result, 0) };
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
private mergeStasTransactions = async (
|
|
213
|
+
stasUtxos: OutPoint[],
|
|
214
|
+
satoshis: number,
|
|
215
|
+
mergeFeeUtxo: OutPoint
|
|
216
|
+
): Promise<{
|
|
217
|
+
mergeTransactions?: string[];
|
|
218
|
+
stasUtxo: OutPoint;
|
|
219
|
+
mergeFeeUtxo: OutPoint;
|
|
220
|
+
}> => {
|
|
221
|
+
if (stasUtxos.length === 1) return { mergeFeeUtxo, stasUtxo: stasUtxos[0] };
|
|
222
|
+
|
|
223
|
+
const mergeTransactions: string[] = [];
|
|
224
|
+
const utxos = stasUtxos.map(({ TxId, Vout }) => ({ TxId, Vout }));
|
|
225
|
+
const txIds = Array.from(new Set(stasUtxos.map(({ TxId }) => TxId)));
|
|
226
|
+
const sourceTransactions = await this.getTransactions(txIds);
|
|
227
|
+
const mergeLevels: OutPoint[][] = [[]];
|
|
228
|
+
|
|
229
|
+
for (const { TxId, Vout } of utxos) {
|
|
230
|
+
const tx = sourceTransactions[TxId];
|
|
231
|
+
|
|
232
|
+
if (!tx) throw new Error(`Transaction ${TxId} not found`);
|
|
233
|
+
|
|
234
|
+
mergeLevels[0].push(OutPoint.fromTransaction(tx, Vout));
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
let feePayment: TPayment = {
|
|
238
|
+
OutPoint: mergeFeeUtxo,
|
|
239
|
+
Owner: this.feeWallet,
|
|
240
|
+
};
|
|
241
|
+
let currentLevel = mergeLevels[0];
|
|
242
|
+
let levelsBeforeTransfer = 0;
|
|
243
|
+
let stasUtxo = stasUtxos[0];
|
|
244
|
+
|
|
245
|
+
while (currentLevel.length !== 1) {
|
|
246
|
+
const newLevel: OutPoint[] = [];
|
|
247
|
+
mergeLevels.push(newLevel);
|
|
248
|
+
|
|
249
|
+
if (levelsBeforeTransfer === 3) {
|
|
250
|
+
levelsBeforeTransfer = 0;
|
|
251
|
+
|
|
252
|
+
for (let outPoint of currentLevel) {
|
|
253
|
+
const stasPayment: TPayment = {
|
|
254
|
+
OutPoint: outPoint,
|
|
255
|
+
Owner: this.stasWallet,
|
|
256
|
+
};
|
|
257
|
+
const txRaw = BuildTransferTx({
|
|
258
|
+
tokenScheme: this.tokenScheme,
|
|
259
|
+
stasPayment,
|
|
260
|
+
feePayment,
|
|
261
|
+
to: this.stasWallet.Address,
|
|
262
|
+
});
|
|
263
|
+
const tx = TransactionReader.readHex(txRaw);
|
|
264
|
+
|
|
265
|
+
newLevel.push(OutPoint.fromTransaction(tx, 0));
|
|
266
|
+
mergeTransactions.push(txRaw);
|
|
267
|
+
|
|
268
|
+
stasUtxo = OutPoint.fromTransaction(tx, 0);
|
|
269
|
+
feePayment.OutPoint = OutPoint.fromTransaction(tx, 1);
|
|
270
|
+
}
|
|
271
|
+
} else {
|
|
272
|
+
levelsBeforeTransfer++;
|
|
273
|
+
|
|
274
|
+
const mergeCounts = Math.floor(currentLevel.length / 2);
|
|
275
|
+
const remainder = currentLevel.length % 2;
|
|
276
|
+
|
|
277
|
+
if (remainder !== 0)
|
|
278
|
+
newLevel.push(currentLevel[currentLevel.length - 1]);
|
|
279
|
+
|
|
280
|
+
let currentIdx = 0;
|
|
281
|
+
|
|
282
|
+
for (var i = 0; i < mergeCounts; i++) {
|
|
283
|
+
const outPoint1 = currentLevel[currentIdx++];
|
|
284
|
+
const outPoint2 = currentLevel[currentIdx++];
|
|
285
|
+
const lastMerge = mergeCounts === 1 && remainder === 0;
|
|
286
|
+
const inputSatoshis = outPoint1.Satoshis + outPoint2.Satoshis;
|
|
287
|
+
|
|
288
|
+
let destination: TDestination = {
|
|
289
|
+
Address: this.stasWallet.Address,
|
|
290
|
+
Satoshis: inputSatoshis,
|
|
291
|
+
};
|
|
292
|
+
let splitDestination: TDestination | undefined = undefined;
|
|
293
|
+
|
|
294
|
+
if (lastMerge && inputSatoshis !== satoshis) {
|
|
295
|
+
destination = {
|
|
296
|
+
Address: this.stasWallet.Address,
|
|
297
|
+
Satoshis: satoshis,
|
|
298
|
+
};
|
|
299
|
+
splitDestination = {
|
|
300
|
+
Address: this.stasWallet.Address,
|
|
301
|
+
Satoshis: inputSatoshis - satoshis,
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
const txRaw = BuildMergeTx({
|
|
306
|
+
tokenScheme: this.tokenScheme,
|
|
307
|
+
outPoint1,
|
|
308
|
+
outPoint2,
|
|
309
|
+
owner: this.stasWallet,
|
|
310
|
+
feePayment,
|
|
311
|
+
destination,
|
|
312
|
+
splitDestination,
|
|
313
|
+
});
|
|
314
|
+
const tx = TransactionReader.readHex(txRaw);
|
|
315
|
+
|
|
316
|
+
newLevel.push(OutPoint.fromTransaction(tx, 0));
|
|
317
|
+
mergeTransactions.push(txRaw);
|
|
318
|
+
|
|
319
|
+
stasUtxo = OutPoint.fromTransaction(tx, 0);
|
|
320
|
+
feePayment.OutPoint = OutPoint.fromTransaction(
|
|
321
|
+
tx,
|
|
322
|
+
tx.Outputs.length - 1
|
|
323
|
+
);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
currentLevel = newLevel;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
return { mergeTransactions, mergeFeeUtxo: feePayment.OutPoint, stasUtxo };
|
|
331
|
+
};
|
|
332
|
+
|
|
333
|
+
private buildTransferTransaction = (
|
|
334
|
+
stasUtxo: OutPoint,
|
|
335
|
+
feeUtxo: OutPoint,
|
|
336
|
+
to: Address,
|
|
337
|
+
note?: Buffer[]
|
|
338
|
+
): string =>
|
|
339
|
+
BuildTransferTx({
|
|
340
|
+
tokenScheme: this.tokenScheme,
|
|
341
|
+
stasPayment: { OutPoint: stasUtxo, Owner: this.stasWallet },
|
|
342
|
+
feePayment: { OutPoint: feeUtxo, Owner: this.feeWallet },
|
|
343
|
+
to,
|
|
344
|
+
note,
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
private buildSplitTransaction = (
|
|
348
|
+
stasUtxo: OutPoint,
|
|
349
|
+
satoshis: number,
|
|
350
|
+
to: Address,
|
|
351
|
+
feeUtxo: OutPoint,
|
|
352
|
+
note?: Buffer[]
|
|
353
|
+
): string =>
|
|
354
|
+
BuildSplitTx({
|
|
355
|
+
tokenScheme: this.tokenScheme,
|
|
356
|
+
stasPayment: { OutPoint: stasUtxo, Owner: this.stasWallet },
|
|
357
|
+
feePayment: { OutPoint: feeUtxo, Owner: this.feeWallet },
|
|
358
|
+
destinations: [
|
|
359
|
+
{ Satoshis: satoshis, Address: to },
|
|
360
|
+
{
|
|
361
|
+
Satoshis: stasUtxo.Satoshis - satoshis,
|
|
362
|
+
Address: this.stasWallet.Address,
|
|
363
|
+
},
|
|
364
|
+
],
|
|
365
|
+
note,
|
|
366
|
+
});
|
|
367
|
+
}
|