@teleportdao/bitcoin 1.4.7 → 1.4.8
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/bitcoin-interface.d.ts +8 -61
- package/dist/bitcoin-interface.d.ts.map +1 -1
- package/dist/bitcoin-interface.js +6 -6
- package/dist/bitcoin-interface.js.map +1 -1
- package/dist/bundle.js +5 -5
- package/dist/mempool-space.d.ts +69 -0
- package/dist/mempool-space.d.ts.map +1 -0
- package/dist/mempool-space.js +266 -0
- package/dist/mempool-space.js.map +1 -0
- package/dist/transaction-builder/transaction-builder.d.ts.map +1 -1
- package/dist/transaction-builder/transaction-builder.js +23 -21
- package/dist/transaction-builder/transaction-builder.js.map +1 -1
- package/package.json +4 -4
- package/src/bitcoin-interface.ts +12 -13
- package/src/mempool-space.ts +255 -0
- package/src/transaction-builder/transaction-builder.ts +38 -20
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
import { AxiosInstance } from "axios"
|
|
2
|
+
import { getAxiosInstance } from "./utils/tools"
|
|
3
|
+
|
|
4
|
+
class BlockStream {
|
|
5
|
+
api: AxiosInstance
|
|
6
|
+
constructor(
|
|
7
|
+
testnet = false,
|
|
8
|
+
// not used yet
|
|
9
|
+
_config?: {
|
|
10
|
+
token?: string
|
|
11
|
+
},
|
|
12
|
+
) {
|
|
13
|
+
const baseUrl = testnet
|
|
14
|
+
? "https://mempool.space/testnet/api/v1"
|
|
15
|
+
: "https://mempool.space/api/v1"
|
|
16
|
+
this.api = getAxiosInstance({
|
|
17
|
+
baseUrl,
|
|
18
|
+
} as any)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async getOutspend(txId: string, index: number) {
|
|
22
|
+
let response = await this.api.get(`/tx/${txId}/outspend/${index}`)
|
|
23
|
+
return response.data
|
|
24
|
+
}
|
|
25
|
+
async getOutspends(txId: string) {
|
|
26
|
+
let response = await this.api.get(`/tx/${txId}/outspends`)
|
|
27
|
+
return response.data
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async getLatestBlockNumber() {
|
|
31
|
+
const result = await this.api.get(`/blocks/tip/height`)
|
|
32
|
+
return result.data
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async getBlockHash(blockNumber: number) {
|
|
36
|
+
const result = await this.api.get(`/block-height/${blockNumber}`)
|
|
37
|
+
return result.data
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async getBlockHeaderHex(blockNumber: number) {
|
|
41
|
+
const hash = await this.getBlockHash(blockNumber)
|
|
42
|
+
const result = await this.api.get(`/block/${hash}/header`)
|
|
43
|
+
return result.data
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// --------------
|
|
47
|
+
|
|
48
|
+
async getConfirmedTransactions(userAddress: string, lastReceivedTxId = "") {
|
|
49
|
+
const result = await this.api.get(`/address/${userAddress}/txs/chain/${lastReceivedTxId}`)
|
|
50
|
+
return result.data
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async getMempoolTransactions(userAddress: string) {
|
|
54
|
+
const result = await this.api.get(`/address/${userAddress}/txs/mempool`)
|
|
55
|
+
return result.data
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async getTransaction(txId: string) {
|
|
59
|
+
const result = await this.api.get(`/tx/${txId}`)
|
|
60
|
+
let tx = result.data
|
|
61
|
+
return {
|
|
62
|
+
txId: tx.txid,
|
|
63
|
+
version: tx.version,
|
|
64
|
+
locktime: tx.locktime,
|
|
65
|
+
blockTime: tx.status.block_time,
|
|
66
|
+
blockNumber: tx.status.block_height || null,
|
|
67
|
+
blockHash: tx.status.block_hash || null,
|
|
68
|
+
vout: tx.vout.map((vo: any) => ({
|
|
69
|
+
address: vo.scriptpubkey_address || null,
|
|
70
|
+
script: vo.scriptpubkey,
|
|
71
|
+
value: vo.value,
|
|
72
|
+
})),
|
|
73
|
+
vin: tx.vin.map((vi: any) => ({
|
|
74
|
+
txId: vi.txid,
|
|
75
|
+
index: vi.vout,
|
|
76
|
+
address: vi.prevout.scriptpubkey_address || null,
|
|
77
|
+
script: vi.prevout.scriptpubkey,
|
|
78
|
+
value: vi.prevout.value,
|
|
79
|
+
})),
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async getRawTransaction(txId: string) {
|
|
84
|
+
const result = await this.api.get(`/tx/${txId}/hex`)
|
|
85
|
+
return result.data
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async getTransactionHistory(userAddress: string, blockNumber = 0, lastSeenTxId = undefined) {
|
|
89
|
+
let maximumTxsLength = 15
|
|
90
|
+
// get transactions from lastSeenTxId to the end
|
|
91
|
+
let fetchTxs = true
|
|
92
|
+
let allResults = []
|
|
93
|
+
let lastReceivedTxId = ""
|
|
94
|
+
while (fetchTxs) {
|
|
95
|
+
let result = await this.getConfirmedTransactions(userAddress, lastReceivedTxId)
|
|
96
|
+
let lastSeenTxIdIndex = result.findIndex((tx: any) => tx.txid === lastSeenTxId)
|
|
97
|
+
result = result.filter((_value: any) => +_value.status.block_height > blockNumber)
|
|
98
|
+
result =
|
|
99
|
+
lastSeenTxIdIndex < 0
|
|
100
|
+
? result
|
|
101
|
+
: result.filter((_value: any, index: number) => index < lastSeenTxIdIndex)
|
|
102
|
+
allResults.push(...result)
|
|
103
|
+
lastReceivedTxId = result[result.length - 1]?.txid
|
|
104
|
+
fetchTxs = result.length === maximumTxsLength && lastSeenTxIdIndex < 0 && result.length !== 0
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return allResults.map((tx) => ({
|
|
108
|
+
address: userAddress,
|
|
109
|
+
txId: tx.txid,
|
|
110
|
+
version: tx.version,
|
|
111
|
+
locktime: tx.locktime,
|
|
112
|
+
blockNumber: tx.status.block_height || null,
|
|
113
|
+
blockHash: tx.status.block_hash || null,
|
|
114
|
+
blockTime: tx.status.block_time || null,
|
|
115
|
+
vout: tx.vout.map((vo: any) => ({
|
|
116
|
+
address: vo.scriptpubkey_address || null,
|
|
117
|
+
script: vo.scriptpubkey,
|
|
118
|
+
value: vo.value,
|
|
119
|
+
})),
|
|
120
|
+
vin: tx.vin.map((vi: any) => ({
|
|
121
|
+
txId: vi.txid,
|
|
122
|
+
index: vi.vout,
|
|
123
|
+
address: vi.prevout.scriptpubkey_address || null,
|
|
124
|
+
script: vi.prevout.scriptpubkey,
|
|
125
|
+
value: vi.prevout.value,
|
|
126
|
+
})),
|
|
127
|
+
}))
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
async getMempoolTransactionHistory(userAddress: string) {
|
|
131
|
+
let result = await this.getMempoolTransactions(userAddress)
|
|
132
|
+
return result.map((tx: any) => ({
|
|
133
|
+
address: userAddress,
|
|
134
|
+
txId: tx.txid,
|
|
135
|
+
version: tx.version,
|
|
136
|
+
locktime: tx.locktime,
|
|
137
|
+
vout: tx.vout.map((vo: any) => ({
|
|
138
|
+
address: vo.scriptpubkey_address || null,
|
|
139
|
+
script: vo.scriptpubkey,
|
|
140
|
+
value: vo.value,
|
|
141
|
+
})),
|
|
142
|
+
vin: tx.vin.map((vi: any) => ({
|
|
143
|
+
txId: vi.txid,
|
|
144
|
+
index: vi.vout,
|
|
145
|
+
address: vi.prevout.scriptpubkey_address || null,
|
|
146
|
+
script: vi.prevout.scriptpubkey,
|
|
147
|
+
value: vi.prevout.value,
|
|
148
|
+
})),
|
|
149
|
+
}))
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
async getMempoolTransactionHistoryForMultipleAddresses(addresses: string[], blockNumber = 0) {
|
|
153
|
+
const allPromises = []
|
|
154
|
+
for (let address of addresses) {
|
|
155
|
+
let promise = await this.getMempoolTransactionHistory(address)
|
|
156
|
+
allPromises.push(promise)
|
|
157
|
+
}
|
|
158
|
+
let result = await Promise.all(allPromises)
|
|
159
|
+
return result
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
async getTransactionHistoryForMultipleAddresses(userAddresses: string[], blockNumber = 0) {
|
|
163
|
+
const allPromises = []
|
|
164
|
+
for (let address of userAddresses) {
|
|
165
|
+
let promise = await this.getTransactionHistory(address, blockNumber)
|
|
166
|
+
allPromises.push(promise)
|
|
167
|
+
}
|
|
168
|
+
let result = await Promise.all(allPromises)
|
|
169
|
+
return result
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
async getUtxos(userAddress: string): Promise<
|
|
173
|
+
{
|
|
174
|
+
address: string
|
|
175
|
+
txId: string
|
|
176
|
+
index: number
|
|
177
|
+
value: number
|
|
178
|
+
blockNumber?: number
|
|
179
|
+
}[]
|
|
180
|
+
> {
|
|
181
|
+
const result = await this.api.get(`/address/${userAddress}/utxo`)
|
|
182
|
+
return result.data.map((tx: any) => ({
|
|
183
|
+
address: userAddress,
|
|
184
|
+
txId: tx.txid,
|
|
185
|
+
index: tx.vout,
|
|
186
|
+
value: tx.value,
|
|
187
|
+
blockNumber: tx.status?.block_height || undefined,
|
|
188
|
+
}))
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
async getBalance(address: string) {
|
|
192
|
+
let utxos = await this.getUtxos(address)
|
|
193
|
+
return utxos.reduce((a: any, tx: any) => a + Number(tx.value), 0)
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
async getBlockTransactionIds(blockHash: string) {
|
|
197
|
+
let result = await this.api.get(`/block/${blockHash}/txids`)
|
|
198
|
+
return result.data
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
async getFeeRate(speed: "normal" | "slow" | "fast" = "normal") {
|
|
202
|
+
let result = await this.api.get(`/fee-estimates`)
|
|
203
|
+
let fees = {
|
|
204
|
+
slow: +result.data[10],
|
|
205
|
+
normal: +result.data[6],
|
|
206
|
+
fast: +result.data[3],
|
|
207
|
+
}
|
|
208
|
+
return fees[speed] || fees.normal
|
|
209
|
+
}
|
|
210
|
+
async getRecommendedFeeRate(speed: "normal" | "slow" | "fast" = "normal") {
|
|
211
|
+
let result = await this.api.get(`/fees/recommended`)
|
|
212
|
+
|
|
213
|
+
let fees = {
|
|
214
|
+
slow: +result.data.economyFee,
|
|
215
|
+
normal: +result.data.halfHourFee,
|
|
216
|
+
fast: +result.data.fastestFee,
|
|
217
|
+
}
|
|
218
|
+
return fees[speed] || fees.normal
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
async sendRawTransaction(rawTransaction: string) {
|
|
222
|
+
const result = await this.api.post(`/tx`, rawTransaction)
|
|
223
|
+
return result.data
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// not used
|
|
227
|
+
|
|
228
|
+
async getMerkleProof(txId: string) {
|
|
229
|
+
let result = (await this.api.get(`/tx/${txId}/merkle-proof`)).data
|
|
230
|
+
let intermediateNodes = result.merkle.reduce(
|
|
231
|
+
(a: any, merkle: any) => a + Buffer.from(merkle, "hex").reverse().toString("hex"),
|
|
232
|
+
"0x",
|
|
233
|
+
)
|
|
234
|
+
let transactionIndex = result.pos
|
|
235
|
+
return {
|
|
236
|
+
intermediateNodes,
|
|
237
|
+
transactionIndex,
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
async getLatestBlock() {
|
|
242
|
+
const result = await this.api.get(`/blocks`)
|
|
243
|
+
// 10 newest blocks
|
|
244
|
+
const blocks = result.data
|
|
245
|
+
return blocks[0]
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
async getBlock(blockNumber: number) {
|
|
249
|
+
const blockHash = await this.getBlockHash(blockNumber)
|
|
250
|
+
const result = await this.api.get(`/block/${blockHash}`)
|
|
251
|
+
return result.data
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
export default BlockStream
|
|
@@ -19,7 +19,7 @@ const componentBytes = {
|
|
|
19
19
|
bytePerInput: {
|
|
20
20
|
p2pkh: TX_INPUT_BASE + TX_INPUT_P2PKH,
|
|
21
21
|
p2wpkh: TX_INPUT_BASE + TX_INPUT_P2WPKH,
|
|
22
|
-
|
|
22
|
+
"p2sh-p2wpkh": TX_INPUT_BASE + TX_INPUT_P2SH_P2PKH,
|
|
23
23
|
},
|
|
24
24
|
baseTxBytes: TX_EMPTY_SIZE,
|
|
25
25
|
bytePerOutput: TX_OUTPUT_BASE + TX_OUTPUT_P2PKH,
|
|
@@ -230,29 +230,47 @@ class BaseBitcoinLikeTransaction {
|
|
|
230
230
|
if (!inputs || !outputs) {
|
|
231
231
|
throw new Error("not enough balance")
|
|
232
232
|
}
|
|
233
|
+
// console.log(inputs, outputs, feeRate)
|
|
234
|
+
const inputsSizes = inputs.map(
|
|
235
|
+
(i) =>
|
|
236
|
+
componentBytes.bytePerInput[
|
|
237
|
+
i.signerInfo.addressType as keyof typeof componentBytes.bytePerInput
|
|
238
|
+
],
|
|
239
|
+
)
|
|
240
|
+
const outputSizes = outputs.length * componentBytes.bytePerOutput
|
|
241
|
+
|
|
242
|
+
// console.log(inputsSizes, outputSizes)
|
|
243
|
+
|
|
244
|
+
const txSize =
|
|
245
|
+
componentBytes.baseTxBytes +
|
|
246
|
+
inputsSizes.reduce((a, c) => a + c, 0) +
|
|
247
|
+
outputSizes +
|
|
248
|
+
componentBytes.bytePerOutput
|
|
249
|
+
|
|
250
|
+
const txFee = txSize * feeRate
|
|
251
|
+
let diff = fee - txFee
|
|
252
|
+
// console.log({ txSize, txFee, diff, fee })
|
|
253
|
+
|
|
233
254
|
let changeIndex = outputs.findIndex((x) => !x?.address && !x.script && (x.value || 0) > 0)
|
|
234
255
|
let change: ChangeTarget | undefined
|
|
235
|
-
if (changeIndex >= 0) {
|
|
256
|
+
if (changeIndex >= 0 || diff > 600) {
|
|
257
|
+
if (changeIndex >= 0) {
|
|
258
|
+
diff = diff + componentBytes.bytePerOutput * feeRate
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
if (diff < 0) {
|
|
262
|
+
diff = 0
|
|
263
|
+
}
|
|
264
|
+
|
|
236
265
|
if (!changeObject) throw new Error("change not exist")
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
pubkey: Buffer.from(changeObject.publicKey, "hex"),
|
|
245
|
-
masterFingerprint: Buffer.from(changeObject.masterFingerprint, "hex"),
|
|
246
|
-
},
|
|
247
|
-
],
|
|
248
|
-
}
|
|
249
|
-
} else {
|
|
250
|
-
change = {
|
|
251
|
-
address: changeObject.address,
|
|
252
|
-
value: outputs[changeIndex].value,
|
|
253
|
-
}
|
|
266
|
+
change = {
|
|
267
|
+
address: changeObject.address,
|
|
268
|
+
value: changeIndex >= 0 ? outputs[changeIndex].value + diff : diff,
|
|
269
|
+
}
|
|
270
|
+
fee = fee - diff
|
|
271
|
+
if (changeIndex >= 0) {
|
|
272
|
+
outputs.splice(changeIndex, 1)
|
|
254
273
|
}
|
|
255
|
-
outputs.splice(changeIndex, 1)
|
|
256
274
|
}
|
|
257
275
|
|
|
258
276
|
return {
|