edge-currency-plugins 3.16.0 → 3.17.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/android/src/main/assets/edge-currency-plugins/edge-currency-plugins.js +51 -49
- package/lib/common/plugin/types.js +11 -0
- package/lib/common/utxobased/engine/ServerStates.js +17 -1
- package/lib/common/utxobased/engine/UtxoEngineProcessor.js +53 -18
- package/lib/common/utxobased/info/all.js +6 -0
- package/lib/common/utxobased/info/bitcoin.js +1 -1
- package/lib/common/utxobased/info/ecashbitcoin.js +90 -0
- package/lib/common/utxobased/info/ecashbitcoinbetanet.js +91 -0
- package/lib/common/utxobased/keymanager/keymanager.js +6 -1
- package/lib/common/utxobased/network/Blockbook.js +3 -1
- package/lib/common/utxobased/network/BlockbookElectrum.js +243 -77
- package/lib/common/utxobased/network/Socket.js +26 -20
- package/lib/common/utxobased/network/electrumApi.js +49 -1
- package/lib/src/common/plugin/types.d.ts +10 -0
- package/lib/src/common/utxobased/engine/ServerStates.d.ts +1 -0
- package/lib/src/common/utxobased/engine/UtxoEngineProcessor.d.ts +2 -0
- package/lib/src/common/utxobased/info/all.d.ts +2 -0
- package/lib/src/common/utxobased/info/ecashbitcoin.d.ts +3 -0
- package/lib/src/common/utxobased/info/ecashbitcoinbetanet.d.ts +3 -0
- package/lib/src/common/utxobased/network/Blockbook.d.ts +3 -1
- package/lib/src/common/utxobased/network/BlockbookElectrum.d.ts +2 -0
- package/lib/src/common/utxobased/network/electrumApi.d.ts +21 -0
- package/lib/test/common/utxobased/engine/UtxoEngineProcessor.spec.d.ts +1 -0
- package/lib/test/common/utxobased/keymanager/coins/ecashbitcointransactiontest.spec.d.ts +1 -0
- package/lib/test/common/utxobased/network/BlockbookElectrum.spec.d.ts +1 -0
- package/package.json +1 -1
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }var _altcoinjs = require('altcoin-js');
|
|
1
|
+
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }var _altcoinjs = require('altcoin-js');
|
|
2
|
+
|
|
2
3
|
|
|
3
4
|
|
|
4
5
|
|
|
@@ -22,6 +23,17 @@ var _Blockbook = require('./Blockbook');
|
|
|
22
23
|
|
|
23
24
|
|
|
24
25
|
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
|
|
25
37
|
var _electrumApi = require('./electrumApi');
|
|
26
38
|
|
|
27
39
|
|
|
@@ -49,8 +61,9 @@ var _electrumApi = require('./electrumApi');
|
|
|
49
61
|
|
|
50
62
|
|
|
51
63
|
|
|
52
|
-
|
|
53
|
-
|
|
64
|
+
|
|
65
|
+
const asElectrumMessage = _cleaners.asJSON.call(void 0,
|
|
66
|
+
_cleaners.asEither.call(void 0,
|
|
54
67
|
_cleaners.asObject.call(void 0, {
|
|
55
68
|
id: _cleaners.asString,
|
|
56
69
|
result: _cleaners.asUnknown,
|
|
@@ -60,8 +73,30 @@ const asResponseMessage = (raw) => {
|
|
|
60
73
|
_cleaners.asObject.call(void 0, { connected: _cleaners.asString })
|
|
61
74
|
)
|
|
62
75
|
)
|
|
76
|
+
}),
|
|
77
|
+
// A subscription's notifications carry its method in place of an id:
|
|
78
|
+
_cleaners.asObject.call(void 0, {
|
|
79
|
+
method: _cleaners.asString,
|
|
80
|
+
params: _cleaners.asArray.call(void 0, _cleaners.asUnknown)
|
|
63
81
|
})
|
|
64
|
-
)
|
|
82
|
+
)
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
const { method: headersSubscribeMethod } = _electrumApi.headersSubscribeMessage.call(void 0, )
|
|
86
|
+
|
|
87
|
+
const asResponseMessage = (raw) => {
|
|
88
|
+
const message = asElectrumMessage(raw)
|
|
89
|
+
|
|
90
|
+
// The socket files each subscription under its method name:
|
|
91
|
+
if ('method' in message) {
|
|
92
|
+
return { id: message.method, data: message.params[0] }
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Electrum answers a subscription with the current header, not the
|
|
96
|
+
// acknowledgement the socket waits for. fetchInfo reports that height:
|
|
97
|
+
if (message.id === headersSubscribeMethod && message.error == null) {
|
|
98
|
+
return { id: message.id, data: { subscribed: true } }
|
|
99
|
+
}
|
|
65
100
|
|
|
66
101
|
return {
|
|
67
102
|
id: message.id,
|
|
@@ -70,11 +105,20 @@ const asResponseMessage = (raw) => {
|
|
|
70
105
|
}
|
|
71
106
|
}
|
|
72
107
|
|
|
73
|
-
const
|
|
108
|
+
const makeAsResponse = (
|
|
109
|
+
isWatchingBlocks
|
|
110
|
+
) => (raw) => {
|
|
74
111
|
const payloadString = _cleaners.asString.call(void 0, raw).trimEnd()
|
|
75
112
|
const messageStrings = payloadString.split('\n')
|
|
76
113
|
const messages = messageStrings.map(asResponseMessage)
|
|
77
|
-
|
|
114
|
+
|
|
115
|
+
// fetchInfo's blockchain.headers.subscribe subscribes the connection to new
|
|
116
|
+
// blocks, so the server can announce one before watchBlocks files the
|
|
117
|
+
// subscription. The socket drops connections over messages it cannot route,
|
|
118
|
+
// so announcements until then are skipped. watchBlocks reports each block
|
|
119
|
+
// after it subscribes:
|
|
120
|
+
if (isWatchingBlocks()) return messages
|
|
121
|
+
return messages.filter(message => message.id !== headersSubscribeMethod)
|
|
78
122
|
}
|
|
79
123
|
|
|
80
124
|
function makeBlockbookElectrum(
|
|
@@ -98,14 +142,149 @@ const asResponse = (raw) => {
|
|
|
98
142
|
address,
|
|
99
143
|
coin: pluginInfo.coinInfo.name
|
|
100
144
|
})
|
|
101
|
-
|
|
102
|
-
const scriptHash = scriptHashBuffer.reverse().toString('hex')
|
|
103
|
-
return scriptHash
|
|
145
|
+
return toElectrumScriptHash(Buffer.from(scriptPubkey, 'hex'))
|
|
104
146
|
}
|
|
105
147
|
|
|
148
|
+
// Makes the Electrum requests for a Blockbook task, and returns the
|
|
149
|
+
// Blockbook response built from their answers:
|
|
150
|
+
function* translateTask(
|
|
151
|
+
task
|
|
152
|
+
) {
|
|
153
|
+
// Translate getAccountUtxo to blockchain.scripthash.listunspent:
|
|
154
|
+
if (task.method === 'getAccountUtxo') {
|
|
155
|
+
const params = task.params
|
|
156
|
+
const address =
|
|
157
|
+
asAddress != null ? asAddress(params.descriptor) : params.descriptor
|
|
158
|
+
const scriptHash = addressToScriptHash(address)
|
|
159
|
+
|
|
160
|
+
// Safe because yielding WsTask<ListUnspentResponse> should get
|
|
161
|
+
// ListUnspentResponse next.
|
|
162
|
+
const electrumUtxos = (yield {
|
|
163
|
+
..._electrumApi.listUnspentMessage.call(void 0, scriptHash)
|
|
164
|
+
})
|
|
165
|
+
|
|
166
|
+
const blockbookUtxos = electrumUtxos.map(
|
|
167
|
+
utxo => ({
|
|
168
|
+
txid: utxo.tx_hash,
|
|
169
|
+
vout: utxo.tx_pos,
|
|
170
|
+
value: utxo.value.toString(),
|
|
171
|
+
height: utxo.height
|
|
172
|
+
})
|
|
173
|
+
)
|
|
174
|
+
return blockbookUtxos
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Get Address Transactions:
|
|
178
|
+
if (task.method === 'getAccountInfo') {
|
|
179
|
+
const params = task.params
|
|
180
|
+
const address =
|
|
181
|
+
asAddress != null ? asAddress(params.descriptor) : params.descriptor
|
|
182
|
+
const scriptHash = addressToScriptHash(address)
|
|
183
|
+
|
|
184
|
+
// Safe because yielding WsTask<AddressTransactionsResponse> should get
|
|
185
|
+
// AddressTransactionsResponse next.
|
|
186
|
+
const electrumUtxos = (yield {
|
|
187
|
+
..._electrumApi.addressTransactionsMessage.call(void 0, scriptHash)
|
|
188
|
+
})
|
|
189
|
+
|
|
190
|
+
const blockbookUtxos = {
|
|
191
|
+
address, // unused
|
|
192
|
+
balance: '0', // unused
|
|
193
|
+
totalReceived: '0', // unused
|
|
194
|
+
totalSent: '0', // unused
|
|
195
|
+
txs: electrumUtxos.length,
|
|
196
|
+
unconfirmedBalance: '0', // unused
|
|
197
|
+
unconfirmedTxs: electrumUtxos.reduce(
|
|
198
|
+
// Electrum puts mempool transactions at height 0 or -1:
|
|
199
|
+
(sum, tx) => sum + (tx.height <= 0 ? 1 : 0),
|
|
200
|
+
0
|
|
201
|
+
),
|
|
202
|
+
txids: [], // unused
|
|
203
|
+
transactions: [], // TODO: this requires an extra query per txid
|
|
204
|
+
page: 0, // unused
|
|
205
|
+
totalPages: 1,
|
|
206
|
+
itemsOnPage: 0 // unused
|
|
207
|
+
}
|
|
208
|
+
return blockbookUtxos
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Translate getTransaction to blockchain.transaction.get:
|
|
212
|
+
if (task.method === 'getTransaction') {
|
|
213
|
+
const params = task.params
|
|
214
|
+
return yield* transactionTask(params.txid)
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
throw new Error(`${task.method} not supported for Electrum connections`)
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function* transactionTask(
|
|
221
|
+
txid
|
|
222
|
+
) {
|
|
223
|
+
// Safe because yielding WsTask<RawTransactionResponse> should get
|
|
224
|
+
// RawTransactionResponse next.
|
|
225
|
+
const hex = (yield {
|
|
226
|
+
..._electrumApi.rawTransactionMessage.call(void 0, txid)
|
|
227
|
+
})
|
|
228
|
+
const transaction = _altcoinjs.Transaction.fromHex(hex)
|
|
229
|
+
|
|
230
|
+
// Electrum only reports a transaction's height in the history of a
|
|
231
|
+
// script it pays. Servers refuse the history of busy scripts, so each
|
|
232
|
+
// paid script is tried in turn. When none answers the task fails, so the
|
|
233
|
+
// engine retries it instead of taking the transaction as unconfirmed:
|
|
234
|
+
let blockHeight = 0
|
|
235
|
+
let historyError
|
|
236
|
+
for (const { script } of transaction.outs) {
|
|
237
|
+
if (script[0] === _altcoinjs.opcodes.OP_RETURN) continue
|
|
238
|
+
try {
|
|
239
|
+
// Safe because yielding WsTask<AddressTransactionsResponse> should get
|
|
240
|
+
// AddressTransactionsResponse next.
|
|
241
|
+
const history = (yield {
|
|
242
|
+
..._electrumApi.addressTransactionsMessage.call(void 0, toElectrumScriptHash(script))
|
|
243
|
+
})
|
|
244
|
+
blockHeight = _nullishCoalesce(_optionalChain([history, 'access', _ => _.find, 'call', _2 => _2(item => item.tx_hash === txid), 'optionalAccess', _3 => _3.height]), () => ( 0))
|
|
245
|
+
historyError = undefined
|
|
246
|
+
break
|
|
247
|
+
} catch (error) {
|
|
248
|
+
log.warn(`failed to fetch the height of transaction ${txid}:`, error)
|
|
249
|
+
historyError = error
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
if (historyError != null) throw historyError
|
|
253
|
+
|
|
254
|
+
// The engine reads a fetched transaction's hex, height, outpoints, and
|
|
255
|
+
// outputs. It keeps the date, fee, and input amounts it already stored,
|
|
256
|
+
// which Electrum could only supply with a request per previous transaction:
|
|
257
|
+
const isCoinbase = transaction.isCoinbase()
|
|
258
|
+
return {
|
|
259
|
+
txid,
|
|
260
|
+
hex,
|
|
261
|
+
// Like Blockbook, Electrum puts mempool transactions at height 0 or -1:
|
|
262
|
+
blockHeight,
|
|
263
|
+
confirmations: 0,
|
|
264
|
+
blockTime: 0,
|
|
265
|
+
fees: '0',
|
|
266
|
+
vin: transaction.ins.map((input, n) => ({
|
|
267
|
+
addresses: [],
|
|
268
|
+
n,
|
|
269
|
+
sequence: input.sequence,
|
|
270
|
+
txid: Buffer.from(input.hash).reverse().toString('hex'),
|
|
271
|
+
value: '0',
|
|
272
|
+
vout: input.index,
|
|
273
|
+
coinbase: isCoinbase ? input.script.toString('hex') : undefined
|
|
274
|
+
})),
|
|
275
|
+
vout: transaction.outs.map((output, n) => ({
|
|
276
|
+
n,
|
|
277
|
+
value: output.value.toString(),
|
|
278
|
+
addresses: [], // unused, the engine reads the script in `hex`
|
|
279
|
+
hex: output.script.toString('hex')
|
|
280
|
+
}))
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
let isWatchingBlocks = false
|
|
106
285
|
const instance = _Blockbook.makeBlockbook.call(void 0, {
|
|
107
286
|
asAddress,
|
|
108
|
-
asResponse,
|
|
287
|
+
asResponse: makeAsResponse(() => isWatchingBlocks),
|
|
109
288
|
connectionUri,
|
|
110
289
|
engineEmitter,
|
|
111
290
|
initOptions,
|
|
@@ -114,85 +293,47 @@ const asResponse = (raw) => {
|
|
|
114
293
|
uri
|
|
115
294
|
) {
|
|
116
295
|
const generator = taskGeneratorFn(uri)
|
|
117
|
-
let
|
|
296
|
+
let result = await generator.next()
|
|
118
297
|
|
|
119
298
|
while (true) {
|
|
120
|
-
const result = await generator.next(nextValue)
|
|
121
|
-
const task = result.value
|
|
122
|
-
|
|
123
299
|
// If the generator result is not a WsTask we will return it:
|
|
124
|
-
if (
|
|
300
|
+
if (result.done === true) return result.value
|
|
301
|
+
const task = result.value
|
|
125
302
|
if (typeof task === 'boolean') return task
|
|
126
303
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
// Safe because yielding WsTask<ListUnspentResponse> should get
|
|
135
|
-
// ListUnspentResponse next.
|
|
136
|
-
const electrumUtxos = (yield {
|
|
137
|
-
..._electrumApi.listUnspentMessage.call(void 0, scriptHash)
|
|
138
|
-
})
|
|
139
|
-
|
|
140
|
-
const blockbookUtxos = electrumUtxos.map(
|
|
141
|
-
utxo => ({
|
|
142
|
-
txid: utxo.tx_hash,
|
|
143
|
-
vout: utxo.tx_pos,
|
|
144
|
-
value: utxo.value.toString(),
|
|
145
|
-
height: utxo.height
|
|
146
|
-
})
|
|
147
|
-
)
|
|
148
|
-
nextValue = blockbookUtxos
|
|
149
|
-
continue
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
// Get Address Transactions:
|
|
153
|
-
if (task.method === 'getAccountInfo') {
|
|
154
|
-
const params = task.params
|
|
155
|
-
const address =
|
|
156
|
-
asAddress != null ? asAddress(params.descriptor) : params.descriptor
|
|
157
|
-
const scriptHash = addressToScriptHash(address)
|
|
158
|
-
|
|
159
|
-
// Safe because yielding WsTask<AddressTransactionsResponse> should get
|
|
160
|
-
// AddressTransactionsResponse next.
|
|
161
|
-
const electrumUtxos = (yield {
|
|
162
|
-
..._electrumApi.addressTransactionsMessage.call(void 0, scriptHash)
|
|
163
|
-
})
|
|
164
|
-
|
|
165
|
-
const blockbookUtxos = {
|
|
166
|
-
address, // unused
|
|
167
|
-
balance: '0', // unused
|
|
168
|
-
totalReceived: '0', // unused
|
|
169
|
-
totalSent: '0', // unused
|
|
170
|
-
txs: electrumUtxos.length,
|
|
171
|
-
unconfirmedBalance: '0', // unused
|
|
172
|
-
unconfirmedTxs: electrumUtxos.reduce(
|
|
173
|
-
(sum, tx) => (sum + tx.height >= 0 ? 1 : 0),
|
|
174
|
-
0
|
|
175
|
-
),
|
|
176
|
-
txids: [], // unused
|
|
177
|
-
transactions: [], // TODO: this requires an extra query per txid
|
|
178
|
-
page: 0, // unused
|
|
179
|
-
totalPages: 1,
|
|
180
|
-
itemsOnPage: 0 // unused
|
|
181
|
-
}
|
|
182
|
-
nextValue = blockbookUtxos
|
|
304
|
+
let response
|
|
305
|
+
try {
|
|
306
|
+
response = yield* translateTask(task)
|
|
307
|
+
} catch (error) {
|
|
308
|
+
// Hand the failure to the task that made the request, as the socket
|
|
309
|
+
// does for the requests it sends unchanged:
|
|
310
|
+
result = await generator.throw(error)
|
|
183
311
|
continue
|
|
184
312
|
}
|
|
313
|
+
result = await generator.next(response)
|
|
185
314
|
}
|
|
186
315
|
},
|
|
187
316
|
ping: async () => {
|
|
188
317
|
return await instance.promisifyWsMessage(_electrumApi.pingMessage.call(void 0, ))
|
|
189
318
|
},
|
|
319
|
+
subscribeNewBlockMessage: () => ({
|
|
320
|
+
..._electrumApi.headersSubscribeMessage.call(void 0, ),
|
|
321
|
+
// Notifications hold the header itself, with no response envelope:
|
|
322
|
+
cleaner: (raw) => {
|
|
323
|
+
const header = _electrumApi.asHeadersSubscribeResponse.call(void 0, raw)
|
|
324
|
+
return { height: header.height, hash: headerToBlockHash(header.hex) }
|
|
325
|
+
}
|
|
326
|
+
}),
|
|
190
327
|
socketEmitter,
|
|
191
328
|
walletId
|
|
192
329
|
})
|
|
193
330
|
|
|
194
|
-
instance.broadcastTx = async
|
|
195
|
-
|
|
331
|
+
instance.broadcastTx = async transaction => {
|
|
332
|
+
const txid = await instance.promisifyWsMessage(
|
|
333
|
+
_electrumApi.broadcastTransactionMessage.call(void 0, transaction.signedTx)
|
|
334
|
+
)
|
|
335
|
+
// Blockbook reports the broadcast txid as `result`:
|
|
336
|
+
return { result: txid }
|
|
196
337
|
}
|
|
197
338
|
instance.fetchAddress = async () => {
|
|
198
339
|
throw new Error('fetchAddress not supported for Electrum connections')
|
|
@@ -201,18 +342,43 @@ const asResponse = (raw) => {
|
|
|
201
342
|
throw new Error('fetchAddressUtxos not supported for Electrum connections')
|
|
202
343
|
}
|
|
203
344
|
instance.fetchInfo = async () => {
|
|
204
|
-
|
|
345
|
+
const header = await instance.promisifyWsMessage(_electrumApi.headersSubscribeMessage.call(void 0, ))
|
|
346
|
+
return {
|
|
347
|
+
name: pluginInfo.currencyInfo.displayName, // unused
|
|
348
|
+
shortcut: pluginInfo.currencyInfo.currencyCode, // unused
|
|
349
|
+
decimals: 8, // unused
|
|
350
|
+
version: '', // unused
|
|
351
|
+
bestHeight: header.height,
|
|
352
|
+
bestHash: headerToBlockHash(header.hex),
|
|
353
|
+
block0Hash: '', // unused
|
|
354
|
+
testnet: false, // unused
|
|
355
|
+
backend: undefined // unused
|
|
356
|
+
}
|
|
205
357
|
}
|
|
206
358
|
instance.fetchTransaction = async () => {
|
|
207
359
|
throw new Error('fetchTransaction not supported for Electrum connections')
|
|
208
360
|
}
|
|
209
361
|
|
|
210
|
-
|
|
211
|
-
|
|
362
|
+
const { watchBlocks } = instance
|
|
363
|
+
instance.watchBlocks = deferredBlockSub => {
|
|
364
|
+
isWatchingBlocks = true
|
|
365
|
+
watchBlocks(deferredBlockSub)
|
|
212
366
|
}
|
|
213
|
-
instance.
|
|
367
|
+
instance.watchAddresses = async () => {
|
|
214
368
|
return // Fail gracefully
|
|
215
369
|
}
|
|
216
370
|
|
|
217
371
|
return instance
|
|
218
372
|
} exports.makeBlockbookElectrum = makeBlockbookElectrum;
|
|
373
|
+
|
|
374
|
+
/** True for the URIs of Electrum servers, as opposed to Blockbook servers. */
|
|
375
|
+
const isElectrumUri = (uri) =>
|
|
376
|
+
/^electrumwss?:/.test(uri); exports.isElectrumUri = isElectrumUri
|
|
377
|
+
|
|
378
|
+
// Electrum indexes scripts by their SHA-256, in reverse byte order:
|
|
379
|
+
const toElectrumScriptHash = (scriptPubkey) =>
|
|
380
|
+
_altcoinjs.crypto.sha256(scriptPubkey).reverse().toString('hex')
|
|
381
|
+
|
|
382
|
+
// A block's hash is its header's double SHA-256, in reverse byte order:
|
|
383
|
+
const headerToBlockHash = (headerHex) =>
|
|
384
|
+
_altcoinjs.crypto.hash256(Buffer.from(headerHex, 'hex')).reverse().toString('hex')
|
|
@@ -267,6 +267,23 @@ const asResponseDefault = (raw) => [
|
|
|
267
267
|
}
|
|
268
268
|
}
|
|
269
269
|
|
|
270
|
+
// A task resumes with each response or failure, and then makes its next
|
|
271
|
+
// request or reports with a boolean that it is done:
|
|
272
|
+
const submitNextTask = (
|
|
273
|
+
generator,
|
|
274
|
+
resumed
|
|
275
|
+
) => {
|
|
276
|
+
resumed
|
|
277
|
+
.then(result => {
|
|
278
|
+
const task = result.value
|
|
279
|
+
if (typeof task === 'boolean') return
|
|
280
|
+
instance.submitTask(task, generator)
|
|
281
|
+
})
|
|
282
|
+
.catch(e => {
|
|
283
|
+
log.error(e.message)
|
|
284
|
+
})
|
|
285
|
+
}
|
|
286
|
+
|
|
270
287
|
const onTimer = () => {
|
|
271
288
|
log(`socket timer with server ${uri} expired, check if healthCheck needed`)
|
|
272
289
|
const now = Date.now() - TIMER_SLACK
|
|
@@ -283,11 +300,10 @@ const asResponseDefault = (raw) => [
|
|
|
283
300
|
|
|
284
301
|
for (const [id, request] of Object.entries(pendingRequests)) {
|
|
285
302
|
if (request.startTime + timeout < now) {
|
|
286
|
-
|
|
287
|
-
.
|
|
288
|
-
.
|
|
289
|
-
|
|
290
|
-
})
|
|
303
|
+
submitNextTask(
|
|
304
|
+
request.generator,
|
|
305
|
+
request.generator.throw(new Error(`Timeout for request ${id}`))
|
|
306
|
+
)
|
|
291
307
|
_utils.removeItem.call(void 0, pendingRequests, id)
|
|
292
308
|
}
|
|
293
309
|
}
|
|
@@ -358,23 +374,13 @@ const asResponseDefault = (raw) => [
|
|
|
358
374
|
} else {
|
|
359
375
|
nextValue = responseMessage.data
|
|
360
376
|
}
|
|
361
|
-
|
|
362
|
-
.
|
|
363
|
-
.
|
|
364
|
-
|
|
365
|
-
if (typeof task === 'boolean') {
|
|
366
|
-
return
|
|
367
|
-
}
|
|
368
|
-
instance.submitTask(task, request.generator)
|
|
369
|
-
})
|
|
370
|
-
.catch(e => {
|
|
371
|
-
log.error(e.message)
|
|
372
|
-
})
|
|
377
|
+
submitNextTask(
|
|
378
|
+
request.generator,
|
|
379
|
+
request.generator.next(nextValue)
|
|
380
|
+
)
|
|
373
381
|
} catch (error) {
|
|
374
382
|
console.log({ uri, error, response: responseMessage, request })
|
|
375
|
-
request.generator.throw(error)
|
|
376
|
-
log.error(e.message)
|
|
377
|
-
})
|
|
383
|
+
submitNextTask(request.generator, request.generator.throw(error))
|
|
378
384
|
}
|
|
379
385
|
continue
|
|
380
386
|
}
|
|
@@ -144,13 +144,32 @@ var _cleaners = require('cleaners');
|
|
|
144
144
|
const pingMessage = () => {
|
|
145
145
|
return {
|
|
146
146
|
method: 'server.ping',
|
|
147
|
-
|
|
147
|
+
// Electrum servers such as Fulcrum reject the `{}` the socket sends for
|
|
148
|
+
// missing params as named params, so an empty array is required:
|
|
149
|
+
params: [],
|
|
148
150
|
cleaner: exports.asElectrumResponse.call(void 0, exports.asPingResponse)
|
|
149
151
|
}
|
|
150
152
|
}; exports.pingMessage = pingMessage
|
|
151
153
|
|
|
152
154
|
const asPingResponse = _cleaners.asMaybe.call(void 0, _cleaners.asUndefined); exports.asPingResponse = asPingResponse
|
|
153
155
|
|
|
156
|
+
/**
|
|
157
|
+
* Subscribe to Headers (Get Block Height)
|
|
158
|
+
*/
|
|
159
|
+
const headersSubscribeMessage = () => ({
|
|
160
|
+
method: 'blockchain.headers.subscribe',
|
|
161
|
+
params: [],
|
|
162
|
+
cleaner: exports.asElectrumResponse.call(void 0, exports.asHeadersSubscribeResponse)
|
|
163
|
+
}); exports.headersSubscribeMessage = headersSubscribeMessage
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
// The chain tip's header, which the server also sends for each new tip:
|
|
168
|
+
const asHeadersSubscribeResponse = _cleaners.asObject.call(void 0, {
|
|
169
|
+
height: _cleaners.asNumber,
|
|
170
|
+
hex: _cleaners.asString
|
|
171
|
+
}); exports.asHeadersSubscribeResponse = asHeadersSubscribeResponse
|
|
172
|
+
|
|
154
173
|
/**
|
|
155
174
|
* List Unspent (Get UTXOs)
|
|
156
175
|
*/
|
|
@@ -201,3 +220,32 @@ var _cleaners = require('cleaners');
|
|
|
201
220
|
}); exports.transactionMessage = transactionMessage
|
|
202
221
|
|
|
203
222
|
const asTransactionResponse = exports.asElectrumTransaction; exports.asTransactionResponse = asTransactionResponse
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Get Raw Transaction
|
|
226
|
+
*/
|
|
227
|
+
const rawTransactionMessage = (
|
|
228
|
+
txHash
|
|
229
|
+
) => ({
|
|
230
|
+
method: 'blockchain.transaction.get',
|
|
231
|
+
params: [txHash, false],
|
|
232
|
+
cleaner: exports.asElectrumResponse.call(void 0, exports.asRawTransactionResponse)
|
|
233
|
+
}); exports.rawTransactionMessage = rawTransactionMessage
|
|
234
|
+
|
|
235
|
+
const asRawTransactionResponse = _cleaners.asString; exports.asRawTransactionResponse = asRawTransactionResponse
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Broadcast Transaction
|
|
239
|
+
*/
|
|
240
|
+
const broadcastTransactionMessage = (
|
|
241
|
+
rawTx
|
|
242
|
+
) => ({
|
|
243
|
+
method: 'blockchain.transaction.broadcast',
|
|
244
|
+
params: [rawTx],
|
|
245
|
+
cleaner: exports.asElectrumResponse.call(void 0, exports.asBroadcastTransactionResponse)
|
|
246
|
+
}); exports.broadcastTransactionMessage = broadcastTransactionMessage
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
// The server answers with the transaction's txid:
|
|
251
|
+
const asBroadcastTransactionResponse = _cleaners.asString; exports.asBroadcastTransactionResponse = asBroadcastTransactionResponse
|
|
@@ -161,6 +161,16 @@ export interface CoinInfo {
|
|
|
161
161
|
* build-in replay-protection mechanisms.
|
|
162
162
|
*/
|
|
163
163
|
sighash?: number;
|
|
164
|
+
/**
|
|
165
|
+
* Optional nLockTime value set on every transaction. This is used by forks
|
|
166
|
+
* whose nodes treat a magic nLockTime as final while the parent chain reads
|
|
167
|
+
* it as an unreachable block height, making the fork's transactions
|
|
168
|
+
* impossible to replay on the parent chain.
|
|
169
|
+
*
|
|
170
|
+
* Transactions for these currencies always have a non-final input sequence,
|
|
171
|
+
* since nodes ignore nLockTime when every input sequence is final.
|
|
172
|
+
*/
|
|
173
|
+
lockTime?: number;
|
|
164
174
|
/**
|
|
165
175
|
* A function to be passed to AltcoinJS `signInput` method. This is used to
|
|
166
176
|
* get the input hash for the signature algorithm before signing the input.
|
|
@@ -52,6 +52,7 @@ export interface ServerStates {
|
|
|
52
52
|
stop: () => void;
|
|
53
53
|
serverCanGetTx: (uri: string, txid: string) => boolean;
|
|
54
54
|
serverCanGetAddress: (uri: string, address: string) => boolean;
|
|
55
|
+
serverCanGetAddressHistory: (uri: string, address: string) => boolean;
|
|
55
56
|
serverIsAwareOfAddress: (uri: string, address: string) => boolean;
|
|
56
57
|
getServerState: (uri: string) => ServerState | undefined;
|
|
57
58
|
refillServers: () => void;
|
|
@@ -42,6 +42,8 @@ interface CommonParams {
|
|
|
42
42
|
emitter: EngineEmitter;
|
|
43
43
|
taskCache: TaskCache;
|
|
44
44
|
pendingTimeouts: Set<NodeJS.Timeout>;
|
|
45
|
+
electrumHistoryAddresses: Set<string>;
|
|
46
|
+
reopenAddresses: (addresses: Iterable<string>) => number;
|
|
45
47
|
updateProgressRatio: (scriptPubkey: string, process: AddressProcess) => Promise<void>;
|
|
46
48
|
updateSeenTxCheckpoint: () => void;
|
|
47
49
|
io: EdgeIo;
|
|
@@ -12,6 +12,8 @@ export { info as digibyte } from './digibyte';
|
|
|
12
12
|
export { info as dogecoin } from './dogecoin';
|
|
13
13
|
export { info as eboost } from './eboost';
|
|
14
14
|
export { info as ecash } from './ecash';
|
|
15
|
+
export { info as ecashbitcoin } from './ecashbitcoin';
|
|
16
|
+
export { info as ecashbitcoinbetanet } from './ecashbitcoinbetanet';
|
|
15
17
|
export { info as feathercoin } from './feathercoin';
|
|
16
18
|
export { info as groestlcoin } from './groestlcoin';
|
|
17
19
|
export { info as litecoin } from './litecoin';
|
|
@@ -2,7 +2,7 @@ import { Cleaner } from 'cleaners';
|
|
|
2
2
|
import { EdgeLog, EdgeTransaction } from 'edge-core-js/types';
|
|
3
3
|
import { EngineEmitter } from '../../plugin/EngineEmitter';
|
|
4
4
|
import { UtxoInitOptions } from '../engine/types';
|
|
5
|
-
import { AddressResponse, AddresssMessageParams, AddressUtxosResponse, BlockbookTask, BlockbookTransaction, BroadcastTxResponse, InfoResponse, SubscribeAddressResponse, TransactionResponse } from './blockbookApi';
|
|
5
|
+
import { AddressResponse, AddresssMessageParams, AddressUtxosResponse, BlockbookTask, BlockbookTransaction, BroadcastTxResponse, InfoResponse, SubscribeAddressResponse, SubscribeNewBlockResponse, TransactionResponse } from './blockbookApi';
|
|
6
6
|
import Deferred from './Deferred';
|
|
7
7
|
import { TaskGeneratorFn, WsResponse, WsTaskGenerator } from './Socket';
|
|
8
8
|
import { SocketEmitter } from './SocketEmitter';
|
|
@@ -40,6 +40,8 @@ interface BlockbookConfig {
|
|
|
40
40
|
log: EdgeLog;
|
|
41
41
|
taskGeneratorFn: TaskGeneratorFn;
|
|
42
42
|
ping?: () => Promise<void>;
|
|
43
|
+
/** The new-block subscription, for servers that are not Blockbook. */
|
|
44
|
+
subscribeNewBlockMessage?: () => BlockbookTask<SubscribeNewBlockResponse>;
|
|
43
45
|
socketEmitter: SocketEmitter;
|
|
44
46
|
walletId: string;
|
|
45
47
|
/** Socket timing overrides, for tests. */
|
|
@@ -25,3 +25,5 @@ export interface ElectrumResponseMessage {
|
|
|
25
25
|
} | undefined;
|
|
26
26
|
}
|
|
27
27
|
export declare function makeBlockbookElectrum(config: BlockbookElectrumConfig): Blockbook;
|
|
28
|
+
/** True for the URIs of Electrum servers, as opposed to Blockbook servers. */
|
|
29
|
+
export declare const isElectrumUri: (uri: string) => boolean;
|
|
@@ -67,6 +67,15 @@ export declare const asElectrumResponse: <T>(asT: Cleaner<T>) => Cleaner<T>;
|
|
|
67
67
|
export declare const pingMessage: () => ElectrumTask<PingResponse>;
|
|
68
68
|
export declare type PingResponse = ReturnType<typeof asPingResponse>;
|
|
69
69
|
export declare const asPingResponse: Cleaner<undefined>;
|
|
70
|
+
/**
|
|
71
|
+
* Subscribe to Headers (Get Block Height)
|
|
72
|
+
*/
|
|
73
|
+
export declare const headersSubscribeMessage: () => ElectrumTask<HeadersSubscribeResponse>;
|
|
74
|
+
export declare type HeadersSubscribeResponse = ReturnType<typeof asHeadersSubscribeResponse>;
|
|
75
|
+
export declare const asHeadersSubscribeResponse: import("cleaners").ObjectCleaner<{
|
|
76
|
+
height: number;
|
|
77
|
+
hex: string;
|
|
78
|
+
}>;
|
|
70
79
|
/**
|
|
71
80
|
* List Unspent (Get UTXOs)
|
|
72
81
|
*/
|
|
@@ -88,3 +97,15 @@ export declare const asAddressTransactionsResponse: Cleaner<{
|
|
|
88
97
|
export declare const transactionMessage: (txHash: string, asAddress?: Cleaner<string>) => ElectrumTask<TransactionResponse>;
|
|
89
98
|
export declare type TransactionResponse = ElectrumTransaction;
|
|
90
99
|
export declare const asTransactionResponse: (asAddress?: Cleaner<string>) => Cleaner<ElectrumTransaction>;
|
|
100
|
+
/**
|
|
101
|
+
* Get Raw Transaction
|
|
102
|
+
*/
|
|
103
|
+
export declare const rawTransactionMessage: (txHash: string) => ElectrumTask<RawTransactionResponse>;
|
|
104
|
+
export declare type RawTransactionResponse = ReturnType<typeof asRawTransactionResponse>;
|
|
105
|
+
export declare const asRawTransactionResponse: Cleaner<string>;
|
|
106
|
+
/**
|
|
107
|
+
* Broadcast Transaction
|
|
108
|
+
*/
|
|
109
|
+
export declare const broadcastTransactionMessage: (rawTx: string) => ElectrumTask<BroadcastTransactionResponse>;
|
|
110
|
+
export declare type BroadcastTransactionResponse = ReturnType<typeof asBroadcastTransactionResponse>;
|
|
111
|
+
export declare const asBroadcastTransactionResponse: Cleaner<string>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "edge-currency-plugins",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.17.0",
|
|
4
4
|
"description": "Edge currency plugins to fetch and process wallet accounts, balances and transactions.",
|
|
5
5
|
"homepage": "https://edge.app/",
|
|
6
6
|
"repository": "https://github.com/EdgeApp/edge-currency-plugins",
|