@subwallet/extension-base 1.3.40-0 → 1.3.42-0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (232) hide show
  1. package/background/KoniTypes.d.ts +121 -4
  2. package/background/KoniTypes.js +18 -0
  3. package/background/errors/BitcoinProviderError.d.ts +6 -0
  4. package/background/errors/BitcoinProviderError.js +47 -0
  5. package/cjs/background/KoniTypes.js +20 -1
  6. package/cjs/background/errors/BitcoinProviderError.js +54 -0
  7. package/cjs/constants/bitcoin.js +22 -0
  8. package/cjs/constants/environment.js +4 -2
  9. package/cjs/constants/index.js +16 -1
  10. package/cjs/core/logic-validation/recipientAddress.js +9 -0
  11. package/cjs/core/logic-validation/transfer.js +25 -5
  12. package/cjs/core/types.js +1 -0
  13. package/cjs/core/utils.js +15 -1
  14. package/cjs/koni/background/handlers/Extension.js +96 -41
  15. package/cjs/koni/background/handlers/State.js +52 -11
  16. package/cjs/packageInfo.js +1 -1
  17. package/cjs/services/balance-service/helpers/subscribe/bitcoin.js +94 -0
  18. package/cjs/services/balance-service/helpers/subscribe/evm.js +6 -1
  19. package/cjs/services/balance-service/helpers/subscribe/index.js +19 -7
  20. package/cjs/services/balance-service/index.js +32 -4
  21. package/cjs/services/balance-service/transfer/bitcoin-transfer.js +119 -0
  22. package/cjs/services/balance-service/transfer/token.js +2 -0
  23. package/cjs/services/balance-service/transfer/xcm/index.js +15 -9
  24. package/cjs/services/balance-service/transfer/xcm/utils.js +12 -14
  25. package/cjs/services/base/types.js +2 -0
  26. package/cjs/services/chain-service/constants.js +18 -6
  27. package/cjs/services/chain-service/handler/CardanoApi.js +25 -35
  28. package/cjs/services/chain-service/handler/bitcoin/BitcoinApi.js +105 -0
  29. package/cjs/services/chain-service/handler/bitcoin/BitcoinChainHandler.js +78 -0
  30. package/cjs/services/chain-service/handler/bitcoin/strategy/BlockStreamTestnet/blockstream-testnet-strategy.js +371 -0
  31. package/cjs/services/chain-service/handler/bitcoin/strategy/BlockStreamTestnet/index.js +19 -0
  32. package/cjs/services/chain-service/handler/bitcoin/strategy/BlockStreamTestnet/mempool-testnet-strategy.js +368 -0
  33. package/cjs/services/chain-service/handler/bitcoin/strategy/SubWalletMainnet/index.js +302 -0
  34. package/cjs/services/chain-service/handler/bitcoin/strategy/types.js +1 -0
  35. package/cjs/services/chain-service/index.js +27 -3
  36. package/cjs/services/chain-service/utils/index.js +57 -4
  37. package/cjs/services/chain-service/utils/patch.js +1 -1
  38. package/cjs/services/earning-service/handlers/native-staking/tao.js +4 -38
  39. package/cjs/services/event-service/index.js +4 -0
  40. package/cjs/services/fee-service/service.js +8 -3
  41. package/cjs/services/hiro-service/index.js +96 -0
  42. package/cjs/services/hiro-service/utils/index.js +85 -0
  43. package/cjs/services/history-service/bitcoin-history.js +58 -0
  44. package/cjs/services/history-service/helpers/recoverHistoryStatus.js +96 -4
  45. package/cjs/services/history-service/index.js +41 -3
  46. package/cjs/services/keyring-service/context/handlers/Derive.js +1 -1
  47. package/cjs/services/keyring-service/context/handlers/Migration.js +2 -2
  48. package/cjs/services/keyring-service/context/handlers/Mnemonic.js +4 -3
  49. package/cjs/services/migration-service/scripts/MigrateNewUnifiedAccount.js +29 -0
  50. package/cjs/services/migration-service/scripts/index.js +3 -1
  51. package/cjs/services/request-service/handler/BitcoinRequestHandler.js +440 -0
  52. package/cjs/services/request-service/index.js +29 -3
  53. package/cjs/services/rune-service/index.js +105 -0
  54. package/cjs/services/swap-service/handler/chainflip-handler.js +29 -18
  55. package/cjs/services/swap-service/handler/kyber-handler.js +5 -9
  56. package/cjs/services/swap-service/handler/simpleswap-handler.js +4 -7
  57. package/cjs/services/swap-service/handler/uniswap-handler.js +5 -12
  58. package/cjs/services/swap-service/utils.js +46 -37
  59. package/cjs/services/transaction-service/helpers/index.js +7 -1
  60. package/cjs/services/transaction-service/index.js +136 -15
  61. package/cjs/services/transaction-service/utils.js +6 -3
  62. package/cjs/strategy/api-request-strategy/context/base.js +31 -0
  63. package/cjs/strategy/api-request-strategy/index.js +90 -0
  64. package/cjs/strategy/api-request-strategy/types.js +1 -0
  65. package/cjs/strategy/api-request-strategy/utils/index.js +33 -0
  66. package/cjs/types/account/info/keyring.js +1 -1
  67. package/cjs/types/bitcoin.js +24 -0
  68. package/cjs/types/environment.js +19 -0
  69. package/cjs/types/fee/bitcoin.js +1 -0
  70. package/cjs/types/fee/index.js +11 -0
  71. package/cjs/types/index.js +11 -0
  72. package/cjs/utils/account/analyze.js +3 -3
  73. package/cjs/utils/account/common.js +16 -6
  74. package/cjs/utils/account/derive/info/solo.js +68 -19
  75. package/cjs/utils/account/derive/info/unified.js +2 -0
  76. package/cjs/utils/account/derive/validate.js +70 -2
  77. package/cjs/utils/account/transform.js +11 -5
  78. package/cjs/utils/bitcoin/common.js +98 -0
  79. package/cjs/utils/bitcoin/fee.js +21 -0
  80. package/cjs/utils/bitcoin/index.js +38 -0
  81. package/cjs/utils/bitcoin/utxo-management.js +281 -0
  82. package/cjs/utils/environment.js +30 -2
  83. package/cjs/utils/fee/transfer.js +48 -0
  84. package/cjs/utils/index.js +15 -1
  85. package/constants/bitcoin.d.ts +3 -0
  86. package/constants/bitcoin.js +13 -0
  87. package/constants/environment.d.ts +1 -0
  88. package/constants/environment.js +2 -1
  89. package/constants/index.d.ts +2 -0
  90. package/constants/index.js +3 -1
  91. package/core/logic-validation/recipientAddress.js +10 -1
  92. package/core/logic-validation/transfer.d.ts +2 -2
  93. package/core/logic-validation/transfer.js +27 -7
  94. package/core/types.d.ts +1 -0
  95. package/core/types.js +1 -0
  96. package/core/utils.d.ts +1 -0
  97. package/core/utils.js +15 -2
  98. package/koni/background/handlers/Extension.d.ts +2 -0
  99. package/koni/background/handlers/Extension.js +95 -42
  100. package/koni/background/handlers/State.d.ts +7 -3
  101. package/koni/background/handlers/State.js +52 -12
  102. package/package.json +149 -8
  103. package/packageInfo.js +1 -1
  104. package/services/balance-service/helpers/subscribe/bitcoin.d.ts +2 -0
  105. package/services/balance-service/helpers/subscribe/bitcoin.js +87 -0
  106. package/services/balance-service/helpers/subscribe/evm.js +6 -1
  107. package/services/balance-service/helpers/subscribe/index.d.ts +2 -2
  108. package/services/balance-service/helpers/subscribe/index.js +20 -8
  109. package/services/balance-service/index.d.ts +2 -0
  110. package/services/balance-service/index.js +32 -4
  111. package/services/balance-service/transfer/bitcoin-transfer.d.ts +14 -0
  112. package/services/balance-service/transfer/bitcoin-transfer.js +112 -0
  113. package/services/balance-service/transfer/token.js +2 -0
  114. package/services/balance-service/transfer/xcm/index.js +15 -9
  115. package/services/balance-service/transfer/xcm/utils.d.ts +2 -0
  116. package/services/balance-service/transfer/xcm/utils.js +12 -14
  117. package/services/base/types.d.ts +2 -0
  118. package/services/base/types.js +2 -0
  119. package/services/chain-service/constants.d.ts +7 -0
  120. package/services/chain-service/constants.js +12 -5
  121. package/services/chain-service/handler/CardanoApi.d.ts +1 -5
  122. package/services/chain-service/handler/CardanoApi.js +26 -34
  123. package/services/chain-service/handler/bitcoin/BitcoinApi.d.ts +31 -0
  124. package/services/chain-service/handler/bitcoin/BitcoinApi.js +98 -0
  125. package/services/chain-service/handler/bitcoin/BitcoinChainHandler.d.ts +16 -0
  126. package/services/chain-service/handler/bitcoin/BitcoinChainHandler.js +70 -0
  127. package/services/chain-service/handler/bitcoin/strategy/BlockStreamTestnet/blockstream-testnet-strategy.d.ts +28 -0
  128. package/services/chain-service/handler/bitcoin/strategy/BlockStreamTestnet/blockstream-testnet-strategy.js +362 -0
  129. package/services/chain-service/handler/bitcoin/strategy/BlockStreamTestnet/index.d.ts +2 -0
  130. package/services/chain-service/handler/bitcoin/strategy/BlockStreamTestnet/index.js +5 -0
  131. package/services/chain-service/handler/bitcoin/strategy/BlockStreamTestnet/mempool-testnet-strategy.d.ts +28 -0
  132. package/services/chain-service/handler/bitcoin/strategy/BlockStreamTestnet/mempool-testnet-strategy.js +359 -0
  133. package/services/chain-service/handler/bitcoin/strategy/SubWalletMainnet/index.d.ts +28 -0
  134. package/services/chain-service/handler/bitcoin/strategy/SubWalletMainnet/index.js +293 -0
  135. package/services/chain-service/handler/bitcoin/strategy/types.d.ts +291 -0
  136. package/services/chain-service/handler/bitcoin/strategy/types.js +1 -0
  137. package/services/chain-service/index.d.ts +3 -0
  138. package/services/chain-service/index.js +31 -5
  139. package/services/chain-service/types.d.ts +20 -0
  140. package/services/chain-service/utils/index.d.ts +4 -0
  141. package/services/chain-service/utils/index.js +50 -4
  142. package/services/chain-service/utils/patch.js +1 -1
  143. package/services/earning-service/handlers/native-staking/tao.d.ts +0 -11
  144. package/services/earning-service/handlers/native-staking/tao.js +4 -24
  145. package/services/event-service/index.d.ts +3 -0
  146. package/services/event-service/index.js +4 -0
  147. package/services/event-service/types.d.ts +3 -0
  148. package/services/fee-service/service.js +8 -3
  149. package/services/hiro-service/index.d.ts +17 -0
  150. package/services/hiro-service/index.js +88 -0
  151. package/services/hiro-service/utils/index.d.ts +6 -0
  152. package/services/hiro-service/utils/index.js +72 -0
  153. package/services/history-service/bitcoin-history.d.ts +4 -0
  154. package/services/history-service/bitcoin-history.js +52 -0
  155. package/services/history-service/helpers/recoverHistoryStatus.d.ts +3 -1
  156. package/services/history-service/helpers/recoverHistoryStatus.js +96 -4
  157. package/services/history-service/index.d.ts +1 -0
  158. package/services/history-service/index.js +42 -4
  159. package/services/keyring-service/context/handlers/Derive.js +2 -2
  160. package/services/keyring-service/context/handlers/Migration.js +2 -2
  161. package/services/keyring-service/context/handlers/Mnemonic.js +4 -3
  162. package/services/migration-service/scripts/MigrateNewUnifiedAccount.d.ts +4 -0
  163. package/services/migration-service/scripts/MigrateNewUnifiedAccount.js +21 -0
  164. package/services/migration-service/scripts/index.js +3 -1
  165. package/services/request-service/handler/BitcoinRequestHandler.d.ts +23 -0
  166. package/services/request-service/handler/BitcoinRequestHandler.js +427 -0
  167. package/services/request-service/index.d.ts +9 -2
  168. package/services/request-service/index.js +25 -3
  169. package/services/rune-service/index.d.ts +17 -0
  170. package/services/rune-service/index.js +97 -0
  171. package/services/swap-service/handler/chainflip-handler.d.ts +0 -2
  172. package/services/swap-service/handler/chainflip-handler.js +25 -13
  173. package/services/swap-service/handler/kyber-handler.d.ts +0 -1
  174. package/services/swap-service/handler/kyber-handler.js +5 -8
  175. package/services/swap-service/handler/simpleswap-handler.d.ts +0 -1
  176. package/services/swap-service/handler/simpleswap-handler.js +4 -6
  177. package/services/swap-service/handler/uniswap-handler.js +6 -13
  178. package/services/swap-service/utils.d.ts +0 -13
  179. package/services/swap-service/utils.js +46 -34
  180. package/services/transaction-service/helpers/index.d.ts +3 -1
  181. package/services/transaction-service/helpers/index.js +5 -0
  182. package/services/transaction-service/index.d.ts +3 -5
  183. package/services/transaction-service/index.js +135 -16
  184. package/services/transaction-service/types.d.ts +12 -2
  185. package/services/transaction-service/utils.js +7 -4
  186. package/strategy/api-request-strategy/context/base.d.ts +15 -0
  187. package/strategy/api-request-strategy/context/base.js +24 -0
  188. package/strategy/api-request-strategy/index.d.ts +15 -0
  189. package/strategy/api-request-strategy/index.js +83 -0
  190. package/strategy/api-request-strategy/types.d.ts +22 -0
  191. package/strategy/api-request-strategy/types.js +1 -0
  192. package/strategy/api-request-strategy/utils/index.d.ts +2 -0
  193. package/strategy/api-request-strategy/utils/index.js +23 -0
  194. package/types/account/info/keyring.d.ts +1 -1
  195. package/types/account/info/keyring.js +1 -1
  196. package/types/balance/index.d.ts +4 -1
  197. package/types/balance/transfer.d.ts +17 -0
  198. package/types/bitcoin.d.ts +93 -0
  199. package/types/bitcoin.js +17 -0
  200. package/types/environment.d.ts +9 -0
  201. package/types/environment.js +13 -0
  202. package/types/fee/base.d.ts +4 -1
  203. package/types/fee/bitcoin.d.ts +18 -0
  204. package/types/fee/bitcoin.js +1 -0
  205. package/types/fee/index.d.ts +1 -0
  206. package/types/fee/index.js +2 -1
  207. package/types/fee/subscription.d.ts +4 -3
  208. package/types/index.d.ts +1 -0
  209. package/types/index.js +1 -0
  210. package/utils/account/analyze.js +4 -4
  211. package/utils/account/common.d.ts +7 -8
  212. package/utils/account/common.js +16 -6
  213. package/utils/account/derive/info/solo.js +70 -21
  214. package/utils/account/derive/info/unified.js +2 -0
  215. package/utils/account/derive/validate.d.ts +1 -0
  216. package/utils/account/derive/validate.js +68 -1
  217. package/utils/account/transform.d.ts +1 -1
  218. package/utils/account/transform.js +11 -5
  219. package/utils/bitcoin/common.d.ts +22 -0
  220. package/utils/bitcoin/common.js +88 -0
  221. package/utils/bitcoin/fee.d.ts +2 -0
  222. package/utils/bitcoin/fee.js +14 -0
  223. package/utils/bitcoin/index.d.ts +3 -0
  224. package/utils/bitcoin/index.js +6 -0
  225. package/utils/bitcoin/utxo-management.d.ts +33 -0
  226. package/utils/bitcoin/utxo-management.js +266 -0
  227. package/utils/environment.d.ts +2 -0
  228. package/utils/environment.js +27 -1
  229. package/utils/fee/transfer.d.ts +3 -1
  230. package/utils/fee/transfer.js +47 -1
  231. package/utils/index.d.ts +1 -0
  232. package/utils/index.js +6 -3
