mainnet-js 3.1.7 → 4.0.0-next.10

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 (267) hide show
  1. package/dist/module/cache/IndexedDbCache.d.ts +1 -1
  2. package/dist/module/cache/IndexedDbCache.d.ts.map +1 -1
  3. package/dist/module/cache/MemoryCache.d.ts +1 -1
  4. package/dist/module/cache/MemoryCache.d.ts.map +1 -1
  5. package/dist/module/cache/WebStorageCache.d.ts +1 -1
  6. package/dist/module/cache/WebStorageCache.d.ts.map +1 -1
  7. package/dist/module/cache/walletCache.d.ts +16 -6
  8. package/dist/module/cache/walletCache.d.ts.map +1 -1
  9. package/dist/module/cache/walletCache.js +92 -34
  10. package/dist/module/cache/walletCache.js.map +1 -1
  11. package/dist/module/cli.js +1 -2
  12. package/dist/module/cli.js.map +1 -1
  13. package/dist/module/db/index.d.ts +1 -1
  14. package/dist/module/db/index.d.ts.map +1 -1
  15. package/dist/module/db/index.js +1 -1
  16. package/dist/module/db/index.js.map +1 -1
  17. package/dist/module/enum.d.ts +1 -1
  18. package/dist/module/enum.d.ts.map +1 -1
  19. package/dist/module/history/getHistory.d.ts +1 -1
  20. package/dist/module/history/getHistory.d.ts.map +1 -1
  21. package/dist/module/history/getHistory.js +3 -3
  22. package/dist/module/history/getHistory.js.map +1 -1
  23. package/dist/module/index.d.ts +25 -22
  24. package/dist/module/index.d.ts.map +1 -1
  25. package/dist/module/index.js +33 -26
  26. package/dist/module/index.js.map +1 -1
  27. package/dist/module/interface.d.ts +12 -2
  28. package/dist/module/interface.d.ts.map +1 -1
  29. package/dist/module/interface.js.map +1 -1
  30. package/dist/module/libauth.d.ts +1 -1
  31. package/dist/module/libauth.d.ts.map +1 -1
  32. package/dist/module/libauth.js +1 -1
  33. package/dist/module/libauth.js.map +1 -1
  34. package/dist/module/mine/mine.d.ts +2 -7
  35. package/dist/module/mine/mine.d.ts.map +1 -1
  36. package/dist/module/mine/mine.js +6 -27
  37. package/dist/module/mine/mine.js.map +1 -1
  38. package/dist/module/network/Connection.d.ts +1 -12
  39. package/dist/module/network/Connection.d.ts.map +1 -1
  40. package/dist/module/network/Connection.js +24 -33
  41. package/dist/module/network/Connection.js.map +1 -1
  42. package/dist/module/network/ElectrumNetworkProvider.d.ts +16 -17
  43. package/dist/module/network/ElectrumNetworkProvider.d.ts.map +1 -1
  44. package/dist/module/network/ElectrumNetworkProvider.js +91 -93
  45. package/dist/module/network/ElectrumNetworkProvider.js.map +1 -1
  46. package/dist/module/network/MockNetworkProvider.d.ts +28 -0
  47. package/dist/module/network/MockNetworkProvider.d.ts.map +1 -0
  48. package/dist/module/network/MockNetworkProvider.js +74 -0
  49. package/dist/module/network/MockNetworkProvider.js.map +1 -0
  50. package/dist/module/network/NetworkProvider.d.ts +9 -2
  51. package/dist/module/network/NetworkProvider.d.ts.map +1 -1
  52. package/dist/module/network/configuration.d.ts +2 -4
  53. package/dist/module/network/configuration.d.ts.map +1 -1
  54. package/dist/module/network/configuration.js +25 -50
  55. package/dist/module/network/configuration.js.map +1 -1
  56. package/dist/module/network/constant.d.ts +7 -7
  57. package/dist/module/network/constant.d.ts.map +1 -1
  58. package/dist/module/network/constant.js +21 -24
  59. package/dist/module/network/constant.js.map +1 -1
  60. package/dist/module/network/default.d.ts +5 -3
  61. package/dist/module/network/default.d.ts.map +1 -1
  62. package/dist/module/network/default.js +29 -51
  63. package/dist/module/network/default.js.map +1 -1
  64. package/dist/module/network/index.d.ts +4 -4
  65. package/dist/module/network/index.d.ts.map +1 -1
  66. package/dist/module/network/index.js +2 -2
  67. package/dist/module/network/index.js.map +1 -1
  68. package/dist/module/network/interface.d.ts +0 -6
  69. package/dist/module/network/interface.d.ts.map +1 -1
  70. package/dist/module/rate/ExchangeRate.d.ts +1 -1
  71. package/dist/module/rate/ExchangeRate.d.ts.map +1 -1
  72. package/dist/module/rate/ExchangeRate.js +13 -7
  73. package/dist/module/rate/ExchangeRate.js.map +1 -1
  74. package/dist/module/transaction/Wif.d.ts +3 -3
  75. package/dist/module/transaction/Wif.d.ts.map +1 -1
  76. package/dist/module/transaction/Wif.js +4 -4
  77. package/dist/module/transaction/Wif.js.map +1 -1
  78. package/dist/module/transaction/allocateFee.d.ts +1 -1
  79. package/dist/module/transaction/allocateFee.d.ts.map +1 -1
  80. package/dist/module/transaction/allocateFee.js +2 -2
  81. package/dist/module/transaction/allocateFee.js.map +1 -1
  82. package/dist/module/util/deriveCashaddr.js.map +1 -1
  83. package/dist/module/util/deriveNetwork.js +1 -1
  84. package/dist/module/util/deriveNetwork.js.map +1 -1
  85. package/dist/module/util/getAddrsByXpubKey.js +1 -1
  86. package/dist/module/util/getAddrsByXpubKey.js.map +1 -1
  87. package/dist/module/util/getXPubKey.js +1 -1
  88. package/dist/module/util/getXPubKey.js.map +1 -1
  89. package/dist/module/util/hd.d.ts +2 -0
  90. package/dist/module/util/hd.d.ts.map +1 -1
  91. package/dist/module/util/hd.js +41 -0
  92. package/dist/module/util/hd.js.map +1 -1
  93. package/dist/module/util/index.d.ts +7 -9
  94. package/dist/module/util/index.d.ts.map +1 -1
  95. package/dist/module/util/index.js +7 -9
  96. package/dist/module/util/index.js.map +1 -1
  97. package/dist/module/util/transaction.js +1 -1
  98. package/dist/module/util/transaction.js.map +1 -1
  99. package/dist/module/wallet/Base.d.ts +13 -9
  100. package/dist/module/wallet/Base.d.ts.map +1 -1
  101. package/dist/module/wallet/Base.js +54 -58
  102. package/dist/module/wallet/Base.js.map +1 -1
  103. package/dist/module/wallet/HDWallet.d.ts.map +1 -1
  104. package/dist/module/wallet/HDWallet.js +19 -38
  105. package/dist/module/wallet/HDWallet.js.map +1 -1
  106. package/dist/module/wallet/Util.d.ts +4 -22
  107. package/dist/module/wallet/Util.d.ts.map +1 -1
  108. package/dist/module/wallet/Util.js +75 -102
  109. package/dist/module/wallet/Util.js.map +1 -1
  110. package/dist/module/wallet/Watch.d.ts +24 -5
  111. package/dist/module/wallet/Watch.d.ts.map +1 -1
  112. package/dist/module/wallet/Watch.js +141 -17
  113. package/dist/module/wallet/Watch.js.map +1 -1
  114. package/dist/module/wallet/Wif.d.ts +2 -6
  115. package/dist/module/wallet/Wif.d.ts.map +1 -1
  116. package/dist/module/wallet/Wif.js +3 -69
  117. package/dist/module/wallet/Wif.js.map +1 -1
  118. package/dist/module/wallet/createWallet.d.ts +1 -1
  119. package/dist/module/wallet/createWallet.d.ts.map +1 -1
  120. package/dist/module/wallet/createWallet.js +2 -1
  121. package/dist/module/wallet/createWallet.js.map +1 -1
  122. package/dist/module/wallet/interface.d.ts +3 -2
  123. package/dist/module/wallet/interface.d.ts.map +1 -1
  124. package/dist/module/wallet/model.d.ts +2 -1
  125. package/dist/module/wallet/model.d.ts.map +1 -1
  126. package/dist/module/wallet/model.js +1 -1
  127. package/dist/module/wallet/model.js.map +1 -1
  128. package/package.json +17 -29
  129. package/dist/index.html +0 -9
  130. package/dist/mainnet-3.1.7.js +0 -2066
  131. package/dist/module/network/util.d.ts +0 -3
  132. package/dist/module/network/util.d.ts.map +0 -1
  133. package/dist/module/network/util.js +0 -27
  134. package/dist/module/network/util.js.map +0 -1
  135. package/dist/module/test/expect.d.ts +0 -12
  136. package/dist/module/test/expect.d.ts.map +0 -1
  137. package/dist/module/test/expect.js +0 -47
  138. package/dist/module/test/expect.js.map +0 -1
  139. package/dist/module/test/fetch.d.ts +0 -3
  140. package/dist/module/test/fetch.d.ts.map +0 -1
  141. package/dist/module/test/fetch.js +0 -32
  142. package/dist/module/test/fetch.js.map +0 -1
  143. package/dist/module/util/randomBytes.d.ts +0 -2
  144. package/dist/module/util/randomBytes.d.ts.map +0 -1
  145. package/dist/module/util/randomBytes.js +0 -13
  146. package/dist/module/util/randomBytes.js.map +0 -1
  147. package/dist/tsconfig.tsbuildinfo +0 -1
  148. package/src/cache/IndexedDbCache.test.ts +0 -15
  149. package/src/cache/IndexedDbCache.ts +0 -172
  150. package/src/cache/MemoryCache.test.ts +0 -15
  151. package/src/cache/MemoryCache.ts +0 -32
  152. package/src/cache/WebStorageCache.test.ts +0 -15
  153. package/src/cache/WebStorageCache.ts +0 -38
  154. package/src/cache/index.ts +0 -2
  155. package/src/cache/interface.ts +0 -9
  156. package/src/cache/walletCache.ts +0 -254
  157. package/src/chain.ts +0 -3
  158. package/src/cli.ts +0 -32
  159. package/src/config.ts +0 -23
  160. package/src/constant.ts +0 -27
  161. package/src/db/ExchangeRateProvider.ts +0 -28
  162. package/src/db/StorageProvider.ts +0 -64
  163. package/src/db/index.ts +0 -2
  164. package/src/db/interface.ts +0 -11
  165. package/src/enum.ts +0 -34
  166. package/src/history/getHistory.test.ts +0 -290
  167. package/src/history/getHistory.ts +0 -411
  168. package/src/history/interface.ts +0 -24
  169. package/src/index.ts +0 -48
  170. package/src/interface.ts +0 -72
  171. package/src/libauth.ts +0 -11
  172. package/src/message/index.ts +0 -2
  173. package/src/message/interface.ts +0 -40
  174. package/src/message/signed.test.ts +0 -309
  175. package/src/message/signed.ts +0 -201
  176. package/src/mine/index.ts +0 -1
  177. package/src/mine/mine.test.ts +0 -10
  178. package/src/mine/mine.ts +0 -42
  179. package/src/network/Connection.test.ts +0 -51
  180. package/src/network/Connection.ts +0 -73
  181. package/src/network/ElectrumNetworkProvider.ts +0 -657
  182. package/src/network/NetworkProvider.ts +0 -180
  183. package/src/network/Rpc.test.ts +0 -130
  184. package/src/network/configuration.test.ts +0 -59
  185. package/src/network/configuration.ts +0 -72
  186. package/src/network/constant.ts +0 -43
  187. package/src/network/default.ts +0 -120
  188. package/src/network/electrum.test.ts +0 -28
  189. package/src/network/getRelayFeeCache.test.ts +0 -15
  190. package/src/network/getRelayFeeCache.ts +0 -23
  191. package/src/network/index.ts +0 -14
  192. package/src/network/interface.ts +0 -80
  193. package/src/network/util.test.ts +0 -24
  194. package/src/network/util.ts +0 -30
  195. package/src/rate/ExchangeRate.test.headless.js +0 -35
  196. package/src/rate/ExchangeRate.test.ts +0 -51
  197. package/src/rate/ExchangeRate.ts +0 -142
  198. package/src/test/expect.ts +0 -59
  199. package/src/test/fetch.ts +0 -39
  200. package/src/test/json.test.ts +0 -13
  201. package/src/transaction/Wif.ts +0 -680
  202. package/src/transaction/allocateFee.test.ts +0 -298
  203. package/src/transaction/allocateFee.ts +0 -149
  204. package/src/util/amountInSatoshi.test.ts +0 -27
  205. package/src/util/amountInSatoshi.ts +0 -33
  206. package/src/util/asSendRequestObject.ts +0 -81
  207. package/src/util/base64.test.ts +0 -39
  208. package/src/util/base64.ts +0 -12
  209. package/src/util/browserNotSupported.ts +0 -7
  210. package/src/util/checkForEmptySeed.ts +0 -9
  211. package/src/util/checkUtxos.ts +0 -29
  212. package/src/util/checkWifNetwork.ts +0 -24
  213. package/src/util/convert.test.ts +0 -46
  214. package/src/util/convert.ts +0 -50
  215. package/src/util/delay.ts +0 -3
  216. package/src/util/deriveCashaddr.test.ts +0 -164
  217. package/src/util/deriveCashaddr.ts +0 -143
  218. package/src/util/deriveLockscript.ts +0 -16
  219. package/src/util/deriveNetwork.ts +0 -19
  220. package/src/util/derivePublicKeyHash.test.ts +0 -55
  221. package/src/util/derivePublicKeyHash.ts +0 -64
  222. package/src/util/floor.test.ts +0 -21
  223. package/src/util/floor.ts +0 -4
  224. package/src/util/getAddrsByXpubKey.test.ts +0 -115
  225. package/src/util/getAddrsByXpubKey.ts +0 -86
  226. package/src/util/getRuntimePlatform.test.headless.js +0 -40
  227. package/src/util/getRuntimePlatform.test.ts +0 -5
  228. package/src/util/getRuntimePlatform.ts +0 -31
  229. package/src/util/getUsdRate.ts +0 -5
  230. package/src/util/getXPubKey.ts +0 -39
  231. package/src/util/hash160.test.ts +0 -18
  232. package/src/util/hash160.ts +0 -12
  233. package/src/util/hd.ts +0 -16
  234. package/src/util/header.test.ts +0 -34
  235. package/src/util/header.ts +0 -26
  236. package/src/util/index.ts +0 -33
  237. package/src/util/randomBytes.ts +0 -13
  238. package/src/util/randomInt.test.ts +0 -15
  239. package/src/util/randomInt.ts +0 -4
  240. package/src/util/sanitizeAddress.ts +0 -10
  241. package/src/util/sanitizeUnit.ts +0 -11
  242. package/src/util/satoshiToAmount.test.ts +0 -6
  243. package/src/util/satoshiToAmount.ts +0 -33
  244. package/src/util/sumSendRequestAmounts.ts +0 -34
  245. package/src/util/sumUtxoValue.ts +0 -27
  246. package/src/util/transaction.ts +0 -10
  247. package/src/wallet/Base.ts +0 -1563
  248. package/src/wallet/Cashtokens.test.headless.js +0 -730
  249. package/src/wallet/Cashtokens.test.ts +0 -1411
  250. package/src/wallet/HDWallet.test.ts +0 -1086
  251. package/src/wallet/HDWallet.ts +0 -992
  252. package/src/wallet/Util.test.ts +0 -134
  253. package/src/wallet/Util.ts +0 -191
  254. package/src/wallet/WalletCache.test.ts +0 -45
  255. package/src/wallet/Watch.ts +0 -441
  256. package/src/wallet/Wif.bip39.test.ts +0 -48
  257. package/src/wallet/Wif.test.ts +0 -1189
  258. package/src/wallet/Wif.ts +0 -687
  259. package/src/wallet/Wif.watchOnly.test.ts +0 -58
  260. package/src/wallet/createWallet.ts +0 -238
  261. package/src/wallet/enum.ts +0 -18
  262. package/src/wallet/interface.ts +0 -102
  263. package/src/wallet/model.test.ts +0 -24
  264. package/src/wallet/model.ts +0 -352
  265. package/tsconfig.browser.json +0 -11
  266. package/tsconfig.json +0 -33
  267. package/webpack.config.cjs +0 -132
