mainnet-js 0.5.7 → 0.5.9
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/index.html +1 -1
- package/dist/main/history/electrumTransformer.d.ts +4 -0
- package/dist/main/history/electrumTransformer.js +216 -0
- package/dist/main/history/electrumTransformer.js.map +1 -0
- package/dist/main/history/interface.d.ts +16 -0
- package/dist/main/history/interface.js +3 -0
- package/dist/main/history/interface.js.map +1 -0
- package/dist/main/wallet/Wif.d.ts +3 -1
- package/dist/main/wallet/Wif.js +7 -2
- package/dist/main/wallet/Wif.js.map +1 -1
- package/dist/mainnet-0.5.9.js +2 -0
- package/dist/{mainnet-0.5.7.js.LICENSE.txt → mainnet-0.5.9.js.LICENSE.txt} +0 -0
- package/dist/module/history/electrumTransformer.d.ts +4 -0
- package/dist/module/history/electrumTransformer.js +211 -0
- package/dist/module/history/electrumTransformer.js.map +1 -0
- package/dist/module/history/interface.d.ts +16 -0
- package/dist/module/history/interface.js +2 -0
- package/dist/module/history/interface.js.map +1 -0
- package/dist/module/wallet/Wif.d.ts +3 -1
- package/dist/module/wallet/Wif.js +7 -2
- package/dist/module/wallet/Wif.js.map +1 -1
- package/dist/tsconfig.browser.tsbuildinfo +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/Wallet.test.ts +69 -1
- package/src/history/electrumTransformer.test.ts +251 -0
- package/src/history/electrumTransformer.ts +320 -0
- package/src/history/interface.ts +18 -0
- package/src/wallet/Wif.ts +22 -2
- package/dist/mainnet-0.5.7.js +0 -2
package/src/Wallet.test.ts
CHANGED
|
@@ -422,8 +422,76 @@ describe(`Test Wallet library`, () => {
|
|
|
422
422
|
const sendMaxResponse = await bob.sendMax(alice.cashaddr);
|
|
423
423
|
expect(sendMaxResponse.txId!.length).toBe(64);
|
|
424
424
|
|
|
425
|
-
//
|
|
425
|
+
//
|
|
426
426
|
const bobBalanceFinal = (await bob.getBalance()) as BalanceResponse;
|
|
427
427
|
expect(bobBalanceFinal.sat).toBe(0);
|
|
428
428
|
});
|
|
429
|
+
|
|
430
|
+
test("Send all coins back on regtest", async () => {
|
|
431
|
+
// Build Alice's wallet from Wallet Import Format string, send some sats
|
|
432
|
+
|
|
433
|
+
const alice = await RegTestWallet.fromWIF(process.env.PRIVATE_WIF!);
|
|
434
|
+
const bob = await createWallet({
|
|
435
|
+
type: WalletTypeEnum.Seed,
|
|
436
|
+
network: "regtest",
|
|
437
|
+
name: "Bob's random wallet",
|
|
438
|
+
});
|
|
439
|
+
|
|
440
|
+
const charlie = await createWallet({
|
|
441
|
+
type: WalletTypeEnum.Seed,
|
|
442
|
+
network: "regtest",
|
|
443
|
+
name: "Charlie's random wallet",
|
|
444
|
+
});
|
|
445
|
+
if (!alice.cashaddr || !bob.cashaddr) {
|
|
446
|
+
throw Error("Alice or Bob's wallet are missing addresses");
|
|
447
|
+
}
|
|
448
|
+
if (!alice.privateKey || !bob.privateKey) {
|
|
449
|
+
throw Error("Alice or Bob's wallet are missing private keys");
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
// Assume fulcrum node polling is 1s
|
|
453
|
+
await alice.send([
|
|
454
|
+
{
|
|
455
|
+
cashaddr: bob.cashaddr,
|
|
456
|
+
value: 3400,
|
|
457
|
+
unit: UnitEnum.SAT,
|
|
458
|
+
},
|
|
459
|
+
{
|
|
460
|
+
cashaddr: bob.cashaddr,
|
|
461
|
+
value: 3400,
|
|
462
|
+
unit: UnitEnum.SAT,
|
|
463
|
+
},
|
|
464
|
+
{
|
|
465
|
+
cashaddr: bob.cashaddr,
|
|
466
|
+
value: 3400,
|
|
467
|
+
unit: UnitEnum.SAT,
|
|
468
|
+
},
|
|
469
|
+
{
|
|
470
|
+
cashaddr: bob.cashaddr,
|
|
471
|
+
value: 3400,
|
|
472
|
+
unit: UnitEnum.SAT,
|
|
473
|
+
},
|
|
474
|
+
{
|
|
475
|
+
cashaddr: bob.cashaddr,
|
|
476
|
+
value: 3400,
|
|
477
|
+
unit: UnitEnum.SAT,
|
|
478
|
+
},
|
|
479
|
+
]);
|
|
480
|
+
|
|
481
|
+
// Send ALL of Bob's coins to Charlie.
|
|
482
|
+
const sendMaxResponse = await bob.sendMax(charlie.cashaddr!);
|
|
483
|
+
expect(sendMaxResponse.txId!.length).toBe(64);
|
|
484
|
+
expect(sendMaxResponse.balance!.sat!).toBe(0);
|
|
485
|
+
|
|
486
|
+
const bobFinalBalance = await bob.getBalance("sat");
|
|
487
|
+
expect(bobFinalBalance).toBe(0);
|
|
488
|
+
|
|
489
|
+
// Send ALL of Charlie's coins to Alice.
|
|
490
|
+
const sendMaxResponse2 = await charlie.sendMax(alice.cashaddr);
|
|
491
|
+
expect(sendMaxResponse2.txId!.length).toBe(64);
|
|
492
|
+
expect(sendMaxResponse2.balance!.sat!).toBe(0);
|
|
493
|
+
|
|
494
|
+
const charlieFinalBalance = await charlie.getBalance("sat");
|
|
495
|
+
expect(charlieFinalBalance).toBe(0);
|
|
496
|
+
});
|
|
429
497
|
});
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
import { RegTestWallet } from "../wallet/Wif";
|
|
2
|
+
import { WalletTypeEnum } from "../wallet/enum";
|
|
3
|
+
import { createWallet } from "../wallet/createWallet";
|
|
4
|
+
import { getAddressHistory } from "./electrumTransformer";
|
|
5
|
+
import { mine } from "../mine";
|
|
6
|
+
|
|
7
|
+
// This class transforms outputs from electrum to a standard array of history.
|
|
8
|
+
test("Should get an address history", async () => {
|
|
9
|
+
// Build Alice's wallet from Wallet Import Format string
|
|
10
|
+
if (!process.env.PRIVATE_WIF) {
|
|
11
|
+
throw Error("Attempted to pass an empty WIF");
|
|
12
|
+
} else {
|
|
13
|
+
let alice = await RegTestWallet.fromWIF(process.env.PRIVATE_WIF); // insert WIF from #1
|
|
14
|
+
const bob = await createWallet({
|
|
15
|
+
type: WalletTypeEnum.Wif,
|
|
16
|
+
network: "regtest",
|
|
17
|
+
});
|
|
18
|
+
const charlie = await createWallet({
|
|
19
|
+
type: WalletTypeEnum.Wif,
|
|
20
|
+
network: "regtest",
|
|
21
|
+
});
|
|
22
|
+
let sendResponse = await alice.send([
|
|
23
|
+
{
|
|
24
|
+
cashaddr: bob.cashaddr!,
|
|
25
|
+
value: 31000,
|
|
26
|
+
unit: "satoshis",
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
cashaddr: charlie.cashaddr!,
|
|
30
|
+
value: 41000,
|
|
31
|
+
unit: "satoshis",
|
|
32
|
+
},
|
|
33
|
+
]);
|
|
34
|
+
await mine({ cashaddr: alice.getDepositAddress(), blocks: 10 });
|
|
35
|
+
await bob.send([
|
|
36
|
+
{
|
|
37
|
+
cashaddr: charlie.cashaddr!,
|
|
38
|
+
value: 2100,
|
|
39
|
+
unit: "satoshis",
|
|
40
|
+
},
|
|
41
|
+
]);
|
|
42
|
+
await mine({ cashaddr: alice.getDepositAddress(), blocks: 1 });
|
|
43
|
+
await bob.send([
|
|
44
|
+
{
|
|
45
|
+
cashaddr: alice.cashaddr!,
|
|
46
|
+
value: 2100,
|
|
47
|
+
unit: "satoshis",
|
|
48
|
+
},
|
|
49
|
+
]);
|
|
50
|
+
expect(sendResponse!.txId!.length).toBe(64);
|
|
51
|
+
expect(sendResponse.balance!.bch).toBeGreaterThan(0.01);
|
|
52
|
+
await mine({ cashaddr: alice.getDepositAddress(), blocks: 10 });
|
|
53
|
+
|
|
54
|
+
// Build Bob's wallet from a public address, check his balance.
|
|
55
|
+
const bobHistory = await getAddressHistory(
|
|
56
|
+
bob.getDepositAddress(),
|
|
57
|
+
bob.provider
|
|
58
|
+
);
|
|
59
|
+
expect(bobHistory.transactions[0].value).toBe(-2100);
|
|
60
|
+
expect(bobHistory.transactions[0].to).toBe(alice.getDepositAddress());
|
|
61
|
+
expect(bobHistory.transactions[0].from).toBe(bob.getDepositAddress());
|
|
62
|
+
expect(bobHistory.transactions[1].value).toBe(-2100);
|
|
63
|
+
expect(bobHistory.transactions[1].from).toBe(bob.getDepositAddress());
|
|
64
|
+
expect(bobHistory.transactions[1].to).toBe(charlie.getDepositAddress());
|
|
65
|
+
expect(bobHistory.transactions[2].value).toBe(31000);
|
|
66
|
+
expect(bobHistory.transactions[2].balance).toBe(31000);
|
|
67
|
+
expect(bobHistory.transactions[2].fee).toBe(0);
|
|
68
|
+
expect(bobHistory.transactions[2].from).toBe(alice.getDepositAddress());
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// This class transforms outputs from electrum to a standard array of history.
|
|
73
|
+
test("Should get a history with multi-party sends", async () => {
|
|
74
|
+
// Build Alice's wallet from Wallet Import Format string
|
|
75
|
+
if (!process.env.PRIVATE_WIF) {
|
|
76
|
+
throw Error("Attempted to pass an empty WIF");
|
|
77
|
+
} else {
|
|
78
|
+
let alice = await RegTestWallet.fromWIF(process.env.PRIVATE_WIF); // insert WIF from #1
|
|
79
|
+
const bob = await createWallet({
|
|
80
|
+
type: WalletTypeEnum.Wif,
|
|
81
|
+
network: "regtest",
|
|
82
|
+
});
|
|
83
|
+
const charlie = await createWallet({
|
|
84
|
+
type: WalletTypeEnum.Wif,
|
|
85
|
+
network: "regtest",
|
|
86
|
+
});
|
|
87
|
+
let sendResponse = await alice.send([
|
|
88
|
+
{
|
|
89
|
+
cashaddr: bob.cashaddr!,
|
|
90
|
+
value: 31000,
|
|
91
|
+
unit: "satoshis",
|
|
92
|
+
},
|
|
93
|
+
]);
|
|
94
|
+
await mine({ cashaddr: alice.getDepositAddress(), blocks: 10 });
|
|
95
|
+
await bob.send([
|
|
96
|
+
{
|
|
97
|
+
cashaddr: charlie.cashaddr!,
|
|
98
|
+
value: 2100,
|
|
99
|
+
unit: "satoshis",
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
cashaddr: alice.cashaddr!,
|
|
103
|
+
value: 2100,
|
|
104
|
+
unit: "satoshis",
|
|
105
|
+
},
|
|
106
|
+
]);
|
|
107
|
+
await mine({ cashaddr: alice.getDepositAddress(), blocks: 1 });
|
|
108
|
+
expect(sendResponse!.txId!.length).toBe(64);
|
|
109
|
+
expect(sendResponse.balance!.bch).toBeGreaterThan(0.01);
|
|
110
|
+
await mine({ cashaddr: alice.getDepositAddress(), blocks: 1 });
|
|
111
|
+
|
|
112
|
+
// Build Bob's wallet from a public address, check his balance.
|
|
113
|
+
const bobHistory = await getAddressHistory(
|
|
114
|
+
bob.getDepositAddress(),
|
|
115
|
+
bob.provider
|
|
116
|
+
);
|
|
117
|
+
expect(bobHistory.transactions[1].txn).toBe(bobHistory.transactions[0].txn);
|
|
118
|
+
expect(bobHistory.transactions[1].fee).toBe(bobHistory.transactions[0].fee);
|
|
119
|
+
expect(bobHistory.transactions[1].fee).toBeGreaterThan(120);
|
|
120
|
+
expect(bobHistory.transactions[1].fee).toBeLessThan(150);
|
|
121
|
+
expect(bobHistory.transactions[0].value).toBe(-2100);
|
|
122
|
+
expect(bobHistory.transactions[0].to).toBe(alice.getDepositAddress());
|
|
123
|
+
expect(bobHistory.transactions[0].from).toBe(bob.getDepositAddress());
|
|
124
|
+
expect(bobHistory.transactions[1].value).toBe(-2100);
|
|
125
|
+
expect(bobHistory.transactions[1].to).toBe(charlie.getDepositAddress());
|
|
126
|
+
expect(bobHistory.transactions[1].from).toBe(bob.getDepositAddress());
|
|
127
|
+
|
|
128
|
+
expect(bobHistory.transactions[2].value).toBe(31000);
|
|
129
|
+
expect(bobHistory.transactions[2].fee).toBe(0);
|
|
130
|
+
expect(bobHistory.transactions[2].from).toBe(alice.getDepositAddress());
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
// This class transforms outputs from electrum to a standard array of history.
|
|
135
|
+
test("Should cut results with a longer history to given count", async () => {
|
|
136
|
+
// Build Alice's wallet from Wallet Import Format string
|
|
137
|
+
if (!process.env.PRIVATE_WIF) {
|
|
138
|
+
throw Error("Attempted to pass an empty WIF");
|
|
139
|
+
} else {
|
|
140
|
+
let alice = await RegTestWallet.fromWIF(process.env.PRIVATE_WIF); // insert WIF from #1
|
|
141
|
+
const bob = await createWallet({
|
|
142
|
+
type: WalletTypeEnum.Wif,
|
|
143
|
+
network: "regtest",
|
|
144
|
+
});
|
|
145
|
+
const charlie = await createWallet({
|
|
146
|
+
type: WalletTypeEnum.Wif,
|
|
147
|
+
network: "regtest",
|
|
148
|
+
});
|
|
149
|
+
let sendResponse = await alice.send([
|
|
150
|
+
{
|
|
151
|
+
cashaddr: bob.cashaddr!,
|
|
152
|
+
value: 31000,
|
|
153
|
+
unit: "satoshis",
|
|
154
|
+
},
|
|
155
|
+
]);
|
|
156
|
+
await mine({ cashaddr: alice.getDepositAddress(), blocks: 10 });
|
|
157
|
+
await bob.send([
|
|
158
|
+
{
|
|
159
|
+
cashaddr: charlie.cashaddr!,
|
|
160
|
+
value: 2100,
|
|
161
|
+
unit: "satoshis",
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
cashaddr: alice.cashaddr!,
|
|
165
|
+
value: 2100,
|
|
166
|
+
unit: "satoshis",
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
cashaddr: alice.cashaddr!,
|
|
170
|
+
value: 2100,
|
|
171
|
+
unit: "satoshis",
|
|
172
|
+
},
|
|
173
|
+
]);
|
|
174
|
+
await mine({ cashaddr: alice.getDepositAddress(), blocks: 1 });
|
|
175
|
+
expect(sendResponse!.txId!.length).toBe(64);
|
|
176
|
+
expect(sendResponse.balance!.bch).toBeGreaterThan(0.01);
|
|
177
|
+
await mine({ cashaddr: alice.getDepositAddress(), blocks: 10 });
|
|
178
|
+
|
|
179
|
+
// Build Bob's wallet from a public address, check his balance.
|
|
180
|
+
const bobHistory = await getAddressHistory(
|
|
181
|
+
bob.getDepositAddress(),
|
|
182
|
+
bob.provider,
|
|
183
|
+
"sat",
|
|
184
|
+
0,
|
|
185
|
+
2
|
|
186
|
+
);
|
|
187
|
+
expect(bobHistory.transactions.length).toBe(4);
|
|
188
|
+
|
|
189
|
+
expect(bobHistory.transactions[0].value).toBe(-2100);
|
|
190
|
+
expect(bobHistory.transactions[0].to).toBe(alice.getDepositAddress());
|
|
191
|
+
expect(bobHistory.transactions[1].value).toBe(-2100);
|
|
192
|
+
expect(bobHistory.transactions[1].to).toBe(alice.getDepositAddress());
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
// This class transforms outputs from electrum to a standard array of history.
|
|
197
|
+
test("Should handel input and fee from many utxos", async () => {
|
|
198
|
+
// Build Alice's wallet from Wallet Import Format string
|
|
199
|
+
if (!process.env.PRIVATE_WIF) {
|
|
200
|
+
throw Error("Attempted to pass an empty WIF");
|
|
201
|
+
} else {
|
|
202
|
+
let alice = await RegTestWallet.fromWIF(process.env.PRIVATE_WIF); // insert WIF from #1
|
|
203
|
+
const bob = await createWallet({
|
|
204
|
+
type: WalletTypeEnum.Wif,
|
|
205
|
+
network: "regtest",
|
|
206
|
+
});
|
|
207
|
+
const charlie = await createWallet({
|
|
208
|
+
type: WalletTypeEnum.Wif,
|
|
209
|
+
network: "regtest",
|
|
210
|
+
});
|
|
211
|
+
let sendResponse = await alice.send([
|
|
212
|
+
{
|
|
213
|
+
cashaddr: bob.cashaddr!,
|
|
214
|
+
value: 600,
|
|
215
|
+
unit: "satoshis",
|
|
216
|
+
},
|
|
217
|
+
{
|
|
218
|
+
cashaddr: bob.cashaddr!,
|
|
219
|
+
value: 600,
|
|
220
|
+
unit: "satoshis",
|
|
221
|
+
},
|
|
222
|
+
{
|
|
223
|
+
cashaddr: bob.cashaddr!,
|
|
224
|
+
value: 600,
|
|
225
|
+
unit: "satoshis",
|
|
226
|
+
},
|
|
227
|
+
{
|
|
228
|
+
cashaddr: bob.cashaddr!,
|
|
229
|
+
value: 600,
|
|
230
|
+
unit: "satoshis",
|
|
231
|
+
},
|
|
232
|
+
]);
|
|
233
|
+
await mine({ cashaddr: alice.getDepositAddress(), blocks: 10 });
|
|
234
|
+
await bob.sendMax(charlie.cashaddr!);
|
|
235
|
+
await mine({ cashaddr: alice.getDepositAddress(), blocks: 1 });
|
|
236
|
+
expect(sendResponse!.txId!.length).toBe(64);
|
|
237
|
+
expect(sendResponse.balance!.bch).toBeGreaterThan(0.01);
|
|
238
|
+
await mine({ cashaddr: alice.getDepositAddress(), blocks: 10 });
|
|
239
|
+
|
|
240
|
+
// Build Bob's wallet from a public address, check his balance.
|
|
241
|
+
const bobHistory = await getAddressHistory(
|
|
242
|
+
bob.getDepositAddress(),
|
|
243
|
+
bob.provider,
|
|
244
|
+
"sat"
|
|
245
|
+
);
|
|
246
|
+
|
|
247
|
+
expect(bobHistory.transactions[0].value).toBeLessThan(-1700);
|
|
248
|
+
expect(bobHistory.transactions[0].to).toBe(charlie.getDepositAddress());
|
|
249
|
+
expect(bobHistory.transactions[1].from).toBe(alice.getDepositAddress());
|
|
250
|
+
}
|
|
251
|
+
});
|
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
import {
|
|
2
|
+
binToHex,
|
|
3
|
+
cashAddressToLockingBytecode,
|
|
4
|
+
decodeTransaction,
|
|
5
|
+
hexToBin,
|
|
6
|
+
binToBigIntUint64LE,
|
|
7
|
+
Transaction,
|
|
8
|
+
lockingBytecodeToCashAddress,
|
|
9
|
+
Output,
|
|
10
|
+
} from "@bitauth/libauth";
|
|
11
|
+
import { UnitEnum } from "../enum";
|
|
12
|
+
import NetworkProvider from "../network/NetworkProvider";
|
|
13
|
+
import { derivePrefix } from "../util/derivePublicKeyHash";
|
|
14
|
+
import { convert } from "../util/convert";
|
|
15
|
+
import { bchParam } from "../chain";
|
|
16
|
+
import { floor } from "../util/floor";
|
|
17
|
+
import { TransactionHistoryI, TransactionHistoryItemI } from "./interface";
|
|
18
|
+
|
|
19
|
+
export async function getAddressHistory(
|
|
20
|
+
cashaddr: string,
|
|
21
|
+
provider: NetworkProvider,
|
|
22
|
+
unit = "sat",
|
|
23
|
+
start = 0,
|
|
24
|
+
count = 25,
|
|
25
|
+
collapseChange = true
|
|
26
|
+
): Promise<TransactionHistoryI> {
|
|
27
|
+
// Get an array of raw transactions as hex
|
|
28
|
+
let txnHashes = await provider.getHistory(cashaddr);
|
|
29
|
+
|
|
30
|
+
// Assume transaction hashes will be served in chronological order
|
|
31
|
+
// Slice count in from the end and count to the provided inputs
|
|
32
|
+
let len = txnHashes.length;
|
|
33
|
+
txnHashes = txnHashes.slice(len - start - count, len - start);
|
|
34
|
+
|
|
35
|
+
// get the current balance in satoshis
|
|
36
|
+
let currentBalance = await provider.getBalance(cashaddr);
|
|
37
|
+
|
|
38
|
+
// Transform the hex transactions to and array of histroy item array promises.
|
|
39
|
+
let txItemPromises = txnHashes.map((tx) => {
|
|
40
|
+
return getDetailedHistory(
|
|
41
|
+
cashaddr,
|
|
42
|
+
tx.tx_hash,
|
|
43
|
+
tx.height,
|
|
44
|
+
provider,
|
|
45
|
+
collapseChange
|
|
46
|
+
);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
// await the history array promises
|
|
50
|
+
let items = await Promise.all(txItemPromises);
|
|
51
|
+
|
|
52
|
+
// flatten the array of responses
|
|
53
|
+
let preprocessedTxns = Array.prototype.concat.apply([], items);
|
|
54
|
+
|
|
55
|
+
// Reverse chronological order (again), so list appear as newest first.
|
|
56
|
+
preprocessedTxns = preprocessedTxns.reverse();
|
|
57
|
+
|
|
58
|
+
// Get the factor to apply the requested unit of measure
|
|
59
|
+
let factor =
|
|
60
|
+
(await convert(bchParam.subUnits, "sat", unit)) / bchParam.subUnits;
|
|
61
|
+
|
|
62
|
+
// Apply the unit factor and
|
|
63
|
+
let txns = applyBalance(preprocessedTxns, currentBalance, unit, factor);
|
|
64
|
+
|
|
65
|
+
return {
|
|
66
|
+
transactions: txns,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export async function getDetailedHistory(
|
|
71
|
+
cashaddr: string,
|
|
72
|
+
hash: string,
|
|
73
|
+
height: number,
|
|
74
|
+
provider: NetworkProvider,
|
|
75
|
+
collapseChange: boolean
|
|
76
|
+
): Promise<TransactionHistoryItemI[]> {
|
|
77
|
+
let transactionHex = await provider.getRawTransaction(hash);
|
|
78
|
+
|
|
79
|
+
collapseChange;
|
|
80
|
+
let addressBytecode = cashAddressToLockingBytecode(cashaddr);
|
|
81
|
+
if (typeof addressBytecode === "string") throw Error(addressBytecode);
|
|
82
|
+
|
|
83
|
+
let transaction = decodeTransaction(hexToBin(transactionHex));
|
|
84
|
+
if (typeof transaction === "string") throw Error(transaction);
|
|
85
|
+
|
|
86
|
+
let r: TransactionHistoryItemI[] = [];
|
|
87
|
+
r.push(
|
|
88
|
+
...(await getMatchingInputs(
|
|
89
|
+
transaction,
|
|
90
|
+
cashaddr,
|
|
91
|
+
height,
|
|
92
|
+
hash,
|
|
93
|
+
provider,
|
|
94
|
+
collapseChange
|
|
95
|
+
))
|
|
96
|
+
);
|
|
97
|
+
return r;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
async function getMatchingInputs(
|
|
101
|
+
transaction: Transaction,
|
|
102
|
+
cashaddr: string,
|
|
103
|
+
height: number,
|
|
104
|
+
hash: string,
|
|
105
|
+
provider,
|
|
106
|
+
collapseChange
|
|
107
|
+
) {
|
|
108
|
+
let addressBytecode = cashAddressToLockingBytecode(cashaddr);
|
|
109
|
+
if (typeof addressBytecode === "string") throw Error(addressBytecode);
|
|
110
|
+
let lockingBytecodeHex = binToHex(addressBytecode.bytecode);
|
|
111
|
+
let prefix = derivePrefix(cashaddr);
|
|
112
|
+
|
|
113
|
+
let inputUtxos = await getInputTransactions(transaction, provider);
|
|
114
|
+
|
|
115
|
+
let fee = getFee(
|
|
116
|
+
inputUtxos,
|
|
117
|
+
transaction.outputs,
|
|
118
|
+
lockingBytecodeHex,
|
|
119
|
+
collapseChange
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
let r: TransactionHistoryItemI[] = [];
|
|
123
|
+
|
|
124
|
+
let txIds: string[] = [];
|
|
125
|
+
|
|
126
|
+
for (let input of transaction.inputs) {
|
|
127
|
+
let outpoint = inputUtxos[transaction.inputs.indexOf(input)];
|
|
128
|
+
|
|
129
|
+
// if the utxo of the input matches the address in question
|
|
130
|
+
if (binToHex(outpoint.lockingBytecode) === lockingBytecodeHex) {
|
|
131
|
+
for (let output of transaction.outputs) {
|
|
132
|
+
let idx = transaction.outputs.indexOf(output);
|
|
133
|
+
let from = lockingBytecodeToCashAddress(
|
|
134
|
+
outpoint.lockingBytecode,
|
|
135
|
+
prefix
|
|
136
|
+
) as string;
|
|
137
|
+
|
|
138
|
+
// the output was change
|
|
139
|
+
if (binToHex(output.lockingBytecode) === lockingBytecodeHex) {
|
|
140
|
+
if (!collapseChange) {
|
|
141
|
+
r.push({
|
|
142
|
+
from: from,
|
|
143
|
+
to: cashaddr,
|
|
144
|
+
unit: "sat",
|
|
145
|
+
index: idx,
|
|
146
|
+
blockheight: height,
|
|
147
|
+
txn: `${hash}`,
|
|
148
|
+
txId: `${hash}:i:${idx}`,
|
|
149
|
+
value: -Number(binToBigIntUint64LE(output.satoshis)),
|
|
150
|
+
fee: 0,
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
} else {
|
|
154
|
+
if (!txIds.find((str) => str === `${hash}:i:${idx}`)) {
|
|
155
|
+
// the utxo was sent to another address
|
|
156
|
+
let to = lockingBytecodeToCashAddress(
|
|
157
|
+
output.lockingBytecode,
|
|
158
|
+
prefix
|
|
159
|
+
) as string;
|
|
160
|
+
r.push({
|
|
161
|
+
from: from,
|
|
162
|
+
to: to,
|
|
163
|
+
unit: "sat",
|
|
164
|
+
index: idx,
|
|
165
|
+
blockheight: height,
|
|
166
|
+
txn: `${hash}`,
|
|
167
|
+
txId: `${hash}:i:${idx}`,
|
|
168
|
+
value: -Number(binToBigIntUint64LE(output.satoshis)),
|
|
169
|
+
fee: fee,
|
|
170
|
+
});
|
|
171
|
+
txIds.push(`${hash}:i:${idx}`);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// check the transaction outputs for receiving transactions
|
|
179
|
+
for (let output of transaction.outputs) {
|
|
180
|
+
// the output was a utxo for the address in question
|
|
181
|
+
if (binToHex(output.lockingBytecode) === lockingBytecodeHex) {
|
|
182
|
+
// the input was from a single address
|
|
183
|
+
if (transaction.inputs.length == 1) {
|
|
184
|
+
const input = transaction.inputs[0];
|
|
185
|
+
const inHash = binToHex(input.outpointTransactionHash);
|
|
186
|
+
const transactionHex = await provider.getRawTransaction(inHash);
|
|
187
|
+
const inTransaction = decodeTransaction(hexToBin(transactionHex));
|
|
188
|
+
if (typeof inTransaction === "string") throw Error(inTransaction);
|
|
189
|
+
|
|
190
|
+
let outpoint = inTransaction.outputs[input.outpointIndex];
|
|
191
|
+
let from = lockingBytecodeToCashAddress(
|
|
192
|
+
outpoint.lockingBytecode,
|
|
193
|
+
prefix
|
|
194
|
+
) as string;
|
|
195
|
+
|
|
196
|
+
// if the utxo was from a different address and change is not being output...
|
|
197
|
+
if (from !== cashaddr || !collapseChange) {
|
|
198
|
+
r.push({
|
|
199
|
+
from: from,
|
|
200
|
+
to: cashaddr,
|
|
201
|
+
unit: "sat",
|
|
202
|
+
index: transaction.outputs.indexOf(output),
|
|
203
|
+
blockheight: height,
|
|
204
|
+
txn: `${hash}`,
|
|
205
|
+
txId: `${hash}:o:${transaction.outputs.indexOf(output)}`,
|
|
206
|
+
value: Number(binToBigIntUint64LE(output.satoshis)),
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
} else {
|
|
210
|
+
let from = transaction.inputs
|
|
211
|
+
.map(
|
|
212
|
+
(i) => `${binToHex(i.outpointTransactionHash)}:o:${i.outpointIndex}`
|
|
213
|
+
)
|
|
214
|
+
.join(";");
|
|
215
|
+
r.push({
|
|
216
|
+
from: from,
|
|
217
|
+
to: cashaddr,
|
|
218
|
+
unit: "sat",
|
|
219
|
+
index: transaction.outputs.indexOf(output),
|
|
220
|
+
blockheight: height,
|
|
221
|
+
txn: `${hash}`,
|
|
222
|
+
txId: `${hash}:o:${transaction.outputs.indexOf(output)}`,
|
|
223
|
+
value: Number(binToBigIntUint64LE(output.satoshis)),
|
|
224
|
+
// incoming transactions pay no fee.
|
|
225
|
+
fee: 0,
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
return r;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
async function getInputTransactions(
|
|
235
|
+
transaction: Transaction,
|
|
236
|
+
provider: NetworkProvider
|
|
237
|
+
) {
|
|
238
|
+
let inputTransactions: Output[] = [];
|
|
239
|
+
for (let input of transaction.inputs) {
|
|
240
|
+
let inHash = binToHex(input.outpointTransactionHash);
|
|
241
|
+
let transactionHex = await provider.getRawTransaction(inHash);
|
|
242
|
+
let inTransaction = decodeTransaction(hexToBin(transactionHex));
|
|
243
|
+
if (typeof inTransaction === "string") throw Error(inTransaction);
|
|
244
|
+
|
|
245
|
+
inputTransactions.push(inTransaction.outputs[input.outpointIndex]);
|
|
246
|
+
}
|
|
247
|
+
return inputTransactions;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function getFee(
|
|
251
|
+
inputUtxos: Output[],
|
|
252
|
+
utxos: Output[],
|
|
253
|
+
lockingBytecodeHex,
|
|
254
|
+
collapseChange
|
|
255
|
+
) {
|
|
256
|
+
let inValues = 0;
|
|
257
|
+
for (let outpoint of inputUtxos) {
|
|
258
|
+
if (binToHex(outpoint.lockingBytecode)) {
|
|
259
|
+
inValues += Number(binToBigIntUint64LE(outpoint.satoshis));
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const outValues = utxos
|
|
264
|
+
.map((utxo) => Number(binToBigIntUint64LE(utxo.satoshis)))
|
|
265
|
+
.reduce((a: number, b: number) => a + b, 0);
|
|
266
|
+
|
|
267
|
+
let fee = 0;
|
|
268
|
+
if (collapseChange) {
|
|
269
|
+
const nonChangeOutputs = utxos
|
|
270
|
+
.map((output) =>
|
|
271
|
+
binToHex(output.lockingBytecode) === lockingBytecodeHex ? 0 : 1
|
|
272
|
+
)
|
|
273
|
+
.reduce((a: number, b: number) => a + b, 0);
|
|
274
|
+
fee = floor((inValues - outValues) / nonChangeOutputs, 0);
|
|
275
|
+
} else {
|
|
276
|
+
fee = floor((inValues - outValues) / utxos.length, 0);
|
|
277
|
+
}
|
|
278
|
+
return fee;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function applyBalance(
|
|
282
|
+
preprocessedTxns: TransactionHistoryItemI[],
|
|
283
|
+
currentBalance: number,
|
|
284
|
+
unit: string,
|
|
285
|
+
factor: number
|
|
286
|
+
): TransactionHistoryItemI[] {
|
|
287
|
+
// balance in satoshis
|
|
288
|
+
let bal = currentBalance;
|
|
289
|
+
|
|
290
|
+
let r: TransactionHistoryItemI[] = [];
|
|
291
|
+
for (let txn of preprocessedTxns) {
|
|
292
|
+
// set the balance to the current balance in the appropriate unit
|
|
293
|
+
txn.balance = bal;
|
|
294
|
+
|
|
295
|
+
// If fee is not defined, configure it to zero.
|
|
296
|
+
txn.fee = txn.fee ? txn.fee : 0;
|
|
297
|
+
|
|
298
|
+
// update the running balance in satoshis for the next record
|
|
299
|
+
// a receiving value is positive, a send is negative
|
|
300
|
+
// The sign is reversed in cronological order from the current balance.
|
|
301
|
+
bal -= txn.value;
|
|
302
|
+
bal += txn.fee;
|
|
303
|
+
|
|
304
|
+
// transform the value of the transaction
|
|
305
|
+
txn.value = txn.value * factor;
|
|
306
|
+
txn.fee = txn.fee! * factor;
|
|
307
|
+
|
|
308
|
+
// If unit is usd, round to two decimal places.
|
|
309
|
+
if (unit.toLowerCase() == "usd") {
|
|
310
|
+
txn.value = floor(txn.value, 2);
|
|
311
|
+
txn.fee = floor(txn.fee, 2);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// note the unit
|
|
315
|
+
txn.unit = unit as UnitEnum;
|
|
316
|
+
|
|
317
|
+
r.push(txn);
|
|
318
|
+
}
|
|
319
|
+
return r;
|
|
320
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { UnitEnum } from "../enum";
|
|
2
|
+
|
|
3
|
+
export interface TransactionHistoryItemI {
|
|
4
|
+
to: string;
|
|
5
|
+
from: string;
|
|
6
|
+
unit: UnitEnum;
|
|
7
|
+
index: number;
|
|
8
|
+
blockheight: number;
|
|
9
|
+
txn: string;
|
|
10
|
+
txId: string;
|
|
11
|
+
value: number;
|
|
12
|
+
fee?: number;
|
|
13
|
+
balance?: number;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface TransactionHistoryI {
|
|
17
|
+
transactions: TransactionHistoryItemI[];
|
|
18
|
+
}
|
package/src/wallet/Wif.ts
CHANGED
|
@@ -104,6 +104,9 @@ import { amountInSatoshi } from "../util/amountInSatoshi";
|
|
|
104
104
|
import { getXPubKey } from "../util/getXPubKey";
|
|
105
105
|
import { DERIVATION_PATHS, DUST_UTXO_THRESHOLD } from "../constant";
|
|
106
106
|
|
|
107
|
+
import { TransactionHistoryI } from "../history/interface";
|
|
108
|
+
import { getAddressHistory } from "../history/electrumTransformer";
|
|
109
|
+
|
|
107
110
|
//#endregion Imports
|
|
108
111
|
|
|
109
112
|
const secp256k1Promise = instantiateSecp256k1();
|
|
@@ -1065,15 +1068,32 @@ export class Wallet extends BaseWallet {
|
|
|
1065
1068
|
}
|
|
1066
1069
|
|
|
1067
1070
|
// gets transaction history of this wallet
|
|
1068
|
-
public async
|
|
1071
|
+
public async getRawHistory(): Promise<TxI[]> {
|
|
1069
1072
|
return await this.provider!.getHistory(this.cashaddr!);
|
|
1070
1073
|
}
|
|
1071
1074
|
|
|
1075
|
+
// gets transaction history of this wallet
|
|
1076
|
+
public async getHistory(
|
|
1077
|
+
unit: UnitEnum,
|
|
1078
|
+
start?: number,
|
|
1079
|
+
count?: number,
|
|
1080
|
+
collapseChange?: boolean
|
|
1081
|
+
): Promise<TransactionHistoryI> {
|
|
1082
|
+
return getAddressHistory(
|
|
1083
|
+
this.cashaddr!,
|
|
1084
|
+
this.provider!,
|
|
1085
|
+
unit,
|
|
1086
|
+
start,
|
|
1087
|
+
count,
|
|
1088
|
+
collapseChange
|
|
1089
|
+
);
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1072
1092
|
// gets last transaction of this wallet
|
|
1073
1093
|
public async getLastTransaction(
|
|
1074
1094
|
confirmedOnly: boolean = false
|
|
1075
1095
|
): Promise<ElectrumRawTransaction | null> {
|
|
1076
|
-
let history: TxI[] = await this.
|
|
1096
|
+
let history: TxI[] = await this.getRawHistory();
|
|
1077
1097
|
if (confirmedOnly) {
|
|
1078
1098
|
history = history.filter((val) => val.height > 0);
|
|
1079
1099
|
}
|