@@ -0,0 +1,359 @@
1
+ // Copyright 2019-2022 @subwallet/extension-base
2
+ // SPDX-License-Identifier: Apache-2.0
3
+
4
+ import { SWError } from '@subwallet/extension-base/background/errors/SWError';
5
+ import { HiroService } from '@subwallet/extension-base/services/hiro-service';
6
+ import { RunesService } from '@subwallet/extension-base/services/rune-service';
7
+ import { BaseApiRequestStrategy } from '@subwallet/extension-base/strategy/api-request-strategy';
8
+ import { BaseApiRequestContext } from '@subwallet/extension-base/strategy/api-request-strategy/context/base';
9
+ import { getRequest, postRequest } from '@subwallet/extension-base/strategy/api-request-strategy/utils';
10
+ import BigN from 'bignumber.js';
11
+ import EventEmitter from 'eventemitter3';
12
+ export class MempoolTestnetRequestStrategy extends BaseApiRequestStrategy {
13
+ timePerBlock = 0; // in milliseconds
14
+
15
+ constructor(url) {
16
+ const context = new BaseApiRequestContext();
17
+ super(context);
18
+ this.baseUrl = url;
19
+ this.isTestnet = url.includes('testnet');
20
+ }
21
+ headers = {
22
+ 'Content-Type': 'application/json'
23
+ };
24
+ isRateLimited() {
25
+ return false;
26
+ }
27
+ getUrl(path) {
28
+ return `${this.baseUrl}/${path}`;
29
+ }
30
+ getBlockTime() {
31
+ return this.addRequest(async () => {
32
+ const response = await getRequest(this.getUrl('blocks'), undefined, this.headers);
33
+ const blocks = await response.json();
34
+ if (!response.ok) {
35
+ throw new SWError('BlockStreamTestnetRequestStrategy.getBlockTime', 'Failed to fetch blocks');
36
+ }
37
+ const length = blocks.length;
38
+ const sortedBlocks = blocks.sort((a, b) => b.timestamp - a.timestamp);
39
+ const time = (sortedBlocks[0].timestamp - sortedBlocks[length - 1].timestamp) * 1000;
40
+ return time / length;
41
+ }, 0);
42
+ }
43
+ async computeBlockTime() {
44
+ let blockTime = this.timePerBlock;
45
+ if (blockTime > 0) {
46
+ return blockTime;
47
+ }
48
+ try {
49
+ blockTime = await this.getBlockTime();
50
+ this.timePerBlock = blockTime;
51
+ } catch (e) {
52
+ console.error('Failed to compute block time', e);
53
+ blockTime = (this.isTestnet ? 5 * 60 : 10 * 60) * 1000; // Default to 10 minutes if failed
54
+ }
55
+
56
+ // Cache block time in 60 seconds
57
+ setTimeout(() => {
58
+ this.timePerBlock = 0;
59
+ }, 60000);
60
+ return blockTime;
61
+ }
62
+ getAddressSummaryInfo(address) {
63
+ return this.addRequest(async () => {
64
+ const response = await getRequest(this.getUrl(`address/${address}`), undefined, this.headers);
65
+ if (!response.ok) {
66
+ throw new SWError('BlockStreamTestnetRequestStrategy.getAddressSummaryInfo', 'Failed to fetch address info');
67
+ }
68
+ const rsRaw = await response.json();
69
+ const chainBalance = rsRaw.chain_stats.funded_txo_sum - rsRaw.chain_stats.spent_txo_sum;
70
+ const pendingLocked = rsRaw.mempool_stats.spent_txo_sum; // Only consider spent UTXOs in mempool
71
+ const mempoolReceived = rsRaw.mempool_stats.funded_txo_sum; // Funds received in mempool (e.g., change)
72
+ const availableBalance = Math.max(0, chainBalance - pendingLocked + mempoolReceived); // Ensure balance is non-negative
73
+
74
+ const rs = {
75
+ address: rsRaw.address,
76
+ chain_stats: {
77
+ funded_txo_count: rsRaw.chain_stats.funded_txo_count,
78
+ funded_txo_sum: rsRaw.chain_stats.funded_txo_sum,
79
+ spent_txo_count: rsRaw.chain_stats.spent_txo_count,
80
+ spent_txo_sum: rsRaw.chain_stats.spent_txo_sum,
81
+ tx_count: rsRaw.chain_stats.tx_count
82
+ },
83
+ mempool_stats: {
84
+ funded_txo_count: rsRaw.mempool_stats.funded_txo_count,
85
+ funded_txo_sum: rsRaw.mempool_stats.funded_txo_sum,
86
+ spent_txo_count: rsRaw.mempool_stats.spent_txo_count,
87
+ spent_txo_sum: rsRaw.mempool_stats.spent_txo_sum,
88
+ tx_count: rsRaw.mempool_stats.tx_count
89
+ },
90
+ balance: availableBalance,
91
+ total_inscription: 0,
92
+ balance_rune: '0',
93
+ balance_inscription: '0'
94
+ };
95
+ return rs;
96
+ }, 0);
97
+ }
98
+ getAddressTransaction(address, limit = 100) {
99
+ return this.addRequest(async () => {
100
+ const response = await getRequest(this.getUrl(`address/${address}/txs`), {
101
+ limit: `${limit}`
102
+ }, this.headers);
103
+ if (!response.ok) {
104
+ throw new SWError('BlockStreamTestnetRequestStrategy.getAddressTransaction', 'Failed to fetch transactions');
105
+ }
106
+ return await response.json();
107
+ }, 1);
108
+ }
109
+ getTransactionStatus(txHash) {
110
+ return this.addRequest(async () => {
111
+ const response = await getRequest(this.getUrl(`tx/${txHash}/status`), undefined, {});
112
+ if (!response.ok) {
113
+ const errorText = await response.text();
114
+ throw new SWError('BlockStreamTestnetRequestStrategy.getTransactionStatus', `Failed to fetch transaction status: ${errorText}`);
115
+ }
116
+
117
+ // Blockstream API trả về object thô
118
+ const data = await response.json();
119
+ return {
120
+ confirmed: data.confirmed || false,
121
+ block_time: data.block_time || 0,
122
+ block_height: data.block_height,
123
+ block_hash: data.block_hash
124
+ };
125
+ }, 1);
126
+ }
127
+ getTransactionDetail(txHash) {
128
+ return this.addRequest(async () => {
129
+ const response = await getRequest(this.getUrl(`tx/${txHash}`), undefined, this.headers);
130
+ if (!response.ok) {
131
+ throw new SWError('BlockStreamTestnetRequestStrategy.getTransactionDetail', 'Failed to fetch transaction detail');
132
+ }
133
+ return await response.json();
134
+ }, 1);
135
+ }
136
+
137
+ // TODO: NOTE: Currently not in use. Recheck the response if you want to use it.
138
+ async getFeeRate() {
139
+ const blockTime = await this.computeBlockTime();
140
+ return await this.addRequest(async () => {
141
+ const response = await getRequest(this.getUrl('fee-estimates'), undefined, this.headers);
142
+ const estimates = await response.json();
143
+ if (!response.ok) {
144
+ throw new SWError('BlockStreamTestnetRequestStrategy.getFeeRate', 'Failed to fetch fee estimates');
145
+ }
146
+ const low = 6;
147
+ const average = 3;
148
+ const fast = 1;
149
+ const convertFee = fee => parseFloat(new BigN(fee).toFixed(2));
150
+ return {
151
+ type: 'bitcoin',
152
+ busyNetwork: false,
153
+ options: {
154
+ slow: {
155
+ feeRate: convertFee(estimates[low] || 10),
156
+ time: blockTime * low
157
+ },
158
+ average: {
159
+ feeRate: convertFee(estimates[average || 12]),
160
+ time: blockTime * average
161
+ },
162
+ fast: {
163
+ feeRate: convertFee(estimates[fast] || 15),
164
+ time: blockTime * fast
165
+ },
166
+ default: 'slow'
167
+ }
168
+ };
169
+ }, 0);
170
+ }
171
+ getRecommendedFeeRate() {
172
+ return this.addRequest(async () => {
173
+ const convertTimeMilisec = {
174
+ fastestFee: 10 * 60000,
175
+ halfHourFee: 30 * 60000,
176
+ hourFee: 60 * 60000
177
+ };
178
+ const defaultFeeInfo = {
179
+ type: 'bitcoin',
180
+ busyNetwork: false,
181
+ options: {
182
+ slow: {
183
+ feeRate: 1.5,
184
+ time: convertTimeMilisec.hourFee
185
+ },
186
+ average: {
187
+ feeRate: 1.5,
188
+ time: convertTimeMilisec.halfHourFee
189
+ },
190
+ fast: {
191
+ feeRate: 1.5,
192
+ time: convertTimeMilisec.fastestFee
193
+ },
194
+ default: 'slow'
195
+ }
196
+ };
197
+ try {
198
+ const response = await getRequest(this.getUrl('v1/fees/recommended'), undefined, this.headers);
199
+ if (!response.ok) {
200
+ console.warn(`Failed to fetch fee estimates: ${response.statusText}`);
201
+ return defaultFeeInfo;
202
+ }
203
+ const estimates = await response.json();
204
+ const convertFee = fee => {
205
+ const adjustedFee = parseInt(new BigN(fee).toFixed(), 10);
206
+ return Math.max(adjustedFee, 1.5);
207
+ };
208
+ return {
209
+ type: 'bitcoin',
210
+ busyNetwork: false,
211
+ options: {
212
+ slow: {
213
+ feeRate: convertFee(estimates.hourFee || 1),
214
+ time: convertTimeMilisec.hourFee
215
+ },
216
+ average: {
217
+ feeRate: convertFee(estimates.halfHourFee || 1),
218
+ time: convertTimeMilisec.halfHourFee
219
+ },
220
+ fast: {
221
+ feeRate: convertFee(estimates.fastestFee || 1),
222
+ time: convertTimeMilisec.fastestFee
223
+ },
224
+ default: 'slow'
225
+ }
226
+ };
227
+ } catch {
228
+ return defaultFeeInfo;
229
+ }
230
+ }, 0);
231
+ }
232
+ getUtxos(address) {
233
+ return this.addRequest(async () => {
234
+ const response = await getRequest(this.getUrl(`address/${address}/utxo`), undefined, {});
235
+ const rs = await response.json();
236
+ if (!response.ok) {
237
+ const errorText = await response.text();
238
+ throw new SWError('BlockStreamTestnetRequestStrategy.getUtxos', `Failed to fetch UTXOs: ${errorText}`);
239
+ }
240
+ return rs.map(item => ({
241
+ txid: item.txid,
242
+ vout: item.vout,
243
+ value: item.value,
244
+ status: item.status
245
+ }));
246
+ }, 0);
247
+ }
248
+ sendRawTransaction(rawTransaction) {
249
+ const eventEmitter = new EventEmitter();
250
+ this.addRequest(async () => {
251
+ const response = await postRequest(this.getUrl('tx'), rawTransaction, {
252
+ 'Content-Type': 'text/plain'
253
+ }, false);
254
+ if (!response.ok) {
255
+ const errorText = await response.text();
256
+ throw new SWError('BlockStreamTestnetRequestStrategy.sendRawTransaction', `Failed to broadcast transaction: ${errorText}`);
257
+ }
258
+ return await response.text();
259
+ }, 0).then(extrinsicHash => {
260
+ eventEmitter.emit('extrinsicHash', extrinsicHash);
261
+
262
+ // Check transaction status
263
+ const interval = setInterval(() => {
264
+ this.getTransactionStatus(extrinsicHash).then(transactionStatus => {
265
+ if (transactionStatus.confirmed && transactionStatus.block_time > 0) {
266
+ clearInterval(interval);
267
+ eventEmitter.emit('success', transactionStatus);
268
+ }
269
+ }).catch(console.error);
270
+ }, 30000);
271
+ }).catch(error => {
272
+ eventEmitter.emit('error', error.message);
273
+ });
274
+ return eventEmitter;
275
+ }
276
+ simpleSendRawTransaction(rawTransaction) {
277
+ return this.addRequest(async () => {
278
+ const response = await postRequest(this.getUrl('tx'), rawTransaction, {
279
+ 'Content-Type': 'text/plain'
280
+ }, false);
281
+ if (!response.ok) {
282
+ const errorText = await response.text();
283
+ throw new SWError('BlockStreamTestnetRequestStrategy.simpleSendRawTransaction', `Failed to broadcast transaction: ${errorText}`);
284
+ }
285
+ return await response.text();
286
+ }, 0);
287
+ }
288
+ async getRunes(address) {
289
+ const runesFullList = [];
290
+ const pageSize = 60;
291
+ let offset = 0;
292
+ const runeService = RunesService.getInstance(this.isTestnet);
293
+ try {
294
+ while (true) {
295
+ const response = await runeService.getAddressRunesInfo(address, {
296
+ limit: String(pageSize),
297
+ offset: String(offset)
298
+ });
299
+ const runes = response.runes;
300
+ if (runes.length !== 0) {
301
+ runesFullList.push(...runes);
302
+ offset += pageSize;
303
+ } else {
304
+ break;
305
+ }
306
+ }
307
+ return runesFullList;
308
+ } catch (error) {
309
+ console.error(`Failed to get ${address} balances`, error);
310
+ throw error;
311
+ }
312
+ }
313
+ async getRuneUtxos(address) {
314
+ const runeService = RunesService.getInstance(this.isTestnet);
315
+ try {
316
+ const responseRuneUtxos = await runeService.getAddressRuneUtxos(address);
317
+ return responseRuneUtxos.results;
318
+ } catch (error) {
319
+ const errorMessage = error instanceof Error ? error.message : String(error);
320
+ throw new SWError('BlockStreamTestnetRequestStrategy.getRuneUtxos', `Failed to get ${address} rune utxos: ${errorMessage}`);
321
+ }
322
+ }
323
+ async getAddressInscriptions(address) {
324
+ const inscriptionsFullList = [];
325
+ const pageSize = 60;
326
+ let offset = 0;
327
+ const hiroService = HiroService.getInstance(this.isTestnet);
328
+ try {
329
+ while (true) {
330
+ const response = await hiroService.getAddressInscriptionsInfo({
331
+ limit: String(pageSize),
332
+ offset: String(offset),
333
+ address: String(address)
334
+ });
335
+ const inscriptions = response.results;
336
+ if (inscriptions.length !== 0) {
337
+ inscriptionsFullList.push(...inscriptions);
338
+ offset += pageSize;
339
+ } else {
340
+ break;
341
+ }
342
+ }
343
+ return inscriptionsFullList;
344
+ } catch (error) {
345
+ const errorMessage = error instanceof Error ? error.message : String(error);
346
+ throw new SWError('BlockStreamTestnetRequestStrategy.getAddressInscriptions', `Failed to get ${address} inscriptions: ${errorMessage}`);
347
+ }
348
+ }
349
+ getTxHex(txHash) {
350
+ return this.addRequest(async () => {
351
+ const response = await getRequest(this.getUrl(`tx/${txHash}/hex`), undefined, this.headers);
352
+ if (!response.ok) {
353
+ const errorText = await response.text();
354
+ throw new SWError('BlockStreamTestnetRequestStrategy.getTxHex', `Failed to fetch transaction hex: ${errorText}`);
355
+ }
356
+ return await response.text();
357
+ }, 0);
358
+ }
359
+ }
@@ -0,0 +1,28 @@
1
+ import { BitcoinAddressSummaryInfo, BitcoinApiStrategy, BitcoinTransactionEventMap, BlockStreamTransactionDetail, BlockStreamTransactionStatus, Inscription, RunesInfoByAddress } from '@subwallet/extension-base/services/chain-service/handler/bitcoin/strategy/types';
2
+ import { BaseApiRequestStrategy } from '@subwallet/extension-base/strategy/api-request-strategy';
3
+ import { BitcoinFeeInfo, BitcoinTx, UtxoResponseItem } from '@subwallet/extension-base/types';
4
+ import EventEmitter from 'eventemitter3';
5
+ export declare class SubWalletMainnetRequestStrategy extends BaseApiRequestStrategy implements BitcoinApiStrategy {
6
+ private readonly baseUrl;
7
+ private readonly isTestnet;
8
+ private timePerBlock;
9
+ constructor(url: string);
10
+ private headers;
11
+ isRateLimited(): boolean;
12
+ getUrl(path: string): string;
13
+ getBlockTime(): Promise<number>;
14
+ computeBlockTime(): Promise<number>;
15
+ getAddressSummaryInfo(address: string): Promise<BitcoinAddressSummaryInfo>;
16
+ getAddressTransaction(address: string, limit?: number): Promise<BitcoinTx[]>;
17
+ getTransactionStatus(txHash: string): Promise<BlockStreamTransactionStatus>;
18
+ getTransactionDetail(txHash: string): Promise<BlockStreamTransactionDetail>;
19
+ getFeeRate(): Promise<BitcoinFeeInfo>;
20
+ getRecommendedFeeRate(): Promise<BitcoinFeeInfo>;
21
+ getUtxos(address: string): Promise<UtxoResponseItem[]>;
22
+ sendRawTransaction(rawTransaction: string): EventEmitter<BitcoinTransactionEventMap, any>;
23
+ simpleSendRawTransaction(rawTransaction: string): Promise<string>;
24
+ getRunes(address: string): Promise<RunesInfoByAddress[]>;
25
+ getRuneUtxos(address: string): Promise<import("@subwallet/extension-base/services/chain-service/handler/bitcoin/strategy/types").RuneUtxo[]>;
26
+ getAddressInscriptions(address: string): Promise<Inscription[]>;
27
+ getTxHex(txHash: string): Promise<string>;
28
+ }
@@ -0,0 +1,293 @@
1
+ // Copyright 2019-2022 @subwallet/extension-base
2
+ // SPDX-License-Identifier: Apache-2.0
3
+
4
+ import { SWError } from '@subwallet/extension-base/background/errors/SWError';
5
+ import { _BTC_SERVICE_TOKEN } from '@subwallet/extension-base/services/chain-service/constants';
6
+ import { HiroService } from '@subwallet/extension-base/services/hiro-service';
7
+ import { RunesService } from '@subwallet/extension-base/services/rune-service';
8
+ import { BaseApiRequestStrategy } from '@subwallet/extension-base/strategy/api-request-strategy';
9
+ import { BaseApiRequestContext } from '@subwallet/extension-base/strategy/api-request-strategy/context/base';
10
+ import { getRequest, postRequest } from '@subwallet/extension-base/strategy/api-request-strategy/utils';
11
+ import BigN from 'bignumber.js';
12
+ import EventEmitter from 'eventemitter3';
13
+ export class SubWalletMainnetRequestStrategy extends BaseApiRequestStrategy {
14
+ timePerBlock = 0; // in milliseconds
15
+
16
+ constructor(url) {
17
+ const context = new BaseApiRequestContext();
18
+ super(context);
19
+ this.baseUrl = url;
20
+ this.isTestnet = url.includes('testnet');
21
+ }
22
+ headers = {
23
+ 'Content-Type': 'application/json',
24
+ Authorization: `Bearer ${_BTC_SERVICE_TOKEN}`
25
+ };
26
+ isRateLimited() {
27
+ return false;
28
+ }
29
+ getUrl(path) {
30
+ return `${this.baseUrl}/${path}`;
31
+ }
32
+ getBlockTime() {
33
+ return this.addRequest(async () => {
34
+ const _rs = await getRequest(this.getUrl('blocks'), undefined, this.headers);
35
+ const rs = await _rs.json();
36
+ if (rs.status_code !== 200) {
37
+ throw new SWError('BlockStreamRequestStrategy.getBlockTime', rs.message);
38
+ }
39
+ const blocks = rs.result;
40
+ const length = blocks.length;
41
+ const sortedBlocks = blocks.sort((a, b) => b.timestamp - a.timestamp);
42
+ const time = (sortedBlocks[0].timestamp - sortedBlocks[length - 1].timestamp) * 1000;
43
+ return time / length;
44
+ }, 0);
45
+ }
46
+ async computeBlockTime() {
47
+ let blockTime = this.timePerBlock;
48
+ if (blockTime > 0) {
49
+ return blockTime;
50
+ }
51
+ try {
52
+ blockTime = await this.getBlockTime();
53
+ this.timePerBlock = blockTime;
54
+ } catch (e) {
55
+ console.error('Failed to compute block time', e);
56
+ blockTime = (this.isTestnet ? 5 * 60 : 10 * 60) * 1000; // Default to 10 minutes if failed
57
+ }
58
+
59
+ // Cache block time in 60 seconds
60
+ setTimeout(() => {
61
+ this.timePerBlock = 0;
62
+ }, 60000);
63
+ return blockTime;
64
+ }
65
+ getAddressSummaryInfo(address) {
66
+ return this.addRequest(async () => {
67
+ const _rs = await getRequest(this.getUrl(`address/${address}`), undefined, this.headers);
68
+ const rs = await _rs.json();
69
+ if (rs.status_code !== 200) {
70
+ throw new SWError('BlockStreamRequestStrategy.getAddressSummaryInfo', rs.message);
71
+ }
72
+ return rs.result;
73
+ }, 0);
74
+ }
75
+ getAddressTransaction(address, limit = 100) {
76
+ return this.addRequest(async () => {
77
+ const _rs = await getRequest(this.getUrl(`address/${address}/txs`), {
78
+ limit: `${limit}`
79
+ }, this.headers);
80
+ const rs = await _rs.json();
81
+ if (rs.status_code !== 200) {
82
+ throw new SWError('BlockStreamRequestStrategy.getAddressTransaction', rs.message);
83
+ }
84
+ return rs.result;
85
+ }, 1);
86
+ }
87
+ getTransactionStatus(txHash) {
88
+ return this.addRequest(async () => {
89
+ const _rs = await getRequest(this.getUrl(`tx/${txHash}/status`), undefined, this.headers);
90
+ const rs = await _rs.json();
91
+ if (rs.status_code !== 200) {
92
+ throw new SWError('BlockStreamRequestStrategy.getTransactionStatus', rs.message);
93
+ }
94
+ return rs.result;
95
+ }, 1);
96
+ }
97
+ getTransactionDetail(txHash) {
98
+ return this.addRequest(async () => {
99
+ const _rs = await getRequest(this.getUrl(`tx/${txHash}`), undefined, this.headers);
100
+ const rs = await _rs.json();
101
+ if (rs.status_code !== 200) {
102
+ throw new SWError('BlockStreamRequestStrategy.getTransactionDetail', rs.message);
103
+ }
104
+ return rs.result;
105
+ }, 1);
106
+ }
107
+ async getFeeRate() {
108
+ const timePerBlock = await this.computeBlockTime();
109
+ return await this.addRequest(async () => {
110
+ const _rs = await getRequest(this.getUrl('fee-estimates'), undefined, this.headers);
111
+ const rs = await _rs.json();
112
+ if (rs.status_code !== 200) {
113
+ throw new SWError('BlockStreamRequestStrategy.getFeeRate', rs.message);
114
+ }
115
+ const result = rs.result;
116
+ const low = 6;
117
+ const average = 3;
118
+ const fast = 1;
119
+ const convertFee = fee => parseFloat(new BigN(fee).toFixed(2));
120
+ return {
121
+ type: 'bitcoin',
122
+ busyNetwork: false,
123
+ options: {
124
+ slow: {
125
+ feeRate: convertFee(result[low]),
126
+ time: timePerBlock * low
127
+ },
128
+ average: {
129
+ feeRate: convertFee(result[average]),
130
+ time: timePerBlock * average
131
+ },
132
+ fast: {
133
+ feeRate: convertFee(result[fast]),
134
+ time: timePerBlock * fast
135
+ },
136
+ default: 'slow'
137
+ }
138
+ };
139
+ }, 0);
140
+ }
141
+ getRecommendedFeeRate() {
142
+ return this.addRequest(async () => {
143
+ const _rs = await getRequest(this.getUrl('fee-estimates/recommended'), undefined, this.headers);
144
+ const rs = await _rs.json();
145
+ if (rs.status_code !== 200) {
146
+ throw new SWError('BlockStreamRequestStrategy.getRecommendedFeeRate', rs.message);
147
+ }
148
+ const result = rs.result;
149
+ const convertTimeMilisec = {
150
+ fastestFee: 10 * 60000,
151
+ halfHourFee: 30 * 60000,
152
+ hourFee: 60 * 60000
153
+ };
154
+ const convertFee = fee => parseInt(new BigN(fee).toFixed());
155
+ return {
156
+ type: 'bitcoin',
157
+ busyNetwork: false,
158
+ options: {
159
+ slow: {
160
+ feeRate: convertFee(result.hourFee),
161
+ time: convertTimeMilisec.hourFee
162
+ },
163
+ average: {
164
+ feeRate: convertFee(result.halfHourFee),
165
+ time: convertTimeMilisec.halfHourFee
166
+ },
167
+ fast: {
168
+ feeRate: convertFee(result.fastestFee),
169
+ time: convertTimeMilisec.fastestFee
170
+ },
171
+ default: 'slow'
172
+ }
173
+ };
174
+ }, 0);
175
+ }
176
+ getUtxos(address) {
177
+ return this.addRequest(async () => {
178
+ const _rs = await getRequest(this.getUrl(`address/${address}/utxo`), undefined, this.headers);
179
+ const rs = await _rs.json();
180
+ if (rs.status_code !== 200) {
181
+ throw new SWError('BlockStreamRequestStrategy.getUtxos', rs.message);
182
+ }
183
+ return rs.result.utxoItems;
184
+ }, 0);
185
+ }
186
+ sendRawTransaction(rawTransaction) {
187
+ const eventEmitter = new EventEmitter();
188
+ this.addRequest(async () => {
189
+ const _rs = await postRequest(this.getUrl('tx'), rawTransaction, this.headers, false);
190
+ const rs = await _rs.json();
191
+ if (rs.status_code !== 200) {
192
+ throw new SWError('BlockStreamRequestStrategy.sendRawTransaction', rs.message);
193
+ }
194
+ return rs.result;
195
+ }, 0).then(extrinsicHash => {
196
+ eventEmitter.emit('extrinsicHash', extrinsicHash);
197
+
198
+ // Check transaction status
199
+ const interval = setInterval(() => {
200
+ this.getTransactionStatus(extrinsicHash).then(transactionStatus => {
201
+ if (transactionStatus.confirmed && transactionStatus.block_time > 0) {
202
+ clearInterval(interval);
203
+ eventEmitter.emit('success', transactionStatus);
204
+ }
205
+ }).catch(console.error);
206
+ }, 30000);
207
+ }).catch(error => {
208
+ eventEmitter.emit('error', error.message);
209
+ });
210
+ return eventEmitter;
211
+ }
212
+ simpleSendRawTransaction(rawTransaction) {
213
+ return this.addRequest(async () => {
214
+ const _rs = await postRequest(this.getUrl('tx'), rawTransaction, this.headers, false);
215
+ const rs = await _rs.json();
216
+ if (rs.status_code !== 200) {
217
+ throw new SWError('BlockStreamRequestStrategy.simpleSendRawTransaction', rs.message);
218
+ }
219
+ return rs.result;
220
+ }, 0);
221
+ }
222
+ async getRunes(address) {
223
+ const runesFullList = [];
224
+ const pageSize = 60;
225
+ let offset = 0;
226
+ const runeService = RunesService.getInstance(this.isTestnet);
227
+ try {
228
+ while (true) {
229
+ const response = await runeService.getAddressRunesInfo(address, {
230
+ limit: String(pageSize),
231
+ offset: String(offset)
232
+ });
233
+ const runes = response.runes;
234
+ if (runes.length !== 0) {
235
+ runesFullList.push(...runes);
236
+ offset += pageSize;
237
+ } else {
238
+ break;
239
+ }
240
+ }
241
+ return runesFullList;
242
+ } catch (error) {
243
+ console.error(`Failed to get ${address} balances`, error);
244
+ throw error;
245
+ }
246
+ }
247
+ async getRuneUtxos(address) {
248
+ const runeService = RunesService.getInstance(this.isTestnet);
249
+ try {
250
+ const responseRuneUtxos = await runeService.getAddressRuneUtxos(address);
251
+ return responseRuneUtxos.results;
252
+ } catch (error) {
253
+ console.error(`Failed to get ${address} rune utxos`, error);
254
+ throw error;
255
+ }
256
+ }
257
+ async getAddressInscriptions(address) {
258
+ const inscriptionsFullList = [];
259
+ const pageSize = 60;
260
+ let offset = 0;
261
+ const hiroService = HiroService.getInstance(this.isTestnet);
262
+ try {
263
+ while (true) {
264
+ const response = await hiroService.getAddressInscriptionsInfo({
265
+ limit: String(pageSize),
266
+ offset: String(offset),
267
+ address: String(address)
268
+ });
269
+ const inscriptions = response.results;
270
+ if (inscriptions.length !== 0) {
271
+ inscriptionsFullList.push(...inscriptions);
272
+ offset += pageSize;
273
+ } else {
274
+ break;
275
+ }
276
+ }
277
+ return inscriptionsFullList;
278
+ } catch (error) {
279
+ console.error(`Failed to get ${address} inscriptions`, error);
280
+ throw error;
281
+ }
282
+ }
283
+ getTxHex(txHash) {
284
+ return this.addRequest(async () => {
285
+ const _rs = await getRequest(this.getUrl(`tx/${txHash}/hex`), undefined, this.headers);
286
+ const rs = await _rs.json();
287
+ if (rs.status_code !== 200) {
288
+ throw new SWError('BlockStreamRequestStrategy.getTxHex', rs.message);
289
+ }
290
+ return rs.result;
291
+ }, 0);
292
+ }
293
+ }