@@ -1,1563 +0,0 @@
1
- import { binToHex, CashAddressNetworkPrefix } from "@bitauth/libauth";
2
- import { WalletCache } from "../cache/walletCache.js";
3
- import StorageProvider from "../db/StorageProvider.js";
4
- import { NetworkType, prefixFromNetworkMap } from "../enum.js";
5
- import { HexHeaderI, NFTCapability, TxI, Utxo, UtxoId } from "../interface.js";
6
- import {
7
- SignedMessageResponseI,
8
- VerifyMessageResponseI,
9
- } from "../message/interface.js";
10
- import { getNetworkProvider } from "../network/default.js";
11
- import ElectrumNetworkProvider from "../network/ElectrumNetworkProvider.js";
12
- import { getRelayFeeCache } from "../network/getRelayFeeCache.js";
13
- import { ElectrumRawTransaction } from "../network/interface.js";
14
- import {
15
- buildEncodedTransaction,
16
- getFeeAmount,
17
- getFeeAmountSimple,
18
- getSuitableUtxos,
19
- placeholderPrivateKeyBin,
20
- } from "../transaction/Wif.js";
21
- import { checkUtxos } from "../util/checkUtxos.js";
22
- import {
23
- asSendRequestObject,
24
- getRuntimePlatform,
25
- sumTokenAmounts,
26
- sumUtxoValue,
27
- toTokenaddr,
28
- } from "../util/index.js";
29
- import { sumSendRequestAmounts } from "../util/sumSendRequestAmounts.js";
30
- import { FeePaidByEnum, WalletTypeEnum } from "./enum.js";
31
- import {
32
- CancelFn,
33
- SendRequestOptionsI,
34
- WaitForTransactionOptions,
35
- WaitForTransactionResponse,
36
- WalletI,
37
- WalletInfoI,
38
- } from "./interface.js";
39
- import {
40
- fromUtxoId,
41
- OpReturnData,
42
- SendRequest,
43
- SendRequestArray,
44
- SendRequestType,
45
- SendResponse,
46
- TokenBurnRequest,
47
- TokenGenesisRequest,
48
- TokenMintRequest,
49
- TokenSendRequest,
50
- } from "./model.js";
51
- import { Util } from "./Util.js";
52
- import { SignedMessage } from "../message/signed.js";
53
-
54
- export const placeholderCashAddr =
55
- "bitcoincash:qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqfnhks603";
56
- export const placeholderTokenAddr =
57
- "bitcoincash:zqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqweyg7usz";
58
-
59
- /**
60
- * A class to hold features used by all wallets
61
- * @class BaseWallet
62
- */
63
- export class BaseWallet implements WalletI {
64
- public static StorageProvider?: typeof StorageProvider;
65
-
66
- readonly walletCache: WalletCache;
67
- readonly provider: ElectrumNetworkProvider;
68
- readonly network: NetworkType;
69
- readonly walletType: WalletTypeEnum;
70
- _slpSemiAware: boolean = false; // a flag which requires an utxo to have more than 546 sats to be spendable and counted in the balance
71
- // readonly publicKeyHash!: Uint8Array;
72
- // readonly cashaddr!: string;
73
- // readonly tokenaddr!: string;
74
- readonly isTestnet: boolean;
75
- name: string = "";
76
- _util?: Util;
77
- protected cancelFns: CancelFn[] = [];
78
-
79
- public get networkPrefix(): CashAddressNetworkPrefix {
80
- return prefixFromNetworkMap[this.network];
81
- }
82
-
83
- // interface to util functions. see Util.ts
84
- public get util() {
85
- if (!this._util) {
86
- this._util = new Util(this.network);
87
- }
88
-
89
- return this._util;
90
- }
91
-
92
- // interface to util util. see Util.Util
93
- public static get util() {
94
- return new this().util;
95
- }
96
-
97
- // Return wallet info
98
- public getInfo(): WalletInfoI {
99
- throw Error("getInfo not implemented in BaseWallet");
100
- }
101
-
102
- public slpSemiAware(value: boolean = true): this {
103
- this._slpSemiAware = value;
104
- return this;
105
- }
106
-
107
- //#region Accessors
108
- protected getNetworkProvider(
109
- // @ts-ignore
110
- network: NetworkType = NetworkType.Mainnet
111
- ): any {
112
- return getNetworkProvider(network);
113
- }
114
-
115
- /**
116
- * getDepositAddress - get a wallet deposit address
117
- *
118
- * a high-level function,
119
- *
120
- * @see {@link https://rest-unstable.mainnet.cash/api-docs/#/wallet/depositAddress|/wallet/deposit_address} for REST endpoint
121
- *
122
- * @returns The deposit address as a string
123
- */
124
- public getDepositAddress(): string {
125
- // return this.cashaddr;
126
- throw Error("getDepositAddress not implemented in BaseWallet");
127
- }
128
-
129
- /**
130
- * getChangeAddress - get a wallet change address
131
- *
132
- * a high-level function,
133
- *
134
- * @see {@link https://rest-unstable.mainnet.cash/api-docs/#/wallet/changeAddress|/wallet/change_address} for REST endpoint
135
- *
136
- * @returns The change address as a string
137
- */
138
- public getChangeAddress(): string {
139
- // return this.cashaddr;
140
- throw Error("getChangeAddress not implemented in BaseWallet");
141
- }
142
-
143
- /**
144
- * getTokenDepositAddress - get a cashtoken aware wallet deposit address
145
- *
146
- * @returns The cashtoken aware deposit address as a string
147
- */
148
- public getTokenDepositAddress(): string {
149
- // return this.tokenaddr;
150
- throw Error("getTokenDepositAddress not implemented in BaseWallet");
151
- }
152
-
153
- /**
154
- * getTokenDepositAddress - get a cashtoken aware wallet deposit address
155
- *
156
- * @returns The cashtoken aware deposit address as a string
157
- */
158
- public getTokenChangeAddress(): string {
159
- // return this.tokenaddr;
160
- throw Error("getTokenDepositAddress not implemented in BaseWallet");
161
- }
162
-
163
- // check if a given address belongs to this wallet
164
- public hasAddress(address: string): boolean {
165
- return (
166
- address === this.getDepositAddress() ||
167
- address === this.getChangeAddress()
168
- );
169
- }
170
- //#endregion Accessors
171
-
172
- //#region Constructors and Statics
173
- /**
174
- * constructor for a new wallet
175
- * @param network network for wallet
176
- *
177
- * @throws {Error} if called on BaseWallet
178
- */
179
- constructor(network = NetworkType.Mainnet) {
180
- this.network = network;
181
- this.walletType = WalletTypeEnum.Watch;
182
- this.provider = this.getNetworkProvider(this.network);
183
- this.isTestnet = this.network === NetworkType.Mainnet ? false : true;
184
- this.walletCache = new Map<string, { privateKey: Uint8Array }>();
185
- }
186
- //#endregion Constructors
187
-
188
- /**
189
- * named (internal) get a named wallet from the database or create a new one.
190
- * Note: this function should behave identically if
191
- *
192
- * @param {string} name name of the wallet
193
- * @param {string} dbName database name the wallet is stored in
194
- * @param {boolean} forceNew attempt to overwrite an existing wallet
195
- *
196
- * @throws {Error} if forceNew is true and the wallet already exists
197
- * @returns a promise to a named wallet
198
- */
199
- protected async named(
200
- name: string,
201
- dbName?: string,
202
- forceNew: boolean = false
203
- ): Promise<this> {
204
- if (name.length === 0) {
205
- throw Error("Named wallets must have a non-empty name");
206
- }
207
- _checkContextSafety(this);
208
- this.name = name;
209
- dbName = dbName ? dbName : prefixFromNetworkMap[this.network];
210
- const db = getStorageProvider(dbName);
211
-
212
- // If there is a database, force saving or error
213
- if (db) {
214
- await db.init();
215
- const savedWalletRecord = await db.getWallet(name);
216
- if (savedWalletRecord) {
217
- await db.close();
218
- if (forceNew) {
219
- throw Error(
220
- `A wallet with the name ${name} already exists in ${dbName}`
221
- );
222
- }
223
- const recoveredWallet = await this.fromId(savedWalletRecord.wallet);
224
- recoveredWallet.name = savedWalletRecord.name;
225
- return recoveredWallet;
226
- } else {
227
- const wallet = await this.initialize();
228
- wallet.name = name;
229
- await db.addWallet(wallet.name, wallet.toDbString());
230
- await db.close();
231
- return wallet;
232
- }
233
- } else {
234
- throw Error(
235
- "No database was available or configured to store the named wallet."
236
- );
237
- }
238
- }
239
-
240
- /**
241
- * replaceNamed - Replace (recover) named wallet with a new walletId
242
- *
243
- * If wallet with a provided name does not exist yet, it will be created with a `walletId` supplied
244
- * If wallet exists it will be overwritten without exception
245
- *
246
- * @param name user friendly wallet alias
247
- * @param walletId walletId options to steer the creation process
248
- * @param dbName name under which the wallet will be stored in the database
249
- *
250
- * @returns instantiated wallet
251
- */
252
- protected async replaceNamed(
253
- name: string,
254
- walletId: string,
255
- dbName?: string
256
- ): Promise<this> {
257
- if (name.length === 0) {
258
- throw Error("Named wallets must have a non-empty name");
259
- }
260
- _checkContextSafety(this);
261
- this.name = name;
262
- dbName = dbName ? dbName : prefixFromNetworkMap[this.network];
263
- let db = getStorageProvider(dbName);
264
-
265
- if (db) {
266
- await db.init();
267
- let savedWalletRecord = await db.getWallet(name);
268
- await this.fromId(walletId);
269
- if (savedWalletRecord) {
270
- await db.updateWallet(name, walletId);
271
- } else {
272
- await db.addWallet(name, walletId);
273
- }
274
-
275
- await db.close();
276
- return this;
277
- } else {
278
- throw Error(
279
- "No database was available or configured to store the named wallet."
280
- );
281
- }
282
- }
283
-
284
- /**
285
- * namedExists - check if a named wallet already exists
286
- *
287
- * @param name user friendly wallet alias
288
- * @param dbName name under which the wallet will be stored in the database
289
- *
290
- * @returns boolean
291
- */
292
- protected async namedExists(name: string, dbName?: string): Promise<boolean> {
293
- if (name.length === 0) {
294
- throw Error("Named wallets must have a non-empty name");
295
- }
296
- _checkContextSafety(this);
297
- dbName = dbName ? dbName : prefixFromNetworkMap[this.network];
298
- let db = getStorageProvider(dbName);
299
-
300
- if (db) {
301
- await db.init();
302
- let savedWalletRecord = await db.getWallet(name);
303
- await db.close();
304
- return savedWalletRecord !== undefined;
305
- } else {
306
- throw Error(
307
- "No database was available or configured to store the named wallet."
308
- );
309
- }
310
- }
311
-
312
- protected async initialize(): Promise<this> {
313
- return this;
314
- }
315
-
316
- //#region Serialization
317
- /**
318
- * toDbString - store the serialized version of the wallet in the database, not just the name
319
- *
320
- * @throws {Error} if called on BaseWallet
321
- */
322
- public toDbString(): string {
323
- return `${this.walletType}:${this.network}:${this.getDepositAddress()}`;
324
- }
325
-
326
- // Returns the serialized wallet as a string
327
- // If storing in a database, set asNamed to false to store secrets
328
- // In all other cases, the a named wallet is deserialized from the database
329
- // by the name key
330
- public toString() {
331
- return `${this.walletType}:${this.network}:${this.getDepositAddress()}`;
332
- }
333
- //#endregion Serialization
334
-
335
- /**
336
- * explorerUrl Web url to a transaction on a block explorer
337
- *
338
- * @param txId transaction Id
339
- * @returns Url string
340
- */
341
- public explorerUrl(txId: string) {
342
- const explorerUrlMap = {
343
- mainnet: "https://blockchair.com/bitcoin-cash/transaction/",
344
- testnet: "https://www.blockchain.com/bch-testnet/tx/",
345
- regtest: "",
346
- };
347
-
348
- return explorerUrlMap[this.network] + txId;
349
- }
350
-
351
- /**
352
- * named - create a named wallet
353
- *
354
- * @param name user friendly wallet alias
355
- * @param dbName name under which the wallet will be stored in the database
356
- * @param force force recreate wallet in the database if a record already exist
357
- *
358
- * @returns instantiated wallet
359
- */
360
- public static async named<T extends typeof BaseWallet>(
361
- this: T,
362
- name: string,
363
- dbName?: string,
364
- force?: boolean
365
- ): Promise<InstanceType<T>> {
366
- return new this().named(name, dbName, force) as InstanceType<T>;
367
- }
368
-
369
- /**
370
- * replaceNamed - replace (recover) named wallet with a new walletId
371
- *
372
- * If wallet with a provided name does not exist yet, it will be created with a `walletId` supplied
373
- * If wallet exists it will be overwritten without exception
374
- *
375
- * @param name user friendly wallet alias
376
- * @param walletId walletId options to steer the creation process
377
- * @param dbName name under which the wallet will be stored in the database
378
- *
379
- * @returns instantiated wallet
380
- */
381
- public static async replaceNamed<T extends typeof BaseWallet>(
382
- this: T,
383
- name: string,
384
- walletId: string,
385
- dbName?: string
386
- ): Promise<InstanceType<T>> {
387
- return new this().replaceNamed(name, walletId, dbName) as InstanceType<T>;
388
- }
389
-
390
- /**
391
- * namedExists - check if a named wallet already exists
392
- *
393
- * @param name user friendly wallet alias
394
- * @param dbName name under which the wallet will be stored in the database
395
- *
396
- * @returns boolean
397
- */
398
- public static async namedExists(
399
- name: string,
400
- dbName?: string
401
- ): Promise<boolean> {
402
- return new this().namedExists(name, dbName);
403
- }
404
-
405
- protected fromId(walletId: string): Promise<this> {
406
- throw Error("fromId not implemented in BaseWallet");
407
- }
408
-
409
- //#region Funds
410
- /**
411
- * utxos Get unspent outputs for the wallet
412
- *
413
- */
414
- public async getUtxos(): Promise<Utxo[]> {
415
- throw Error("getUtxos not implemented in BaseWallet");
416
- }
417
-
418
- // Gets balance by summing value in all utxos in sats
419
- // Balance includes DUST utxos which could be slp tokens and also cashtokens with BCH amounts
420
- public async getBalance(): Promise<bigint> {
421
- throw Error("getBalance not implemented in BaseWallet");
422
- }
423
-
424
- /**
425
- * Track a cancel function so it can be cancelled by stop()
426
- * Returns a wrapped cancel function that also removes itself from tracking
427
- */
428
- protected trackCancelFn(cancelFn: CancelFn): CancelFn {
429
- this.cancelFns.push(cancelFn);
430
- return async () => {
431
- const index = this.cancelFns.indexOf(cancelFn);
432
- if (index !== -1) {
433
- this.cancelFns.splice(index, 1);
434
- }
435
- await cancelFn();
436
- };
437
- }
438
-
439
- /**
440
- * Stop all active subscriptions on this wallet
441
- */
442
- public async stop(): Promise<void> {
443
- const fns = this.cancelFns.splice(0);
444
- await Promise.all(fns.map((fn) => fn()));
445
- }
446
-
447
- /**
448
- * Watch wallet for any activity (status changes)
449
- * This is the foundation for watchWalletBalance and watchWalletTransactions
450
- * @param callback - Called when the wallet has a status change
451
- * @returns Cancel function to stop watching
452
- */
453
- public async watchStatus(
454
- callback: (status: string | null, address: string) => void
455
- ): Promise<CancelFn> {
456
- const cancelFn = await this.provider.watchAddressStatus(
457
- this.getDepositAddress(),
458
- (status) => callback(status, this.getDepositAddress())
459
- );
460
- return this.trackCancelFn(cancelFn);
461
- }
462
-
463
- /**
464
- * No-op for single-address wallets. HDWallet overrides this to wait for
465
- * depositIndex/changeIndex advancement.
466
- */
467
- public async waitForUpdate(
468
- _options: {
469
- depositIndex?: number;
470
- changeIndex?: number;
471
- timeout?: number;
472
- } = {}
473
- ): Promise<void> {}
474
-
475
- // sets up a callback to be called upon wallet's balance change
476
- // can be cancelled by calling the function returned from this one
477
- public async watchBalance(
478
- callback: (balance: bigint) => void
479
- ): Promise<CancelFn> {
480
- return this.watchStatus(async () => {
481
- const balance = await this.getBalance();
482
- callback(balance);
483
- });
484
- }
485
-
486
- // waits for address balance to be greater than or equal to the target value
487
- // this call halts the execution
488
- public async waitForBalance(value: bigint): Promise<bigint> {
489
- return new Promise(async (resolve) => {
490
- let watchCancel: CancelFn;
491
- watchCancel = await this.watchBalance(async (balance: bigint) => {
492
- if (balance >= value) {
493
- await watchCancel?.();
494
- resolve(balance);
495
- }
496
- });
497
- });
498
- }
499
-
500
- // sets up a callback to be called upon wallet's token balance change
501
- // can be cancelled by calling the function returned from this one
502
- public async watchTokenBalance(
503
- category: string,
504
- callback: (balance: bigint) => void
505
- ): Promise<CancelFn> {
506
- return await this.watchStatus(async () => {
507
- const balance = await this.getTokenBalance(category);
508
- callback(balance);
509
- });
510
- }
511
-
512
- // waits for address token balance to be greater than or equal to the target amount
513
- // this call halts the execution
514
- public async waitForTokenBalance(
515
- category: string,
516
- amount: bigint
517
- ): Promise<bigint> {
518
- return new Promise(async (resolve) => {
519
- let watchCancel: CancelFn;
520
- watchCancel = await this.watchTokenBalance(
521
- category,
522
- async (balance: bigint) => {
523
- if (balance >= amount) {
524
- await watchCancel?.();
525
- resolve(balance);
526
- }
527
- }
528
- );
529
- });
530
- }
531
-
532
- /**
533
- * Watch wallet for new transactions
534
- * @param callback - Called with new transaction hashes when they appear
535
- * @returns Cancel function to stop watching
536
- */
537
- public async watchTransactionHashes(
538
- callback: (txHash: string) => void
539
- ): Promise<CancelFn> {
540
- const seenTxHashes = new Set<string>();
541
-
542
- let topHeight = 0;
543
-
544
- return this.watchStatus(async () => {
545
- const history = (await this.getRawHistory(topHeight)).sort((a, b) =>
546
- a.height <= 0 || b.height <= 0 ? -1 : b.height - a.height
547
- );
548
-
549
- const newTxHashes: string[] = [];
550
-
551
- for (const tx of history) {
552
- if (tx.height > topHeight) {
553
- topHeight = tx.height;
554
- }
555
-
556
- if (!seenTxHashes.has(tx.tx_hash)) {
557
- seenTxHashes.add(tx.tx_hash);
558
- newTxHashes.push(tx.tx_hash);
559
- }
560
- }
561
-
562
- if (newTxHashes.length > 0) {
563
- newTxHashes.forEach((txHash) => callback(txHash));
564
- }
565
- });
566
- }
567
-
568
- /**
569
- * Watch wallet for new transactions
570
- * @param callback - Called with new transaction hashes when they appear
571
- * @returns Cancel function to stop watching
572
- */
573
- public async watchTransactions(
574
- callback: (transaction: ElectrumRawTransaction) => void
575
- ): Promise<CancelFn> {
576
- return this.watchTransactionHashes(async (txHash: string) => {
577
- const tx = await this.provider.getRawTransactionObject(txHash);
578
- callback(tx);
579
- });
580
- }
581
-
582
- public async watchTokenTransactions(
583
- callback: (tx: ElectrumRawTransaction) => void
584
- ): Promise<CancelFn> {
585
- return this.watchTransactions(
586
- async (transaction: ElectrumRawTransaction) => {
587
- if (transaction.vout.some((val) => val.tokenData)) {
588
- callback(transaction);
589
- }
590
- }
591
- );
592
- }
593
-
594
- protected async _getMaxAmountToSend(
595
- params: {
596
- outputCount?: number;
597
- options?: SendRequestOptionsI;
598
- privateKey?: Uint8Array;
599
- } = {
600
- outputCount: 1,
601
- options: {},
602
- }
603
- ): Promise<{ value: bigint; utxos: Utxo[] }> {
604
- if (params.options && params.options.slpSemiAware) {
605
- this._slpSemiAware = true;
606
- }
607
-
608
- let feePaidBy;
609
- if (params.options && params.options.feePaidBy) {
610
- feePaidBy = params.options.feePaidBy;
611
- } else {
612
- feePaidBy = FeePaidByEnum.change;
613
- }
614
-
615
- // get inputs
616
- let utxos: Utxo[];
617
- const allUtxos = await this.getUtxos();
618
- if (params.options && params.options.utxoIds) {
619
- utxos = checkUtxos(
620
- params.options.utxoIds.map((utxoId: Utxo | string) =>
621
- typeof utxoId === "string" ? fromUtxoId(utxoId) : utxoId
622
- ),
623
- allUtxos
624
- );
625
- } else {
626
- utxos = allUtxos.filter((utxo) => !utxo.token);
627
- }
628
-
629
- // Get current height to assure recently mined coins are not spent.
630
- const bestHeight = await this.provider.getBlockHeight();
631
-
632
- // simulate outputs using the sender's address
633
- const sendRequest = new SendRequest({
634
- cashaddr: placeholderCashAddr,
635
- value: 100n,
636
- });
637
- const sendRequests = Array(params.outputCount)
638
- .fill(0)
639
- .map(() => sendRequest);
640
-
641
- const fundingUtxos = await getSuitableUtxos(
642
- utxos,
643
- undefined,
644
- bestHeight,
645
- feePaidBy,
646
- sendRequests
647
- );
648
- const relayFeePerByteInSatoshi = await getRelayFeeCache(this.provider);
649
- const fee = await getFeeAmountSimple({
650
- utxos: fundingUtxos,
651
- sendRequests: sendRequests,
652
- sourceAddress: placeholderCashAddr,
653
- relayFeePerByteInSatoshi: relayFeePerByteInSatoshi,
654
- feePaidBy: feePaidBy,
655
- });
656
- const spendableAmount = sumUtxoValue(fundingUtxos);
657
-
658
- let result = spendableAmount - fee;
659
- if (result < 0n) {
660
- result = 0n;
661
- }
662
-
663
- return { value: result, utxos: fundingUtxos };
664
- }
665
-
666
- public async getMaxAmountToSend(
667
- params: {
668
- outputCount?: number;
669
- options?: SendRequestOptionsI;
670
- } = {
671
- outputCount: 1,
672
- options: {},
673
- }
674
- ): Promise<bigint> {
675
- const { value: result } = await this._getMaxAmountToSend(params);
676
-
677
- return result;
678
- }
679
-
680
- /**
681
- * send Send some amount to an address
682
- * this function processes the send requests, encodes the transaction, sends it to the network
683
- * @returns (depending on the options parameter) the transaction id, new address balance and a link to the transaction on the blockchain explorer
684
- *
685
- * This is a first class function with REST analog, maintainers should strive to keep backward-compatibility
686
- *
687
- */
688
- public async send(
689
- requests:
690
- | SendRequest
691
- | TokenSendRequest
692
- | OpReturnData
693
- | Array<SendRequest | TokenSendRequest | OpReturnData>
694
- | SendRequestArray[],
695
- options?: SendRequestOptionsI
696
- ): Promise<SendResponse> {
697
- const { encodedTransaction, categories, sourceOutputs } =
698
- await this.encodeTransaction(requests, undefined, options);
699
-
700
- const resp = new SendResponse({});
701
- resp.categories = categories;
702
-
703
- if (options?.buildUnsigned !== true) {
704
- const txId = await this.submitTransaction(
705
- encodedTransaction,
706
- options?.awaitTransactionPropagation === undefined ||
707
- options?.awaitTransactionPropagation === true
708
- );
709
-
710
- resp.txId = txId;
711
- resp.explorerUrl = this.explorerUrl(resp.txId);
712
-
713
- if (
714
- options?.queryBalance === undefined ||
715
- options?.queryBalance === true
716
- ) {
717
- resp.balance = await this.getBalance();
718
- }
719
- } else {
720
- resp.unsignedTransaction = binToHex(encodedTransaction);
721
- resp.sourceOutputs = sourceOutputs;
722
- }
723
-
724
- return resp;
725
- }
726
-
727
- /**
728
- * sendMax Send all available funds to a destination cash address
729
- *
730
- * @param {string} cashaddr destination cash address
731
- * @param {SendRequestOptionsI} options Options of the send requests
732
- *
733
- * @returns (depending on the options parameter) the transaction id, new address balance and a link to the transaction on the blockchain explorer
734
- */
735
- public async sendMax(
736
- cashaddr: string,
737
- options?: SendRequestOptionsI
738
- ): Promise<SendResponse> {
739
- return await this.sendMaxRaw(cashaddr, options);
740
- }
741
-
742
- /**
743
- * sendMaxRaw (internal) Send all available funds to a destination cash address
744
- *
745
- * @param {string} cashaddr destination cash address
746
- * @param {SendRequestOptionsI} options Options of the send requests
747
- *
748
- * @returns the transaction id sent to the network
749
- */
750
- protected async sendMaxRaw(
751
- cashaddr: string,
752
- options?: SendRequestOptionsI,
753
- privateKey?: Uint8Array
754
- ): Promise<SendResponse> {
755
- const { value: maxSpendableAmount, utxos } = await this._getMaxAmountToSend(
756
- {
757
- outputCount: 1,
758
- options: options,
759
- privateKey: privateKey,
760
- }
761
- );
762
-
763
- if (!options) {
764
- options = {};
765
- }
766
-
767
- options.utxoIds = utxos;
768
-
769
- const sendRequest = new SendRequest({
770
- cashaddr: cashaddr,
771
- value: maxSpendableAmount,
772
- });
773
-
774
- const { encodedTransaction, categories, sourceOutputs } =
775
- await this.encodeTransaction([sendRequest], true, options, privateKey);
776
-
777
- const resp = new SendResponse({});
778
- resp.categories = categories;
779
-
780
- if (options?.buildUnsigned !== true) {
781
- const txId = await this.submitTransaction(
782
- encodedTransaction,
783
- options?.awaitTransactionPropagation === undefined ||
784
- options?.awaitTransactionPropagation === true
785
- );
786
-
787
- resp.txId = txId;
788
- resp.explorerUrl = this.explorerUrl(resp.txId);
789
-
790
- if (
791
- options?.queryBalance === undefined ||
792
- options?.queryBalance === true
793
- ) {
794
- resp.balance = await this.getBalance();
795
- }
796
- } else {
797
- resp.unsignedTransaction = binToHex(encodedTransaction);
798
- resp.sourceOutputs = sourceOutputs;
799
- }
800
-
801
- return resp;
802
- }
803
-
804
- /**
805
- * encodeTransaction Encode and sign a transaction given a list of sendRequests, options and estimate fees.
806
- * @param {SendRequest[]} sendRequests SendRequests
807
- * @param {boolean} discardChange=false
808
- * @param {SendRequestOptionsI} options Options of the send requests
809
- */
810
- protected async encodeTransaction(
811
- requests:
812
- | SendRequest
813
- | TokenSendRequest
814
- | OpReturnData
815
- | Array<SendRequest | TokenSendRequest | OpReturnData>
816
- | SendRequestArray[],
817
- discardChange: boolean = false,
818
- options?: SendRequestOptionsI,
819
- privateKey?: Uint8Array
820
- ) {
821
- let sendRequests = asSendRequestObject(requests);
822
-
823
- if (options && options.slpSemiAware) {
824
- this._slpSemiAware = true;
825
- }
826
-
827
- let feePaidBy: FeePaidByEnum;
828
- if (options?.feePaidBy) {
829
- feePaidBy = options.feePaidBy;
830
- } else {
831
- feePaidBy = FeePaidByEnum.change;
832
- }
833
-
834
- let changeAddress: string;
835
- if (options?.changeAddress) {
836
- changeAddress = options.changeAddress;
837
- } else {
838
- changeAddress = this.getChangeAddress();
839
- }
840
-
841
- let checkTokenQuantities: boolean = true;
842
- if (options?.checkTokenQuantities === false) {
843
- checkTokenQuantities = false;
844
- }
845
-
846
- // get inputs from options or query all inputs
847
- let utxos: Utxo[] = await this.getUtxos();
848
- if (options && options.utxoIds) {
849
- utxos = checkUtxos(
850
- options.utxoIds.map((utxoId: Utxo | string) =>
851
- typeof utxoId === "string" ? fromUtxoId(utxoId) : utxoId
852
- ),
853
- utxos
854
- );
855
- }
856
-
857
- // filter out token utxos if there are no token requests
858
- if (
859
- checkTokenQuantities &&
860
- !sendRequests.some((val) => val instanceof TokenSendRequest)
861
- ) {
862
- utxos = utxos.filter((val) => !val.token);
863
- }
864
-
865
- const addTokenChangeOutputs = (
866
- inputs: Utxo[],
867
- outputs: SendRequestType[]
868
- ) => {
869
- // Allow for implicit token burn if the total amount sent is less than user had
870
- // allow for token genesis, creating more tokens than we had before (0)
871
- if (!checkTokenQuantities) {
872
- return;
873
- }
874
- const allTokenInputs = inputs.filter((val) => val.token);
875
- const allTokenOutputs = outputs.filter(
876
- (val) => val instanceof TokenSendRequest
877
- ) as TokenSendRequest[];
878
- const categories = allTokenOutputs
879
- .map((val) => val.category)
880
- .filter((val, idx, arr) => arr.indexOf(val) === idx);
881
- for (let category of categories) {
882
- const tokenInputs = allTokenInputs.filter(
883
- (val) => val.token?.category === category
884
- );
885
- const inputAmountSum = tokenInputs.reduce(
886
- (prev, cur) => prev + cur.token!.amount,
887
- 0n
888
- );
889
- const tokenOutputs = allTokenOutputs.filter(
890
- (val) => val.category === category
891
- );
892
- const outputAmountSum = tokenOutputs.reduce(
893
- (prev, cur) => prev + cur.amount,
894
- 0n
895
- );
896
-
897
- const diff = inputAmountSum - outputAmountSum;
898
- if (diff < 0) {
899
- throw new Error("Not enough token amount to send");
900
- }
901
- if (diff >= 0) {
902
- let available = 0n;
903
- let change = 0n;
904
- const ensureUtxos: Utxo[] = [];
905
- for (const token of tokenInputs.filter((val) => val.token?.amount)) {
906
- ensureUtxos.push(token);
907
- available += token.token!.amount;
908
- if (available >= outputAmountSum) {
909
- change = available - outputAmountSum;
910
- //break;
911
- }
912
- }
913
- if (ensureUtxos.length) {
914
- if (!options) {
915
- options = {};
916
- }
917
- options!.ensureUtxos = [
918
- ...(options.ensureUtxos ?? []),
919
- ...ensureUtxos,
920
- ].filter(
921
- (val, index, array) =>
922
- array.findIndex(
923
- (other) => other.txid === val.txid && other.vout === val.vout
924
- ) === index
925
- );
926
- }
927
- if (change > 0) {
928
- outputs.push(
929
- new TokenSendRequest({
930
- cashaddr: toTokenaddr(this.getChangeAddress()),
931
- amount: change,
932
- category: category,
933
- nft: tokenOutputs[0].nft,
934
- value: tokenOutputs[0].value,
935
- })
936
- );
937
- }
938
- }
939
- }
940
- };
941
- addTokenChangeOutputs(utxos, sendRequests);
942
-
943
- const bestHeight = await this.provider.getBlockHeight();
944
- const spendAmount = await sumSendRequestAmounts(sendRequests);
945
-
946
- if (utxos.length === 0) {
947
- throw Error("There were no Unspent Outputs");
948
- }
949
- if (typeof spendAmount !== "bigint") {
950
- throw Error("Couldn't get spend amount when building transaction");
951
- }
952
-
953
- const relayFeePerByteInSatoshi = await getRelayFeeCache(this.provider);
954
- const feeEstimate = await getFeeAmountSimple({
955
- utxos: utxos,
956
- sendRequests: sendRequests,
957
- sourceAddress: this.getDepositAddress(),
958
- relayFeePerByteInSatoshi: relayFeePerByteInSatoshi,
959
- feePaidBy: feePaidBy,
960
- });
961
-
962
- const fundingUtxos = await getSuitableUtxos(
963
- utxos,
964
- spendAmount + feeEstimate,
965
- bestHeight,
966
- feePaidBy,
967
- sendRequests,
968
- options?.ensureUtxos || [],
969
- options?.tokenOperation
970
- );
971
- if (fundingUtxos.length === 0) {
972
- throw Error(
973
- "The available inputs couldn't satisfy the request with fees"
974
- );
975
- }
976
- const fee = await getFeeAmount({
977
- utxos: fundingUtxos,
978
- sendRequests: sendRequests,
979
- sourceAddress: this.getDepositAddress(),
980
- relayFeePerByteInSatoshi: relayFeePerByteInSatoshi,
981
- feePaidBy: feePaidBy,
982
- walletCache: this.walletCache,
983
- });
984
- const { encodedTransaction, sourceOutputs } = await buildEncodedTransaction(
985
- {
986
- inputs: fundingUtxos,
987
- outputs: sendRequests,
988
- signingKey: privateKey ?? placeholderPrivateKeyBin,
989
- fee,
990
- discardChange,
991
- feePaidBy,
992
- changeAddress,
993
- buildUnsigned: options?.buildUnsigned === true,
994
- walletCache: this.walletCache,
995
- }
996
- );
997
-
998
- const categories = [
999
- ...fundingUtxos
1000
- .filter((val) => val.token?.category)
1001
- .map((val) => val.token!.category),
1002
- ...sendRequests
1003
- .filter((val) => val instanceof TokenSendRequest)
1004
- .map((val) => (val as TokenSendRequest).category),
1005
- ].filter((value, index, array) => array.indexOf(value) === index);
1006
-
1007
- return { encodedTransaction, categories, sourceOutputs };
1008
- }
1009
-
1010
- // Submit a raw transaction
1011
- public async submitTransaction(
1012
- transaction: Uint8Array,
1013
- awaitPropagation: boolean = true
1014
- ): Promise<string> {
1015
- if (!this.provider) {
1016
- throw Error("Wallet network provider was not initialized");
1017
- }
1018
- let rawTransaction = binToHex(transaction);
1019
- return await this.provider.sendRawTransaction(
1020
- rawTransaction,
1021
- awaitPropagation
1022
- );
1023
- }
1024
-
1025
- // gets transaction history of this wallet
1026
- public async getRawHistory(
1027
- fromHeight: number = 0,
1028
- toHeight: number = -1
1029
- ): Promise<TxI[]> {
1030
- throw Error("getRawHistory not implemented in BaseWallet");
1031
- }
1032
-
1033
- // gets last transaction of this wallet
1034
- public async getLastTransaction(
1035
- confirmedOnly: boolean = false
1036
- ): Promise<ElectrumRawTransaction | null> {
1037
- let history: TxI[] = await this.getRawHistory();
1038
- if (confirmedOnly) {
1039
- history = history.filter((val) => val.height > 0);
1040
- }
1041
-
1042
- if (!history.length) {
1043
- return null;
1044
- }
1045
-
1046
- const [lastTx] = history.slice(-1);
1047
- return this.provider.getRawTransactionObject(lastTx.tx_hash);
1048
- }
1049
-
1050
- // waits for next transaction, program execution is halted
1051
- public async waitForTransaction(
1052
- options: WaitForTransactionOptions = {
1053
- getTransactionInfo: true,
1054
- getBalance: false,
1055
- txHash: undefined,
1056
- }
1057
- ): Promise<WaitForTransactionResponse> {
1058
- if (options.getTransactionInfo === undefined) {
1059
- options.getTransactionInfo = true;
1060
- }
1061
-
1062
- return new Promise(async (resolve) => {
1063
- let txHashSeen = false;
1064
-
1065
- const makeResponse = async (txHash?: string) => {
1066
- const response = <WaitForTransactionResponse>{};
1067
- const promises: any[] = [undefined, undefined];
1068
-
1069
- if (options.getBalance === true) {
1070
- promises[0] = this.getBalance();
1071
- }
1072
-
1073
- if (options.getTransactionInfo === true) {
1074
- if (!txHash) {
1075
- promises[1] = this.getLastTransaction();
1076
- } else {
1077
- promises[1] = this.provider.getRawTransactionObject(txHash);
1078
- }
1079
- }
1080
-
1081
- const result = await Promise.all(promises);
1082
- response.balance = result[0];
1083
- response.transactionInfo = result[1];
1084
-
1085
- return response;
1086
- };
1087
-
1088
- // waiting for a specific transaction to propagate
1089
- if (options.txHash) {
1090
- let cancel: CancelFn;
1091
-
1092
- const waitForTransactionCallback = async (data) => {
1093
- if (data && data[0] === options.txHash! && data[1] !== null) {
1094
- txHashSeen = true;
1095
- await cancel?.();
1096
-
1097
- resolve(makeResponse(options.txHash!));
1098
- }
1099
- };
1100
-
1101
- cancel = await this.provider.subscribeToTransaction(
1102
- options.txHash,
1103
- waitForTransactionCallback
1104
- );
1105
- return;
1106
- }
1107
-
1108
- // waiting for any address transaction
1109
- let watchCancel: CancelFn;
1110
- let initialResponseSeen = false;
1111
- watchCancel = await this.watchStatus(async (_status) => {
1112
- if (initialResponseSeen) {
1113
- await watchCancel?.();
1114
- resolve(makeResponse());
1115
- return;
1116
- }
1117
-
1118
- initialResponseSeen = true;
1119
- });
1120
- });
1121
- }
1122
-
1123
- /**
1124
- * watchBlocks Watch network blocks
1125
- *
1126
- * @param callback callback with a block header object
1127
- * @param skipCurrentHeight if set, the notification about current block will not arrive
1128
- *
1129
- * @returns a function which will cancel watching upon evaluation
1130
- */
1131
- public async watchBlocks(
1132
- callback: (header: HexHeaderI) => void,
1133
- skipCurrentHeight: boolean = true
1134
- ): Promise<CancelFn> {
1135
- return this.provider.watchBlocks(callback, skipCurrentHeight);
1136
- }
1137
-
1138
- /**
1139
- * waitForBlock Wait for a network block
1140
- *
1141
- * @param height if specified waits for this exact blockchain height, otherwise resolves with the next block
1142
- *
1143
- */
1144
- public async waitForBlock(height?: number): Promise<HexHeaderI> {
1145
- return this.provider.waitForBlock(height);
1146
- }
1147
-
1148
- //#endregion Funds
1149
-
1150
- //#region Cashtokens
1151
- /**
1152
- * Create new cashtoken, both funglible and/or non-fungible (NFT)
1153
- * Refer to spec https://github.com/bitjson/cashtokens
1154
- * @param {number} genesisRequest.amount amount of *fungible* tokens to create
1155
- * @param {NFTCapability?} genesisRequest.capability capability of new NFT
1156
- * @param {string?} genesisRequest.commitment NFT commitment message
1157
- * @param {string?} genesisRequest.cashaddr cash address to send the created token UTXO to; if undefined will default to your address
1158
- * @param {number?} genesisRequest.value satoshi value to send alongside with tokens; if undefined will default to 1000 satoshi
1159
- * @param {SendRequestType | SendRequestType[]} sendRequests single or an array of extra send requests (OP_RETURN, value transfer, etc.) to include in genesis transaction
1160
- * @param {SendRequestOptionsI} options Options of the send requests
1161
- */
1162
- public async tokenGenesis(
1163
- genesisRequest: TokenGenesisRequest,
1164
- sendRequests: SendRequestType | SendRequestType[] = [],
1165
- options?: SendRequestOptionsI
1166
- ): Promise<SendResponse> {
1167
- if (!Array.isArray(sendRequests)) {
1168
- sendRequests = [sendRequests];
1169
- }
1170
-
1171
- let utxos: Utxo[] = await this.getUtxos();
1172
- if (options?.utxoIds) {
1173
- utxos = checkUtxos(
1174
- options.utxoIds.map((utxoId: UtxoId | string) =>
1175
- typeof utxoId === "string" ? fromUtxoId(utxoId) : utxoId
1176
- ),
1177
- utxos
1178
- );
1179
- }
1180
-
1181
- const genesisInputs = utxos.filter((val) => val.vout === 0 && !val.token);
1182
- if (genesisInputs.length === 0) {
1183
- throw new Error(
1184
- "No suitable inputs with vout=0 available for new token genesis"
1185
- );
1186
- }
1187
-
1188
- const genesisSendRequest = new TokenSendRequest({
1189
- cashaddr: genesisRequest.cashaddr || this.getTokenDepositAddress(),
1190
- amount: genesisRequest.amount,
1191
- value: genesisRequest.value || 1000n,
1192
- nft: genesisRequest.nft,
1193
- category: genesisInputs[0].txid,
1194
- });
1195
-
1196
- return this.send([genesisSendRequest, ...(sendRequests as any)], {
1197
- ...options,
1198
- utxoIds: utxos,
1199
- ensureUtxos: [genesisInputs[0]],
1200
- checkTokenQuantities: false,
1201
- queryBalance: false,
1202
- tokenOperation: "genesis",
1203
- });
1204
- }
1205
-
1206
- /**
1207
- * Mint new NFT cashtokens using an existing minting token
1208
- * Refer to spec https://github.com/bitjson/cashtokens
1209
- * @param {string} category category of an NFT to mint
1210
- * @param {TokenMintRequest | TokenMintRequest[]} mintRequests mint requests with new token properties and recipients
1211
- * @param {NFTCapability?} mintRequest.capability capability of new NFT
1212
- * @param {string?} mintRequest.commitment NFT commitment message
1213
- * @param {string?} mintRequest.cashaddr cash address to send the created token UTXO to; if undefined will default to your address
1214
- * @param {number?} mintRequest.value satoshi value to send alongside with tokens; if undefined will default to 1000 satoshi
1215
- * @param {boolean?} deductTokenAmount if minting token contains fungible amount, deduct from it by amount of minted tokens
1216
- * @param {SendRequestOptionsI} options Options of the send requests
1217
- */
1218
- public async tokenMint(
1219
- category: string,
1220
- mintRequests: TokenMintRequest | Array<TokenMintRequest>,
1221
- deductTokenAmount: boolean = false,
1222
- options?: SendRequestOptionsI
1223
- ): Promise<SendResponse> {
1224
- if (category?.length !== 64) {
1225
- throw Error(`Invalid category supplied: ${category}`);
1226
- }
1227
-
1228
- if (!Array.isArray(mintRequests)) {
1229
- mintRequests = [mintRequests];
1230
- }
1231
-
1232
- const utxos = await this.getUtxos();
1233
- const nftUtxos = utxos.filter(
1234
- (val) =>
1235
- val.token?.category === category &&
1236
- val.token?.nft?.capability === NFTCapability.minting
1237
- );
1238
- if (!nftUtxos.length) {
1239
- throw new Error(
1240
- "You do not have any token UTXOs with minting capability for specified category"
1241
- );
1242
- }
1243
- const newAmount =
1244
- deductTokenAmount && nftUtxos[0].token!.amount > 0
1245
- ? nftUtxos[0].token!.amount - BigInt(mintRequests.length)
1246
- : nftUtxos[0].token!.amount;
1247
- const safeNewAmount = newAmount < 0n ? 0n : newAmount;
1248
- const mintingInput = new TokenSendRequest({
1249
- cashaddr: toTokenaddr(nftUtxos[0].address),
1250
- category: category,
1251
- nft: nftUtxos[0].token?.nft,
1252
- amount: safeNewAmount,
1253
- value: nftUtxos[0].satoshis,
1254
- });
1255
- return this.send(
1256
- [
1257
- mintingInput,
1258
- ...mintRequests.map(
1259
- (val) =>
1260
- new TokenSendRequest({
1261
- cashaddr: val.cashaddr || this.getTokenDepositAddress(),
1262
- amount: 0n,
1263
- category: category,
1264
- value: val.value,
1265
- nft: val.nft,
1266
- })
1267
- ),
1268
- ],
1269
- {
1270
- ...options,
1271
- ensureUtxos: [nftUtxos[0]],
1272
- checkTokenQuantities: false,
1273
- queryBalance: false,
1274
- tokenOperation: "mint",
1275
- }
1276
- );
1277
- }
1278
-
1279
- /**
1280
- * Perform an explicit token burning by spending a token utxo to an OP_RETURN
1281
- *
1282
- * Behaves differently for fungible and non-fungible tokens:
1283
- * * NFTs are always "destroyed"
1284
- * * FTs' amount is reduced by the amount specified, if 0 FT amount is left and no NFT present, the token is "destroyed"
1285
- *
1286
- * Refer to spec https://github.com/bitjson/cashtokens
1287
- * @param {string} burnRequest.category category of a token to burn
1288
- * @param {NFTCapability} burnRequest.capability capability of the NFT token to select, optional
1289
- * @param {string?} burnRequest.commitment commitment of the NFT token to select, optional
1290
- * @param {number?} burnRequest.amount amount of fungible tokens to burn, optional
1291
- * @param {string?} burnRequest.cashaddr address to return token and satoshi change to
1292
- * @param {string?} message optional message to include in OP_RETURN
1293
- * @param {SendRequestOptionsI} options Options of the send requests
1294
- */
1295
- public async tokenBurn(
1296
- burnRequest: TokenBurnRequest,
1297
- message?: string,
1298
- options?: SendRequestOptionsI
1299
- ): Promise<SendResponse> {
1300
- if (burnRequest.category?.length !== 64) {
1301
- throw Error(`Invalid category supplied: ${burnRequest.category}`);
1302
- }
1303
-
1304
- const utxos = await this.getUtxos();
1305
- const tokenUtxos = utxos.filter(
1306
- (val) =>
1307
- val.token?.category === burnRequest.category &&
1308
- val.token?.nft?.capability === burnRequest.nft?.capability &&
1309
- val.token?.nft?.commitment === burnRequest.nft?.commitment
1310
- );
1311
-
1312
- if (!tokenUtxos.length) {
1313
- throw new Error("You do not have suitable token UTXOs to perform burn");
1314
- }
1315
-
1316
- const totalFungibleAmount = tokenUtxos.reduce(
1317
- (prev, cur) => prev + (cur.token?.amount || 0n),
1318
- 0n
1319
- );
1320
- let fungibleBurnAmount =
1321
- burnRequest.amount && burnRequest.amount > 0 ? burnRequest.amount! : 0n;
1322
- fungibleBurnAmount = BigInt(fungibleBurnAmount);
1323
- const hasNFT = burnRequest.nft !== undefined;
1324
-
1325
- let utxoIds: Utxo[] = [];
1326
- let changeSendRequests: TokenSendRequest[];
1327
- if (hasNFT) {
1328
- // does not have FT tokens, let us destroy the token completely
1329
- if (totalFungibleAmount === 0n) {
1330
- changeSendRequests = [];
1331
- utxoIds.push(tokenUtxos[0]);
1332
- } else {
1333
- // add utxos to spend from
1334
- let available = 0n;
1335
- for (const token of tokenUtxos.filter((val) => val.token?.amount)) {
1336
- utxoIds.push(token);
1337
- available += token.token!.amount;
1338
- if (available >= fungibleBurnAmount) {
1339
- break;
1340
- }
1341
- }
1342
-
1343
- // if there are FT, reduce their amount
1344
- const newAmount = totalFungibleAmount - fungibleBurnAmount;
1345
- const safeNewAmount = newAmount < 0n ? 0n : newAmount;
1346
- changeSendRequests = [
1347
- new TokenSendRequest({
1348
- cashaddr:
1349
- burnRequest.cashaddr || toTokenaddr(this.getChangeAddress()),
1350
- category: burnRequest.category,
1351
- nft: burnRequest.nft,
1352
- amount: safeNewAmount,
1353
- value: tokenUtxos[0].satoshis,
1354
- }),
1355
- ];
1356
- }
1357
- } else {
1358
- // if we are burning last fungible tokens, let us destroy the token completely
1359
- if (totalFungibleAmount === fungibleBurnAmount) {
1360
- changeSendRequests = [];
1361
- utxoIds.push(...tokenUtxos);
1362
- } else {
1363
- // add utxos to spend from
1364
- let available = 0n;
1365
- for (const token of tokenUtxos.filter((val) => val.token?.amount)) {
1366
- utxoIds.push(token);
1367
- available += token.token!.amount;
1368
- if (available >= fungibleBurnAmount) {
1369
- break;
1370
- }
1371
- }
1372
-
1373
- // reduce the FT amount
1374
- const newAmount = available - fungibleBurnAmount;
1375
- const safeNewAmount = newAmount < 0n ? 0n : newAmount;
1376
- changeSendRequests = [
1377
- new TokenSendRequest({
1378
- cashaddr:
1379
- burnRequest.cashaddr || toTokenaddr(this.getChangeAddress()),
1380
- category: burnRequest.category,
1381
- amount: safeNewAmount,
1382
- value: tokenUtxos.reduce((a, c) => a + c.satoshis, 0n),
1383
- }),
1384
- ];
1385
- }
1386
- }
1387
-
1388
- const opReturn = OpReturnData.fromString(message || "");
1389
- return this.send([opReturn, ...changeSendRequests], {
1390
- ...options,
1391
- checkTokenQuantities: false,
1392
- queryBalance: false,
1393
- ensureUtxos: utxoIds.length > 0 ? utxoIds : undefined,
1394
- tokenOperation: "burn",
1395
- });
1396
- }
1397
-
1398
- /**
1399
- * getTokenUtxos Get unspent token outputs for the wallet
1400
- * will return utxos only for the specified token if `category` provided
1401
- * @param {string?} category category to filter utxos by, if not set will return utxos from all tokens
1402
- * @returns {Utxo[]} token utxos
1403
- */
1404
- public async getTokenUtxos(category?: string): Promise<Utxo[]> {
1405
- const utxos = await this.getUtxos();
1406
- return utxos.filter((val) =>
1407
- category ? val.token?.category === category : val.token
1408
- );
1409
- }
1410
-
1411
- /**
1412
- * getTokenBalance Gets fungible token balance
1413
- * for NFT token balance see @ref getNftTokenBalance
1414
- * @param {string} category category to get balance for
1415
- * @returns {bigint} fungible token balance
1416
- */
1417
- public async getTokenBalance(category: string): Promise<bigint> {
1418
- const utxos = (await this.getTokenUtxos(category)).filter(
1419
- (val) => val.token?.amount
1420
- );
1421
- return sumTokenAmounts(utxos, category);
1422
- }
1423
-
1424
- /**
1425
- * getNftTokenBalance Gets non-fungible token (NFT) balance for a particular category
1426
- * disregards fungible token balances
1427
- * for fungible token balance see @ref getTokenBalance
1428
- * @param {string} category category to get balance for
1429
- * @returns {number} non-fungible token balance
1430
- */
1431
- public async getNftTokenBalance(category: string): Promise<number> {
1432
- const utxos = (await this.getTokenUtxos(category)).filter(
1433
- (val) => val.token?.nft?.commitment !== undefined
1434
- );
1435
- return utxos.length;
1436
- }
1437
-
1438
- /**
1439
- * getAllTokenBalances Gets all fungible token balances in this wallet
1440
- * @returns {Object} a map [category => balance] for all tokens in this wallet
1441
- */
1442
- public async getAllTokenBalances(): Promise<{ [category: string]: bigint }> {
1443
- const result = {};
1444
- const utxos = (await this.getTokenUtxos()).filter(
1445
- (val) => val.token?.amount
1446
- );
1447
- for (const utxo of utxos) {
1448
- if (!result[utxo.token!.category]) {
1449
- result[utxo.token!.category] = 0n;
1450
- }
1451
- result[utxo.token!.category] += utxo.token!.amount;
1452
- }
1453
- return result;
1454
- }
1455
-
1456
- /**
1457
- * getAllNftTokenBalances Gets all non-fungible token (NFT) balances in this wallet
1458
- * @returns {Object} a map [category => balance] for all NFTs in this wallet
1459
- */
1460
- public async getAllNftTokenBalances(): Promise<{
1461
- [category: string]: number;
1462
- }> {
1463
- const result = {};
1464
- const utxos = (await this.getTokenUtxos()).filter(
1465
- (val) => val.token?.nft?.commitment !== undefined
1466
- );
1467
- for (const utxo of utxos) {
1468
- if (!result[utxo.token!.category]) {
1469
- result[utxo.token!.category] = 0;
1470
- }
1471
- result[utxo.token!.category] += 1;
1472
- }
1473
- return result;
1474
- }
1475
- //#endregion Cashtokens
1476
-
1477
- public sign(
1478
- message: string,
1479
- privateKey: Uint8Array | undefined = undefined
1480
- ): SignedMessageResponseI {
1481
- if (!privateKey) {
1482
- throw new Error("Signing private key not provided");
1483
- }
1484
- return new SignedMessage().sign(message, privateKey);
1485
- }
1486
-
1487
- // Convenience wrapper to verify interface
1488
- public verify(
1489
- message: string,
1490
- sig: string,
1491
- address?: string,
1492
- publicKey?: Uint8Array
1493
- ): VerifyMessageResponseI {
1494
- if (!address && !publicKey) {
1495
- throw new Error(
1496
- "Either address or publicKey must be provided for verification"
1497
- );
1498
- }
1499
-
1500
- return new SignedMessage().verify(message, sig, address, publicKey);
1501
- }
1502
- }
1503
-
1504
- /**
1505
- * _checkContextSafety (internal) if running in nodejs, will disable saving
1506
- * mainnet wallets on public servers if ALLOW_MAINNET_USER_WALLETS is set to false
1507
- * @param {BaseWallet} wallet a wallet
1508
- */
1509
- export const _checkContextSafety = function (wallet: BaseWallet) {
1510
- if (getRuntimePlatform() === "node") {
1511
- if (process.env.ALLOW_MAINNET_USER_WALLETS === `false`) {
1512
- if (wallet.network === NetworkType.Mainnet) {
1513
- throw Error(
1514
- `Refusing to save wallet in an open public database, remove ALLOW_MAINNET_USER_WALLETS="false", if this service is secure and private`
1515
- );
1516
- }
1517
- }
1518
- }
1519
- };
1520
-
1521
- /**
1522
- * getNamedWalletId - get the full wallet id from the database
1523
- *
1524
- * @param name user friendly wallet alias
1525
- * @param dbName name under which the wallet will be stored in the database
1526
- *
1527
- * @returns boolean
1528
- */
1529
- export async function getNamedWalletId(
1530
- name: string,
1531
- dbName?: string
1532
- ): Promise<string | undefined> {
1533
- if (name.length === 0) {
1534
- throw Error("Named wallets must have a non-empty name");
1535
- }
1536
-
1537
- dbName = dbName ? dbName : (dbName as string);
1538
- let db = getStorageProvider(dbName);
1539
-
1540
- if (db) {
1541
- await db.init();
1542
- let savedWalletRecord = await db.getWallet(name);
1543
- await db.close();
1544
- if (savedWalletRecord !== undefined) {
1545
- return savedWalletRecord.wallet;
1546
- } else {
1547
- throw Error(`No record was found for ${name} in db: ${dbName}`);
1548
- }
1549
- } else {
1550
- throw Error(
1551
- "No database was available or configured to store the named wallet."
1552
- );
1553
- }
1554
- }
1555
-
1556
- export function getStorageProvider(
1557
- dbName: string
1558
- ): StorageProvider | undefined {
1559
- if (!BaseWallet.StorageProvider) {
1560
- return undefined;
1561
- }
1562
- return new (BaseWallet.StorageProvider as any)(dbName);
1563
- }