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.
- package/dist/module/cache/IndexedDbCache.d.ts +1 -1
- package/dist/module/cache/IndexedDbCache.d.ts.map +1 -1
- package/dist/module/cache/MemoryCache.d.ts +1 -1
- package/dist/module/cache/MemoryCache.d.ts.map +1 -1
- package/dist/module/cache/WebStorageCache.d.ts +1 -1
- package/dist/module/cache/WebStorageCache.d.ts.map +1 -1
- package/dist/module/cache/walletCache.d.ts +16 -6
- package/dist/module/cache/walletCache.d.ts.map +1 -1
- package/dist/module/cache/walletCache.js +92 -34
- package/dist/module/cache/walletCache.js.map +1 -1
- package/dist/module/cli.js +1 -2
- package/dist/module/cli.js.map +1 -1
- package/dist/module/db/index.d.ts +1 -1
- package/dist/module/db/index.d.ts.map +1 -1
- package/dist/module/db/index.js +1 -1
- package/dist/module/db/index.js.map +1 -1
- package/dist/module/enum.d.ts +1 -1
- package/dist/module/enum.d.ts.map +1 -1
- package/dist/module/history/getHistory.d.ts +1 -1
- package/dist/module/history/getHistory.d.ts.map +1 -1
- package/dist/module/history/getHistory.js +3 -3
- package/dist/module/history/getHistory.js.map +1 -1
- package/dist/module/index.d.ts +25 -22
- package/dist/module/index.d.ts.map +1 -1
- package/dist/module/index.js +33 -26
- package/dist/module/index.js.map +1 -1
- package/dist/module/interface.d.ts +12 -2
- package/dist/module/interface.d.ts.map +1 -1
- package/dist/module/interface.js.map +1 -1
- package/dist/module/libauth.d.ts +1 -1
- package/dist/module/libauth.d.ts.map +1 -1
- package/dist/module/libauth.js +1 -1
- package/dist/module/libauth.js.map +1 -1
- package/dist/module/mine/mine.d.ts +2 -7
- package/dist/module/mine/mine.d.ts.map +1 -1
- package/dist/module/mine/mine.js +6 -27
- package/dist/module/mine/mine.js.map +1 -1
- package/dist/module/network/Connection.d.ts +1 -12
- package/dist/module/network/Connection.d.ts.map +1 -1
- package/dist/module/network/Connection.js +24 -33
- package/dist/module/network/Connection.js.map +1 -1
- package/dist/module/network/ElectrumNetworkProvider.d.ts +16 -17
- package/dist/module/network/ElectrumNetworkProvider.d.ts.map +1 -1
- package/dist/module/network/ElectrumNetworkProvider.js +91 -93
- package/dist/module/network/ElectrumNetworkProvider.js.map +1 -1
- package/dist/module/network/MockNetworkProvider.d.ts +28 -0
- package/dist/module/network/MockNetworkProvider.d.ts.map +1 -0
- package/dist/module/network/MockNetworkProvider.js +74 -0
- package/dist/module/network/MockNetworkProvider.js.map +1 -0
- package/dist/module/network/NetworkProvider.d.ts +9 -2
- package/dist/module/network/NetworkProvider.d.ts.map +1 -1
- package/dist/module/network/configuration.d.ts +2 -4
- package/dist/module/network/configuration.d.ts.map +1 -1
- package/dist/module/network/configuration.js +25 -50
- package/dist/module/network/configuration.js.map +1 -1
- package/dist/module/network/constant.d.ts +7 -7
- package/dist/module/network/constant.d.ts.map +1 -1
- package/dist/module/network/constant.js +21 -24
- package/dist/module/network/constant.js.map +1 -1
- package/dist/module/network/default.d.ts +5 -3
- package/dist/module/network/default.d.ts.map +1 -1
- package/dist/module/network/default.js +29 -51
- package/dist/module/network/default.js.map +1 -1
- package/dist/module/network/index.d.ts +4 -4
- package/dist/module/network/index.d.ts.map +1 -1
- package/dist/module/network/index.js +2 -2
- package/dist/module/network/index.js.map +1 -1
- package/dist/module/network/interface.d.ts +0 -6
- package/dist/module/network/interface.d.ts.map +1 -1
- package/dist/module/rate/ExchangeRate.d.ts +1 -1
- package/dist/module/rate/ExchangeRate.d.ts.map +1 -1
- package/dist/module/rate/ExchangeRate.js +13 -7
- package/dist/module/rate/ExchangeRate.js.map +1 -1
- package/dist/module/transaction/Wif.d.ts +3 -3
- package/dist/module/transaction/Wif.d.ts.map +1 -1
- package/dist/module/transaction/Wif.js +4 -4
- package/dist/module/transaction/Wif.js.map +1 -1
- package/dist/module/transaction/allocateFee.d.ts +1 -1
- package/dist/module/transaction/allocateFee.d.ts.map +1 -1
- package/dist/module/transaction/allocateFee.js +2 -2
- package/dist/module/transaction/allocateFee.js.map +1 -1
- package/dist/module/util/deriveCashaddr.js.map +1 -1
- package/dist/module/util/deriveNetwork.js +1 -1
- package/dist/module/util/deriveNetwork.js.map +1 -1
- package/dist/module/util/getAddrsByXpubKey.js +1 -1
- package/dist/module/util/getAddrsByXpubKey.js.map +1 -1
- package/dist/module/util/getXPubKey.js +1 -1
- package/dist/module/util/getXPubKey.js.map +1 -1
- package/dist/module/util/hd.d.ts +2 -0
- package/dist/module/util/hd.d.ts.map +1 -1
- package/dist/module/util/hd.js +41 -0
- package/dist/module/util/hd.js.map +1 -1
- package/dist/module/util/index.d.ts +7 -9
- package/dist/module/util/index.d.ts.map +1 -1
- package/dist/module/util/index.js +7 -9
- package/dist/module/util/index.js.map +1 -1
- package/dist/module/util/transaction.js +1 -1
- package/dist/module/util/transaction.js.map +1 -1
- package/dist/module/wallet/Base.d.ts +13 -9
- package/dist/module/wallet/Base.d.ts.map +1 -1
- package/dist/module/wallet/Base.js +54 -58
- package/dist/module/wallet/Base.js.map +1 -1
- package/dist/module/wallet/HDWallet.d.ts.map +1 -1
- package/dist/module/wallet/HDWallet.js +19 -38
- package/dist/module/wallet/HDWallet.js.map +1 -1
- package/dist/module/wallet/Util.d.ts +4 -22
- package/dist/module/wallet/Util.d.ts.map +1 -1
- package/dist/module/wallet/Util.js +75 -102
- package/dist/module/wallet/Util.js.map +1 -1
- package/dist/module/wallet/Watch.d.ts +24 -5
- package/dist/module/wallet/Watch.d.ts.map +1 -1
- package/dist/module/wallet/Watch.js +141 -17
- package/dist/module/wallet/Watch.js.map +1 -1
- package/dist/module/wallet/Wif.d.ts +2 -6
- package/dist/module/wallet/Wif.d.ts.map +1 -1
- package/dist/module/wallet/Wif.js +3 -69
- package/dist/module/wallet/Wif.js.map +1 -1
- package/dist/module/wallet/createWallet.d.ts +1 -1
- package/dist/module/wallet/createWallet.d.ts.map +1 -1
- package/dist/module/wallet/createWallet.js +2 -1
- package/dist/module/wallet/createWallet.js.map +1 -1
- package/dist/module/wallet/interface.d.ts +3 -2
- package/dist/module/wallet/interface.d.ts.map +1 -1
- package/dist/module/wallet/model.d.ts +2 -1
- package/dist/module/wallet/model.d.ts.map +1 -1
- package/dist/module/wallet/model.js +1 -1
- package/dist/module/wallet/model.js.map +1 -1
- package/package.json +17 -29
- package/dist/index.html +0 -9
- package/dist/mainnet-3.1.7.js +0 -2066
- package/dist/module/network/util.d.ts +0 -3
- package/dist/module/network/util.d.ts.map +0 -1
- package/dist/module/network/util.js +0 -27
- package/dist/module/network/util.js.map +0 -1
- package/dist/module/test/expect.d.ts +0 -12
- package/dist/module/test/expect.d.ts.map +0 -1
- package/dist/module/test/expect.js +0 -47
- package/dist/module/test/expect.js.map +0 -1
- package/dist/module/test/fetch.d.ts +0 -3
- package/dist/module/test/fetch.d.ts.map +0 -1
- package/dist/module/test/fetch.js +0 -32
- package/dist/module/test/fetch.js.map +0 -1
- package/dist/module/util/randomBytes.d.ts +0 -2
- package/dist/module/util/randomBytes.d.ts.map +0 -1
- package/dist/module/util/randomBytes.js +0 -13
- package/dist/module/util/randomBytes.js.map +0 -1
- package/dist/tsconfig.tsbuildinfo +0 -1
- package/src/cache/IndexedDbCache.test.ts +0 -15
- package/src/cache/IndexedDbCache.ts +0 -172
- package/src/cache/MemoryCache.test.ts +0 -15
- package/src/cache/MemoryCache.ts +0 -32
- package/src/cache/WebStorageCache.test.ts +0 -15
- package/src/cache/WebStorageCache.ts +0 -38
- package/src/cache/index.ts +0 -2
- package/src/cache/interface.ts +0 -9
- package/src/cache/walletCache.ts +0 -254
- package/src/chain.ts +0 -3
- package/src/cli.ts +0 -32
- package/src/config.ts +0 -23
- package/src/constant.ts +0 -27
- package/src/db/ExchangeRateProvider.ts +0 -28
- package/src/db/StorageProvider.ts +0 -64
- package/src/db/index.ts +0 -2
- package/src/db/interface.ts +0 -11
- package/src/enum.ts +0 -34
- package/src/history/getHistory.test.ts +0 -290
- package/src/history/getHistory.ts +0 -411
- package/src/history/interface.ts +0 -24
- package/src/index.ts +0 -48
- package/src/interface.ts +0 -72
- package/src/libauth.ts +0 -11
- package/src/message/index.ts +0 -2
- package/src/message/interface.ts +0 -40
- package/src/message/signed.test.ts +0 -309
- package/src/message/signed.ts +0 -201
- package/src/mine/index.ts +0 -1
- package/src/mine/mine.test.ts +0 -10
- package/src/mine/mine.ts +0 -42
- package/src/network/Connection.test.ts +0 -51
- package/src/network/Connection.ts +0 -73
- package/src/network/ElectrumNetworkProvider.ts +0 -657
- package/src/network/NetworkProvider.ts +0 -180
- package/src/network/Rpc.test.ts +0 -130
- package/src/network/configuration.test.ts +0 -59
- package/src/network/configuration.ts +0 -72
- package/src/network/constant.ts +0 -43
- package/src/network/default.ts +0 -120
- package/src/network/electrum.test.ts +0 -28
- package/src/network/getRelayFeeCache.test.ts +0 -15
- package/src/network/getRelayFeeCache.ts +0 -23
- package/src/network/index.ts +0 -14
- package/src/network/interface.ts +0 -80
- package/src/network/util.test.ts +0 -24
- package/src/network/util.ts +0 -30
- package/src/rate/ExchangeRate.test.headless.js +0 -35
- package/src/rate/ExchangeRate.test.ts +0 -51
- package/src/rate/ExchangeRate.ts +0 -142
- package/src/test/expect.ts +0 -59
- package/src/test/fetch.ts +0 -39
- package/src/test/json.test.ts +0 -13
- package/src/transaction/Wif.ts +0 -680
- package/src/transaction/allocateFee.test.ts +0 -298
- package/src/transaction/allocateFee.ts +0 -149
- package/src/util/amountInSatoshi.test.ts +0 -27
- package/src/util/amountInSatoshi.ts +0 -33
- package/src/util/asSendRequestObject.ts +0 -81
- package/src/util/base64.test.ts +0 -39
- package/src/util/base64.ts +0 -12
- package/src/util/browserNotSupported.ts +0 -7
- package/src/util/checkForEmptySeed.ts +0 -9
- package/src/util/checkUtxos.ts +0 -29
- package/src/util/checkWifNetwork.ts +0 -24
- package/src/util/convert.test.ts +0 -46
- package/src/util/convert.ts +0 -50
- package/src/util/delay.ts +0 -3
- package/src/util/deriveCashaddr.test.ts +0 -164
- package/src/util/deriveCashaddr.ts +0 -143
- package/src/util/deriveLockscript.ts +0 -16
- package/src/util/deriveNetwork.ts +0 -19
- package/src/util/derivePublicKeyHash.test.ts +0 -55
- package/src/util/derivePublicKeyHash.ts +0 -64
- package/src/util/floor.test.ts +0 -21
- package/src/util/floor.ts +0 -4
- package/src/util/getAddrsByXpubKey.test.ts +0 -115
- package/src/util/getAddrsByXpubKey.ts +0 -86
- package/src/util/getRuntimePlatform.test.headless.js +0 -40
- package/src/util/getRuntimePlatform.test.ts +0 -5
- package/src/util/getRuntimePlatform.ts +0 -31
- package/src/util/getUsdRate.ts +0 -5
- package/src/util/getXPubKey.ts +0 -39
- package/src/util/hash160.test.ts +0 -18
- package/src/util/hash160.ts +0 -12
- package/src/util/hd.ts +0 -16
- package/src/util/header.test.ts +0 -34
- package/src/util/header.ts +0 -26
- package/src/util/index.ts +0 -33
- package/src/util/randomBytes.ts +0 -13
- package/src/util/randomInt.test.ts +0 -15
- package/src/util/randomInt.ts +0 -4
- package/src/util/sanitizeAddress.ts +0 -10
- package/src/util/sanitizeUnit.ts +0 -11
- package/src/util/satoshiToAmount.test.ts +0 -6
- package/src/util/satoshiToAmount.ts +0 -33
- package/src/util/sumSendRequestAmounts.ts +0 -34
- package/src/util/sumUtxoValue.ts +0 -27
- package/src/util/transaction.ts +0 -10
- package/src/wallet/Base.ts +0 -1563
- package/src/wallet/Cashtokens.test.headless.js +0 -730
- package/src/wallet/Cashtokens.test.ts +0 -1411
- package/src/wallet/HDWallet.test.ts +0 -1086
- package/src/wallet/HDWallet.ts +0 -992
- package/src/wallet/Util.test.ts +0 -134
- package/src/wallet/Util.ts +0 -191
- package/src/wallet/WalletCache.test.ts +0 -45
- package/src/wallet/Watch.ts +0 -441
- package/src/wallet/Wif.bip39.test.ts +0 -48
- package/src/wallet/Wif.test.ts +0 -1189
- package/src/wallet/Wif.ts +0 -687
- package/src/wallet/Wif.watchOnly.test.ts +0 -58
- package/src/wallet/createWallet.ts +0 -238
- package/src/wallet/enum.ts +0 -18
- package/src/wallet/interface.ts +0 -102
- package/src/wallet/model.test.ts +0 -24
- package/src/wallet/model.ts +0 -352
- package/tsconfig.browser.json +0 -11
- package/tsconfig.json +0 -33
- package/webpack.config.cjs +0 -132
package/src/wallet/Base.ts
DELETED
|
@@ -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
|
-
}
|